diff --git a/LICENSE b/LICENSE index 3d067ad..93baa59 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ The instructions and text in this tutorial (the "software") are licensed under the zlib License. - (C) 2016-2017 Akuli + (C) 2016-2021 Akuli This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/README.md b/README.md index 3fa6ac2..f8e350d 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -# Python programming tutorial +# Python programming tutorial for beginners -This is a Python 3 programming tutorial for beginners. If you have never -programmed before click [here](basics/what-is-programming.md) to find -out what programming is like and get started. +This is a concise Python 3 programming tutorial for people who think +that reading is boring. I try to show everything with simple code +examples; there are no long and complicated explanations with fancy +words. If you have never programmed before click +[here](basics/what-is-programming.md) to find out what programming is +like and get started. This tutorial is aimed at people with no programming experience at all or very little programming experience. If you have programmed a lot in the past using some other language you may want to read [the official tutorial](https://docs.python.org/3/tutorial/) instead. -You can use Python 3.2 or any newer Python with this tutorial. Don't use -Python 2. If you write a Python 2 program now someone will need to port -it to Python 3 later, so it's best to just write Python 3 to begin with. -Python 3 code will work just fine in Python 4, so you don't need to -worry about that. +You can use Python 3.6 or any newer Python with this tutorial. **Don't +use Python 2 because it's no longer supported.** ## List of contents @@ -35,7 +35,7 @@ to learn more about whatever you want after studying it. 9. [Handy stuff with strings](basics/handy-stuff-strings.md) 10. [Lists and tuples](basics/lists-and-tuples.md) 11. [Loops](basics/loops.md) -12. [Trey Hunner: zip and enumerate](basics/trey-hunner-zip-and-enumerate.md) +12. [zip and enumerate](basics/zip-and-enumerate.md) 13. [Dictionaries](basics/dicts.md) 14. [Defining functions](basics/defining-functions.md) 15. [Writing a larger program](basics/larger-program.md) @@ -44,6 +44,7 @@ to learn more about whatever you want after studying it. 18. [Modules](basics/modules.md) 19. [Exceptions](basics/exceptions.md) 20. [Classes](basics/classes.md) +21. [Docstrings](basics/docstrings.md) ### Advanced @@ -52,7 +53,7 @@ section. Most of the techniques explained here are great when you're working on a large project, and your code would be really repetitive without these things. -You can experient with these things freely, but please **don't use these +You can experiment with these things freely, but please **don't use these techniques just because you know how to use them.** Prefer the simple techniques from the Basics part instead when possible. Simple is better than complex. @@ -66,7 +67,7 @@ than complex. - **Important:** [getting help](getting-help.md) - [Contact me](contact-me.md) -- Answers for excercises in [basics](basics/answers.md) and +- Answers for exercises in [basics](basics/answers.md) and [advanced](advanced/answers.md) sections - [The TODO list](TODO.md) @@ -103,17 +104,15 @@ pull with git and run `make-html.py` again. ## Authors -I'm Akuli and I have written most of this tutorial, but these people -have helped me with it: -- [SpiritualForest](https://github.com/SpiritualForest): Lots of typing - error fixes. -- [theelous3](https://github.com/theelous3): Small improvements and fixes. +I'm Akuli and I have written most of this tutorial, but other people have helped me with it. +See [github's contributors page](https://github.com/Akuli/python-tutorial/graphs/contributors) for details. *** -If you have trouble with this tutorial please [tell me about -it](./contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](./contact-me.md) and I'll make this tutorial better, +or [ask for help online](./getting-help.md). +If you like this tutorial, please [give it a star](./README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/TODO.md b/TODO.md index 6cca320..ff1ecde 100644 --- a/TODO.md +++ b/TODO.md @@ -35,11 +35,15 @@ This tutorial is not complete. It still needs: - "What the heck is this?" section for stuff i haven't talked about - regexes +- add a screenshot about geany's running settings to + basics/editor-setup.md + *** -If you have trouble with this tutorial please [tell me about -it](./contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](./contact-me.md) and I'll make this tutorial better, +or [ask for help online](./getting-help.md). +If you like this tutorial, please [give it a star](./README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/advanced/README.md b/advanced/README.md index cfa964c..bbb46e5 100644 --- a/advanced/README.md +++ b/advanced/README.md @@ -8,7 +8,7 @@ section. Most of the techniques explained here are great when you're working on a large project, and your code would be really repetitive without these things. -You can experient with these things freely, but please **don't use these +You can experiment with these things freely, but please **don't use these techniques just because you know how to use them.** Prefer the simple techniques from the Basics part instead when possible. Simple is better than complex. @@ -20,9 +20,10 @@ than complex. *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/advanced/answers.md b/advanced/answers.md index d6a4371..ca52c7f 100644 --- a/advanced/answers.md +++ b/advanced/answers.md @@ -1,9 +1,10 @@ *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/advanced/datatypes.md b/advanced/datatypes.md index 12a670f..456d777 100644 --- a/advanced/datatypes.md +++ b/advanced/datatypes.md @@ -1,10 +1,10 @@ # Handy data types in the standard library -[](this doesn't explain how dict.setdefault and collections.defaultdict - work because they're not as simple as the things that are here and i - don't actually use them that much) +[comment]: # (this doesn't explain how dict.setdefault and collections.defaultdict) +[comment]: # (work because they're not as simple as the things that are here and i) +[comment]: # (don't actually use them that much) -Now we know how to ues lists, tuples and dictionaries. They are commonly +Now we know how to use lists, tuples and dictionaries. They are commonly used data types in Python, and there's nothing wrong with them. In this chapter we'll learn more data types that make some things easier. You can always do everything with lists and dictionaries, but these data @@ -55,7 +55,7 @@ True We can also convert anything [iterable](../basics/loops.md#summary) to a set [by calling the -class](../basics/classes.md#why-should-I-use-custom-classes-in-my-projects). +class](../basics/classes.md#what-are-classes). ```python >>> set('hello') @@ -65,7 +65,7 @@ class](../basics/classes.md#why-should-I-use-custom-classes-in-my-projects). >>> ``` -When we did `set('hello')` we lost one `h` and the set ended up in a +When we did `set('hello')` we lost one `l` and the set ended up in a different order because sets don't contain duplicates or keep track of their order. @@ -316,68 +316,25 @@ TypeError: unsupported operand type(s) for +: 'dict' and 'dict' >>> ``` -Dictionaries have an `update` method that adds everything from another -dictionary into it. So we can merge dictionaries like this: +Usually it's easiest to do this: ```python ->>> merged = {} ->>> merged.update({'a': 1, 'b': 2}) ->>> merged.update({'c': 3}) ->>> merged -{'c': 3, 'b': 2, 'a': 1} ->>> -``` - -Or we can [write a function](../basics/defining-functions.md) like this: - -```python ->>> def merge_dicts(dictlist): -... result = {} -... for dictionary in dictlist: -... result.update(dictionary) -... return result -... ->>> merge_dicts([{'a': 1, 'b': 2}, {'c': 3}]) -{'c': 3, 'b': 2, 'a': 1} ->>> +>>> dict1 = {'a': 1, 'b': 2} +>>> dict2 = {'c': 3} +>>> {**dict1, **dict2} +{'a': 1, 'b': 2, 'c': 3} ``` -Kind of like counting things, merging dictionaries is also a commonly -needed thing and there's a class just for it in the `collections` -module. It's called ChainMap: +Dictionaries also have an `update` method that adds everything from another +dictionary into it, and you can use that too. This was the most common way to +do it before Python supported `{**dict1, **dict2}`. ```python ->>> import collections ->>> merged = collections.ChainMap({'a': 1, 'b': 2}, {'c': 3}) +>>> merged = {} +>>> merged.update({'a': 1, 'b': 2}) +>>> merged.update({'c': 3}) >>> merged -ChainMap({'b': 2, 'a': 1}, {'c': 3}) ->>> -``` - -Our `merged` is kind of like the Counter object we created earlier. It's -not a dictionary, but it behaves like a dictionary. - -```python ->>> for key, value in merged.items(): -... print(key, value) -... -c 3 -b 2 -a 1 ->>> dict(merged) -{'c': 3, 'b': 2, 'a': 1} ->>> -``` - -Starting with Python 3.5 it's possible to merge dictionaries like this. -**Don't do this unless you are sure that no-one will need to run your -code on Python versions older than 3.5.** - -```python ->>> first = {'a': 1, 'b': 2} ->>> second = {'c': 3, 'd': 4} ->>> {**first, **second} -{'d': 4, 'c': 3, 'a': 1, 'b': 2} +{'a': 1, 'b': 2, 'c': 3} >>> ``` @@ -390,13 +347,14 @@ code on Python versions older than 3.5.** *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See [LICENSE](../LICENSE). -[Previous](../basics/classes.md) | [Next](functions.md) | +[Previous](../basics/docstrings.md) | [Next](functions.md) | [List of contents](../README.md#advanced) diff --git a/advanced/functions.md b/advanced/functions.md index ff097ae..8f54e60 100644 --- a/advanced/functions.md +++ b/advanced/functions.md @@ -34,7 +34,7 @@ wherever the function is called: ```python def login(): ... - return username, password + return (username, password) username, password = login() @@ -50,12 +50,12 @@ For example, instead of this... ```python def get_new_info(username): - print("Changing user information of %s." % username) + print(f"Changing user information of {username}.") username = input("New username: ") password = input("New password: ") fullname = input("Full name: ") phonenumber = input("Phone number: ") - return username, password, fullname, phonenumber + return (username, password, fullname, phonenumber) ``` ...you could do this: @@ -66,7 +66,7 @@ class User: # them here def change_info(self): - print("Changing user information of %s." % self.username) + print(f"Changing user information of {self.username}.") self.username = input("New username: ") self.password = input("New password: ") self.fullname = input("Full name: ") @@ -222,7 +222,7 @@ moving file1.txt to file2.txt Oh crap, that's not what we wanted at all. We have just lost the original `file2.txt`! -The problem was that now `overwrite` was `'file2.txt'`, and the +The problem was that now `overwrite` was `'file3.txt'`, and the `if overwrite` part [treated the string as True](../basics/what-is-true.md) and deleted the file. That's not nice. @@ -290,9 +290,10 @@ does, so using keyword-only arguments makes sense. *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/advanced/iters.md b/advanced/iters.md index 58cebbc..450e763 100644 --- a/advanced/iters.md +++ b/advanced/iters.md @@ -74,7 +74,7 @@ twice. >>> ``` -We have also used [enumerate](../basics/trey-hunner-zip-and-enumerate.md) +We have also used [enumerate](../basics/zip-and-enumerate.md) before, and it actually remembers its position also: ```python @@ -246,6 +246,25 @@ while True: print(thing) ``` +## Checking if object is iterable or not + +There is an easy way of checking if an object in python is iterable or not. The following code will do the needful. +```python +>>> def check(A): +... try: +... st = iter(A) +... print('yes') +... except TypeError: +... print('no') +... +>>> check(25) +no +>>> check([25,35]) +yes +>>> +``` +Here you can observe that the 25 is an integer, so it is not iterable, but [25,35] is a list which is iterable so it outputs no and yes respectively. + ## Generators It's possible to create a custom iterator with a class that defines an @@ -442,12 +461,12 @@ does the same thing as our `count()`. generator runs it to the next yield and gives us the value it yielded. - [The itertools module](https://docs.python.org/3/library/itertools.html) contains many useful iterator-related things. - *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/advanced/magicmethods.md b/advanced/magicmethods.md index c7f6e28..c333bbe 100644 --- a/advanced/magicmethods.md +++ b/advanced/magicmethods.md @@ -112,13 +112,10 @@ the message is 'hello' Combining `repr()` with [string formatting](../basics/handy-stuff-strings.md#string-formatting) is also -easy. `%` formatting has a `%r` formatter, and `.format()` formatting -has a `!r` flag. +easy. ```python ->>> print("the message is %r" % (message,)) -the message is 'hello' ->>> print("the message is {!r}".format(message)) +>>> print(f"the message is {repr(message)}") the message is 'hello' >>> ``` @@ -155,8 +152,7 @@ follow one of these styles: ... self.name = name ... self.founding_year = founding_year ... def __repr__(self): - ... return 'Website(name=%r, founding_year=%r)' % ( - ... self.name, self.founding_year) + ... return f'Website(name={repr(self.name)}, founding_year={repr(self.founding_year)})' ... >>> github = Website('GitHub', 2008) >>> github @@ -174,8 +170,7 @@ follow one of these styles: ... self.name = name ... self.founding_year = founding_year ... def __repr__(self): - ... return '' % ( - ... self.name, self.founding_year) + ... return f'' ... >>> github = Website('GitHub', 2008) >>> github @@ -235,9 +230,10 @@ are not meant to be imported. *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/README.md b/basics/README.md index 283cc57..23d1059 100644 --- a/basics/README.md +++ b/basics/README.md @@ -17,7 +17,7 @@ to learn more about whatever you want after studying it. 9. [Handy stuff with strings](handy-stuff-strings.md) 10. [Lists and tuples](lists-and-tuples.md) 11. [Loops](loops.md) -12. [Trey Hunner: zip and enumerate](trey-hunner-zip-and-enumerate.md) +12. [zip and enumerate](zip-and-enumerate.md) 13. [Dictionaries](dicts.md) 14. [Defining functions](defining-functions.md) 15. [Writing a larger program](larger-program.md) @@ -26,12 +26,14 @@ to learn more about whatever you want after studying it. 18. [Modules](modules.md) 19. [Exceptions](exceptions.md) 20. [Classes](classes.md) +21. [Docstrings](docstrings.md) *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/answers.md b/basics/answers.md index e4a313d..766ec70 100644 --- a/basics/answers.md +++ b/basics/answers.md @@ -25,14 +25,16 @@ isn't exactly like mine but it works just fine it's ok, and you can `print('You entered:', something)`. 2. The broken code has mostly the same issues as exercise 1. Here are - the problems that excercise 1 doesn't have: + the problems that exercise 1 doesn't have: + - The if-elif-else has a blank line at a confusing place. Delete it. + - After deleting the code, it looks quite dense. Add a new blank + line before the `if`. - The elif line is missing a `:` at the end. - On the last line the comma is on the wrong side. `"bla bla,"` is - a string that **contains** a comma, but `"bla bla",` is a - string and a **separate** comma. In this exercise, the last - line should be - `print("I don't know what", something, "means.")` + a string that **contains** a comma, but `"bla bla",` is a + string and a **separate** comma. In this exercise, the last + line should be `print("I don't know what", something, "means.")` 3. We can simply ask the word with input and print `word * 1000`. @@ -64,7 +66,7 @@ isn't exactly like mine but it works just fine it's ok, and you can ```python no_space = input("Enter a word: ") yes_space = no_space + " " - print(yes_space * 999 + no_space) + print(yes_space*999 + no_space) ``` 5. Like this: @@ -77,12 +79,12 @@ isn't exactly like mine but it works just fine it's ok, and you can ``` 6. We can compare the word against an empty string (`""` or `''`) to - check if it's empty. In this example, the password is "s3cr3t". + check if it's empty. In this example, the password is "seKr3t". ```python word = input("Enter your password: ") - if word == "s3cr3t": + if word == "seKr3t": print("Welcome!") elif word == "": print("You didn't enter anything.") @@ -90,8 +92,7 @@ isn't exactly like mine but it works just fine it's ok, and you can print("Access denied.") ``` - This is not a good way to ask a password from the user because the - password isn't hidden in any way, but this is just an example. + Again, this is not a good way to ask a real password from the user. ## Handy stuff: Strings @@ -99,16 +100,7 @@ isn't exactly like mine but it works just fine it's ok, and you can just fine if we run it, but there's a problem. The last line is really long and it's hard to see what it does. - The solution is string formatting. At the time of writing this, I - recommend replacing the last line with one of these: - - ```python - print("You entered %s, %s, %s and %s." % (word1, word2, word3, word4)) - print("You entered {}, {}, {} and {}.".format(word1, word2, word3, word4)) - ``` - - In the future when most people will have Python 3.6 or newer, you - can also use this: + The solution is string formatting. I recommend replacing the last line with this: ```python print(f"You entered {word1}, {word2}, {word3} and {word4}.") @@ -155,10 +147,23 @@ isn't exactly like mine but it works just fine it's ok, and you can print(message, "!!!") print(message, "!!!") ``` - +3. In the code below, `palindrome_input[::-1]` is the string `palindrome_input` reversed. + For example, if `palindrome_input` is `"hello"`, then `palindrome_input[::-1]` is `"olleh"`. + ```python + palindrome_input = input("Enter a string: ") + if palindrome_input == palindrome_input[::-1]: + print("This string is a palindrome") + else: + print("This string is not a palindrome") + ``` ## Lists and tuples -1. When we run the program we get a weird error: +1. Look carefully. The `namelist` is written in `()` instead of `[]`, + so it's actually a tuple, not a list. Using confusing variable names + is of course a bad idea, but you shouldn't be surprised if someone + is doing that. Replace the `()` with `[]` and the code will work. + +2. When we run the program we get a weird error: Hello! Enter your name: my name @@ -189,7 +194,7 @@ isn't exactly like mine but it works just fine it's ok, and you can Python created a tuple automatically, but that's not what we wanted. If we remove the comma, everything works just fine. -2. Again, the code gives us a weird error message. +3. Again, the code gives us a weird error message. Enter your name: my name Traceback (most recent call last): @@ -212,7 +217,8 @@ isn't exactly like mine but it works just fine it's ok, and you can problems and solutions: - `namelist` is None. It should be `namelist.extend('theelous3')`, - not `namelist = namelist.extend('theelous3')`. + not `namelist = namelist.extend('theelous3')`. See [this + thing](using-functions.md#return-values). - Now the namelist is like `['wub_wub', ..., 't', 'h', 'e', 'e', ...]`. Python treated `'theelous3'` like a list so it added each of its characters to `namelist`. We can use `namelist.append('theelous3')` @@ -289,6 +295,36 @@ isn't exactly like mine but it works just fine it's ok, and you can print(converted_numbers) ``` +5. ``` python + row_count = int(input("Type the number of rows needed:")) + for column_count in range(1, row_count+1): + # Print numbers from 1 to column_count + for number in range(1, column_count+1): + print(number, end=" ") + print() # creates a new line for the next row + ``` + If the user enters 5, we want to do a row with 1 column, then 2 columns, and so on until 5 columns. + That would be `for column_count in range(1, 6)`, because the end of the range is excluded. + In general, we need to specify `row_count + 1` so that it actually ends at `row_count`. + The second loop is similar. + + Usually `print(number)` puts a newline character at the end of the line, so that the next print goes to the next line. + To get all numbers on the same line, we use a space instead of a newline character, + but we still need `print()` to add a newline character once we have printed the entire row. + + + +6. ```python + row_count=int(input("Type the number of rows needed:")) + + for line_number in range(1, row_count+1): + for number in range(line_number, row_count+1): + print(number, end=' ') + print() + ``` + Just like in the previous exercise, if the user enters 5, the first `for` loop gives the line numbers `1, 2, 3, 4, 5`.
+ For example, on line 2, we should print numbers from 2 to 5, as in `range(2, 6)`, or in general, `range(line_number, row_count+1)`. + ## Trey Hunner: zip and enumerate 1. Read some lines with `input` into a list and then enumerate it. @@ -428,9 +464,10 @@ isn't exactly like mine but it works just fine it's ok, and you can *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/classes.md b/basics/classes.md index 50649b1..0b2a284 100644 --- a/basics/classes.md +++ b/basics/classes.md @@ -1,17 +1,13 @@ -# Defining and using custom classes in Python +# Defining and using custom classes When I was getting started in Python I learned to make classes for tkinter GUI's before I understood how they work. Everything I did with classes worked, but I didn't understand how. Hopefully you'll first learn to understand classes, and then learn to use them. -This tutorial assumes that you know [how functions work](using-functions.md) -and [how to create your own functions](defining-functions.md). If you -don't I highly recommend learning that first, and then moving to classes. +## What are classes? -## Why should I use custom classes in my projects? - -Python comes with a lot of classes that you are already familiar with. +Python comes with many classes that we know already. ```python >>> str @@ -55,11 +51,11 @@ We can also get an instance's class with `type()`: >>> ``` -Let's say you make a program that processes data about websites. With a -custom class, you're not limited to `str`, `int` and other classes -Python comes with. Instead you can define a Website class, and make -Websites and process information about websites directly. Defining your -own object types like this is called **object-orientated programming**. +Let's say that we make a program that processes data about websites. +With a custom class, we're not limited to `str`, `int` and other classes +Python comes with. Instead we can define a Website class, and make +Websites and process information about websites directly. Defining our +own types like this is called **object-orientated programming**. ## First class @@ -81,6 +77,9 @@ Let's use it to define an empty class. >>> ``` +The `pass` is needed here, just like [when defining functions that do +nothing](defining-functions.md#first-functions). + Note that I named the class `Website`, not `website`. This way we know that it's a class. Built-in classes use lowercase names (like `str` instead of `Str`) because they are faster to type, but use CapsWord @@ -89,34 +88,34 @@ names for your classes. Now we can make a Website instance by calling the class. ```python ->>> stackoverflow = Website() ->>> stackoverflow +>>> github = Website() +>>> github <__main__.Website object at 0x7f36e4c456d8> ->>> type(stackoverflow) +>>> type(github) >>> ``` -We can say that `stackoverflow` is "a Website instance", "a Website +We can say that `github` is "a Website instance", "a Website object" or "a Website". All of these mean the same thing. -Now we can attach more information about stackoverflow to our Website. +Now we can attach more information about github to our Website. ```python ->>> stackoverflow.url = 'http://stackoverflow.com/' ->>> stackoverflow.founding_year = 2008 ->>> stackoverflow.free_to_use = True +>>> github.url = 'https://github.com/' +>>> github.founding_year = 2008 +>>> github.free_to_use = True >>> ``` We can also access the information easily. ```python ->>> stackoverflow.url -'http://stackoverflow.com/' ->>> stackoverflow.founding_year +>>> github.url +'https://github.com/' +>>> github.founding_year 2008 ->>> stackoverflow.free_to_use +>>> github.free_to_use True >>> ``` @@ -153,10 +152,10 @@ recommended to use it for code that needs to be reliable, but it's a handy way to see which attributes the instance contains. ```python ->>> stackoverflow.__dict__ +>>> github.__dict__ {'free_to_use': True, 'founding_year': 2008, - 'url': 'http://stackoverflow.com/'} + 'url': 'https://github.com/'} >>> effbot.__dict__ {} >>> @@ -177,7 +176,7 @@ True Seems to be working, but what happened to the instances? ```python ->>> stackoverflow.is_online +>>> github.is_online True >>> effbot.is_online True @@ -185,17 +184,17 @@ True ``` What was that? Setting `Website.is_online` to a value also set -`stackoverflow.is_online` and `effbot.is_online` to that value! +`github.is_online` and `effbot.is_online` to that value! -Actually, `is_online` is still not in stackoverflow's or effbot's -`__dict__`. stackoverflow and effbot get that attribute directly from +Actually, `is_online` is still not in github's or effbot's +`__dict__`. github and effbot get that attribute directly from the `Website` class. ```python ->>> stackoverflow.__dict__ +>>> github.__dict__ {'free_to_use': True, 'founding_year': 2008, - 'url': 'http://stackoverflow.com/'} + 'url': 'https://github.com/'} >>> effbot.__dict__ {} >>> @@ -203,13 +202,14 @@ the `Website` class. `Website.is_online` is `Website`'s class attribute, and in Python you can access class attributes through instances also, so in this case -`stackoverflow.is_online` points to `Website.is_online`. That can be +`github.is_online` points to `Website.is_online`. That can be confusing, which is why it's not recommended to use class attributes like -this. Use instance attributes instead, e.g. `stackoverflow.is_online = True`. +this. Use instance attributes instead, e.g. `github.is_online = True`. ## Functions and methods -Let's define a function that prints information about a website. +Let's [define a function](defining-functions.md) that prints information +about a website. ```python >>> def website_info(website): @@ -217,8 +217,8 @@ Let's define a function that prints information about a website. ... print("Founding year:", website.founding_year) ... print("Free to use:", website.free_to_use) ... ->>> website_info(stackoverflow) -URL: http://stackoverflow.com/ +>>> website_info(github) +URL: https://github.com/ Founding year: 2008 Free to use: True >>> @@ -230,49 +230,49 @@ Website class? ```python >>> Website.info = website_info ->>> Website.info(stackoverflow) -URL: http://stackoverflow.com/ +>>> Website.info(github) +URL: https://github.com/ Founding year: 2008 Free to use: True >>> ``` -It's working, but `Website.info(stackoverflow)` is a lot of typing, so -wouldn't `stackoverflow.info()` be much better? +It's working, but `Website.info(github)` is a lot of typing, so +wouldn't `github.info()` be much better? ```python ->>> stackoverflow.info() -URL: http://stackoverflow.com/ +>>> github.info() +URL: https://github.com/ Founding year: 2008 Free to use: True >>> ``` -What the heck happened? We didn't define a `stackoverflow.info`, it just +What the heck happened? We didn't define a `github.info`, it just magically worked! -`Website.info` is our `website_info` function, so `stackoverflow.info` +`Website.info` is our `website_info` function, so `github.info` should also be the same function. But `Website.info` takes a `website` -argument, which we didn't give it when we called `stackoverflow.info()`! +argument, which we didn't give it when we called `github.info()`! -But is `stackoverflow.info` the same thing as `Website.info`? +But is `github.info` the same thing as `Website.info`? ```python >>> Website.info ->>> stackoverflow.info +>>> github.info > >>> ``` It's not. -Instead, `stackoverflow.info` is a **method**. If we set a function as a +Instead, `github.info` is a **method**. If we set a function as a class attribute, the instances will have a method with the same name. Methods are "links" to the class attribute functions. So -`Website.info(stackoverflow)` does the same thing as `stackoverflow.info()`, -and when `stackoverflow.info()` is called it automatically gets -`stackoverflow` as an argument. +`Website.info(github)` does the same thing as `github.info()`, +and when `github.info()` is called it automatically gets +`github` as an argument. In other words, **`Class.method(instance)` does the same thing as `instance.method()`**. This also works with built-in classes, for @@ -285,26 +285,26 @@ it later? ```python >>> class Website: -... def info(self): # self will be stackoverflow +... def info(self): # self will be github ... print("URL:", self.url) ... print("Founding year:", self.founding_year) ... print("Free to use:", self.free_to_use) ... ->>> stackoverflow = Website() ->>> stackoverflow.url = 'http://stackoverflow.com/' ->>> stackoverflow.founding_year = 2008 ->>> stackoverflow.free_to_use = True ->>> stackoverflow.info() -URL: http://stackoverflow.com/ +>>> github = Website() +>>> github.url = 'https://github.com/' +>>> github.founding_year = 2008 +>>> github.free_to_use = True +>>> github.info() +URL: https://github.com/ Founding year: 2008 Free to use: True >>> ``` -It's working. The `self` argument in `Website.info` was `stackoverflow`. +It's working. The `self` argument in `Website.info` was `github`. You could call it something else too such as `me`, `this` or `instance`, but use `self` instead. Other Python users have gotten used to it, and -the official style guide recommens it also. +the official style guide recommends it also. We still need to set `url`, `founding_year` and `free_to_use` manually. Maybe we could add a method to do that? @@ -320,10 +320,10 @@ Maybe we could add a method to do that? ... print("Founding year:", self.founding_year) ... print("Free to use:", self.free_to_use) ... ->>> stackoverflow = Website() ->>> stackoverflow.initialize('http://stackoverflow.com/', 2008, True) ->>> stackoverflow.info() -URL: http://stackoverflow.com/ +>>> github = Website() +>>> github.initialize('https://github.com/', 2008, True) +>>> github.info() +URL: https://github.com/ Founding year: 2008 Free to use: True >>> @@ -331,9 +331,9 @@ Free to use: True That works. The attributes we defined in the initialize method are also available in the info method. We could also access them directly from -`stackoverflow`, for example with `stackoverflow.url`. +`github`, for example with `github.url`. -But we still need to call `stackoverflow.initialize`. In Python, there's +But we still need to call `github.initialize`. In Python, there's a "magic" method that runs when we create a new Website by calling the Website class. It's called `__init__` and it does nothing by default. If our `__init__` method takes other arguments than self we can call the @@ -350,9 +350,9 @@ class with arguments and they will be given to `__init__`. Like this: ... print("Founding year:", self.founding_year) ... print("Free to use:", self.free_to_use) ... ->>> stackoverflow = Website('http://stackoverflow.com/', 2008, True) ->>> stackoverflow.info() -URL: http://stackoverflow.com/ +>>> github = Website('https://github.com/', 2008, True) +>>> github.info() +URL: https://github.com/ Founding year: 2008 Free to use: True >>> @@ -416,13 +416,14 @@ print("You entered " + word + ".") *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See [LICENSE](../LICENSE). -[Previous](exceptions.md) | [Next](../advanced/datatypes.md) | +[Previous](exceptions.md) | [Next](docstrings.md) | [List of contents](../README.md#basics) diff --git a/basics/defining-functions.md b/basics/defining-functions.md index b17b28b..9b78b94 100644 --- a/basics/defining-functions.md +++ b/basics/defining-functions.md @@ -44,9 +44,9 @@ In this tutorial we'll learn to define a `print_box` function that prints text in a box. We can write the code for printing the box once, and then use it multiple times anywhere in the program. -Dividing a long program into simple functions also makes the code -easier to work with. If there's a problem with the code we can -test the functions one by one and find the problem easily. +[Dividing a long program into simple functions](larger-program.md) also +makes the code easier to work with. If there's a problem with the code +we can test the functions one by one and find the problem easily. ## First functions @@ -68,9 +68,15 @@ Let's use it to define a function that does nothing. >>> ``` -Seems to be working so far, we have a function. Actually it's just -a value that is assigned to a variable called `do_nothing`. Let's see -what happens if we call it. +Seems to be working so far, we have a function. It's just a value that +is assigned to a variable called `do_nothing`. You can ignore the +`0xblablabla` stuff for now. + +The `pass` is needed here because without it, Python doesn't know when +the function ends and it gives us a syntax error. We don't need the +`pass` when our functions contain something else. + +Let's see what happens if we call our function. ```python >>> do_nothing() @@ -171,20 +177,16 @@ However, modifying a global variable in-place from a function is easy. >>> ``` -This doesn't work if the value is of an immutable type, like string or -integer because immutable values cannot be modified in-place. -Fortunately, Python will tell us if something's wrong. +This only works for changing in-place, we cannot assign a new value to +the variable. ```python ->>> foo = 1 ->>> def bar(): -... foo += 1 +>>> def set_stuff_to_something_new(): +... stuff = ['more local stuff'] ... ->>> bar() -Traceback (most recent call last): - File "", line 1, in - File "", line 2, in bar -UnboundLocalError: local variable 'foo' referenced before assignment +>>> set_stuff_to_something_new() +>>> stuff +['global stuff', 'local stuff'] >>> ``` @@ -256,10 +258,6 @@ This function can be called in two ways: because `message = "hi"` and `some_function(message="hi")` do two completely different things. -Personally, I would use this function with a positional argument. It -only takes one argument, so I don't need to worry about which argument -is which. - Now it's time to solve our box printing problem: ```python @@ -283,8 +281,8 @@ def print_box(message, character): print(character * len(message)) ``` -Then we could change our existing code to always call `print_box` with -a star as the second argument: +Then we could change our code to always call `print_box` with a star as +the second argument: ```python print_box("Hello World", "*") @@ -355,11 +353,11 @@ need to: ## Output The built-in input function [returns a value](using-functions.md#return-values). -Can our function return a value also? +Can our function return a value too? ```python ->>> def times_two(x): -... return x * 2 +>>> def times_two(thing): +... return thing * 2 ... >>> times_two(3) 6 @@ -412,7 +410,7 @@ None ## Return or print? -There's two ways to output information from functions. They can print +There are two ways to output information from functions. They can print something or they can return something. So, should we print or return? Most of the time **returning makes functions much easier to use**. Think @@ -435,6 +433,47 @@ hi >>> ``` +## Common problems + +Functions are easy to understand, but you need to pay attention to how +you're calling them. Note that `some_function` and `some_function()` do +two completely different things. + +```python +>>> def say_hi(): +... print("howdy hi") +... +>>> say_hi # just checking what it is, doesn't run anything + +>>> say_hi() # this runs it +howdy hi +>>> +``` + +Typing `say_hi` just gives us the value of the `say_hi` variable, which +is the function we defined. But `say_hi()` **calls** that function, so +it runs and gives us a return value. The return value is None so the +`>>>` prompt [doesn't show it](variables.md#none). + +But we know that the print function shows None, so what happens if we +wrap the whole thing in `print()`? + +```python +>>> print(say_hi) # prints the function, just like plain say_hi + +>>> print(say_hi()) # runs the function and then prints the return value +howdy hi +None +>>> +``` + +The `print(say_hi())` thing looks a bit weird at first, but it's easy to +understand. There's a print inside `say_hi` and there's also the print +we just wrote, so two things are printed. Python first ran `say_hi()`, +and it returned None so Python did `print(None)`. Adding an extra +`print()` around a function call is actually a common mistake, and I +have helped many people with this problem. + ## Examples Ask yes/no questions. @@ -463,7 +502,7 @@ def ask_until_correct(prompt, correct_options, while True: answer = input(prompt + ' ') if answer in correct_options: - return answer # returning ends the function + return answer print(error_message) @@ -471,7 +510,7 @@ colors = ['red', 'yellow', 'blue', 'green', 'orange', 'pink', 'black', 'gray', 'white', 'brown'] choice = ask_until_correct("What's your favorite color?", colors, error_message="I don't know that color.") -print("Your favorite color is %s!" % choice) +print(f"Your favorite color is {choice}!") ``` ## Summary @@ -489,6 +528,8 @@ print("Your favorite color is %s!" % choice) function does. Returning also ends the function immediately. - Return a value instead of printing it if you need to do something with it after calling the function. +- Remember that `thing`, `thing()`, `print(thing)` and `print(thing())` + do different things. ## Exercises @@ -531,9 +572,10 @@ Answers for the first, second and third exercise are *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/dicts.md b/basics/dicts.md index ec4bf7e..597d1eb 100644 --- a/basics/dicts.md +++ b/basics/dicts.md @@ -61,7 +61,7 @@ favorite_pets = { ``` Here `'horusr'` and `'caisa64'` are **keys** in the dictionary, and -`'cats'` and `'cats and docs'` are their **values**. Dictionaries are +`'cats'` and `'cats and dogs'` are their **values**. Dictionaries are often named by their values. This dictionary has favorite pets as its values so I named the variable `favorite_pets`. @@ -111,10 +111,10 @@ Dictionaries have some similarities with lists. For example, both lists and dictionaries have a length. ```python ->>> len(names_and_pets) # contains two elements -2 ->>> len(favorite_pets) # contains two key:value pairs -2 +>>> len(names_and_pets) # contains three elements +3 +>>> len(favorite_pets) # contains three key:value pairs +3 >>> ``` @@ -324,13 +324,14 @@ Running the program might look like this: *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See [LICENSE](../LICENSE). -[Previous](trey-hunner-zip-and-enumerate.md) | [Next](defining-functions.md) | +[Previous](zip-and-enumerate.md) | [Next](defining-functions.md) | [List of contents](../README.md#basics) diff --git a/basics/docstrings.md b/basics/docstrings.md new file mode 100644 index 0000000..c30cd7c --- /dev/null +++ b/basics/docstrings.md @@ -0,0 +1,363 @@ +# Help and Docstrings + +In this tutorial we have used `help()` a few times. It's great and you +can use it as much as you want to. For example, running `help(str)` +displays a nice list of all string methods and explanations of what they +do, and `help(list.extend)` explains what extending something to a list +does. + +You can get help of many other things too. For example: + +```python +>>> stuff = [] +>>> help(stuff.append) +Help on built-in function append: + +append(object, /) method of builtins.list instance + Append object to the end of the list. + +>>> help(print) +Help on built-in function print in module builtins: + +print(...) + print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False) + + Prints the values to a stream, or to sys.stdout by default. + Optional keyword arguments: + ... +``` + +## Docstrings + +Let's see what happens if we [define a function](defining-functions.md) +and call `help()` on that. + +```python +>>> def thing(stuff): +... return stuff * 2 +... +>>> help(thing) +Help on function thing in module __main__: + +thing(stuff) +>>> +``` + +That sucked! We have no idea about what it does based on this. All we +know is that it takes a `stuff` argument. + +This is when documentation strings or docstrings come in. All we need to +do is to add a string to the beginning of our function and it will show +up in `help(the_function)`. Like this: + +```python +>>> def thing(stuff): +... "hello there" +... return stuff * 2 +... +>>> help(thing) +Help on function thing in module __main__: + +thing(stuff) + hello there +``` + +Note that docstrings are not comments. If you add a `# comment` to the +beginning of the function it won't show up in `help()`. + +## Multi-line strings + +When we did `help(print)`, we got more than one line of help. Maybe we +could do that in our own docstring too? + +```python +>>> def thing(): +... "This thing does stuff.\n\nIt always returns None." +... +>>> help(thing) +Help on function thing in module __main__: + +thing() + This thing does stuff. + + It always returns None. +>>> +``` + +That's better, but how what if we want to do 5 lines of prints? Our +`"stuff\n\nstuff\nstuff"` thing would be really long and hard to work +with. But Python has multi-line strings too. They work like this: + +```python +>>> """bla bla bla +... +... bla bla +... bla bla bla""" +'bla bla bla\n\nbla bla\nbla bla bla' +>>> +``` + +So we can write documented functions like this: + +```python +>>> def thing(): +... """This thing does stuff. +... +... It always returns None. +... """ +... +>>> help(thing) +Help on function thing in module __main__: + +thing() + This thing does stuff. + + It always returns None. + +>>> +``` + +It's recommended to always use `"""strings like this"""` for docstrings, +even if the docstring is only one line long. This way it's easy to add +more stuff to it later. + +## Documenting other stuff + +Docstrings aren't actually limited to functions. You can use them for +documenting [classes](classes.md) and their methods too. For example, +let's make a file like this and save it to `test.py`: + +```python +"""A test module. + +It contains a class and a function. +""" + + +class Thing: + """This is a test class.""" + + def thingy(self): + """This is a test method.""" + print("hello") + + +def do_hello(): + """This is a test function.""" + thing = Thing() + thing.thingy() +``` + +Then we can import it and call help on it: + +[comment]: # (github screws up syntax highlighting here) + +``` +>>> import test +>>> help(test) +Help on module testie: + +NAME + testie - A test module. + +DESCRIPTION + It contains a class and a function. + +CLASSES + builtins.object + Thing + + class Thing(builtins.object) + | This is a test class. + | + | Methods defined here: + | + | thingy(self) + | This is a test method. + | + | ---------------------------------------------------------------------- + | Data descriptors defined here: + | + | __dict__ + | dictionary for instance variables (if defined) + | + | __weakref__ + | list of weak references to the object (if defined) + +FUNCTIONS + do_hello() + This is a test function. + +FILE + /home/akuli/testie.py +``` + +That's pretty cool. We just added docstrings to our code and Python made +this thing out of it. + +You might be wondering what `__weakref__` is. You don't need to care +about it, and I think it would be better if `help()` would hide it. + +## Popular Docstring Formats + +There are different styles for writing docstrings. If you are contributing to +another Python project, make sure to use the same style as rest of that project +is using. + +If you are starting a new project, then you can use whichever style you +want, but don't "reinvent the wheel"; use an existing style instead instead of +making up your own. Here are some examples of popular docstring styles to choose +from: + +### Sphinx Style + +[Sphinx](https://www.sphinx-doc.org/en/master/) is the Python documentation tool +that [the official Python documentation](https://docs.python.org/3/) uses. +By default, sphinx expects you to write docstrings like this: + +```python +class Vehicles: + """ + The Vehicles object contains lots of vehicles. + :param arg: The arg is used for ... + :type arg: str + :ivar arg: This is where we store arg + :vartype arg: str + """ + + def __init__(self, arg): + self.arg = arg + + def cars(self, distance, destination): + """We can't travel a certain distance in vehicles without fuels, so here's the fuels + + :param distance: The amount of distance traveled + :type amount: int + :param bool destinationReached: Should the fuels be refilled to cover required distance? + :raises: :class:`RuntimeError`: Out of fuel + + :returns: A Car mileage + :rtype: Cars + """ + ... +``` + +### Google Style + +Google Style is meant to be easier to read and use without a tool like sphinx. +Sphinx can be configured to use that with +[sphinx.ext.napoleon](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html). + +```python +class Vehicles: + """ + The Vehicles object contains lots of vehicles. + + Args: + arg (str): The arg is used for... + + Attributes: + arg (str): This is where we store arg. + """ + + def __init__(self, arg): + self.arg = arg + + def cars(self, distance, destination): + """We can't travel distance in vehicles without fuels, so here is the fuels + + Args: + distance (int): The amount of distance traveled + destination (bool): Should the fuels refilled to cover the distance? + + Raises: + RuntimeError: Out of fuel + + Returns: + cars: A car mileage + """ + ... + +``` + +### Numpy Style + +[Numpy](https://numpy.org/) is a large and popular Python library, +and numpy developers have their own docstring style. + +```python +class Vehicles: + """ + The Vehicles object contains lots of vehicles. + + Parameters + ---------- + arg : str + The arg is used for ... + *args + The variable arguments are used for ... + **kwargs + The keyword arguments are used for ... + + Attributes + ---------- + arg : str + This is where we store arg. + """ + + def __init__(self, arg): + self.arg = arg + + def cars(self, distance, destination): + """We can't travel distance in vehicles without fuels, so here is the fuels + + Parameters + ---------- + distance : int + The amount of distance traveled + destination : bool + Should the fuels refilled to cover the distance? + + Raises + ------ + RuntimeError + Out of fuel + + Returns + ------- + cars + A car mileage + """ + pass +``` + +## When should we use docstrings? + +I recommend using docstrings when writing code that other people will import. +The `help()` function is awesome, so it's good to make sure it's actually helpful. + +If your code is not meant to be imported, docstrings are usually a good +idea anyway. Other people reading your code will understand what it's +doing without having to read through all of the code. + +## Summary + +- `help()` is awesome. +- A `"""triple-quoted string"""` string in the beginning of a function, + class or file is a docstring. It shows up in `help()`. +- Docstrings are not comments. +- Usually it's a good idea to add docstrings everywhere. + +*** + +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a +star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). + +You may use this tutorial freely at your own risk. See +[LICENSE](../LICENSE). + +[Previous](classes.md) | [Next](../advanced/datatypes.md) | +[List of contents](../README.md#basics) diff --git a/basics/editor-setup.md b/basics/editor-setup.md index cf09f2f..be13ea5 100644 --- a/basics/editor-setup.md +++ b/basics/editor-setup.md @@ -1,7 +1,7 @@ # Setting up an editor for programming An editor is a program that lets us write longer programs than we can -write on the `>>>` prompt. Then we can save the programs to files and +write on the `>>>` prompt. With an editor we can save the programs to files and run them as many times as we want without writing them again. When programmers say "editor" they don't mean programs like Microsoft @@ -9,235 +9,88 @@ Word or LibreOffice/OpenOffice Writer. These programs are for writing text documents, not for programming. **Programming editors don't support things like bigger font sizes for titles or underlining bits of text**, but instead they have features that are actually useful for programming, -like automatically displaying different things with different colors. +like automatically displaying different things with different colors, +but also highlighting mistakes in the code, and coloring syntax. If you are on Windows or Mac OSX you have probably noticed that your -Python came with an editor called IDLE. We are not going to use it -because it's lacking some important features, and most experienced -programmers (including me) don't use it or recommend it to anyone. - -In this chapter we'll download, install, set up and learn to use a -better editor. The setup part will take some time, but it's worth it and -correct settings will help you avoid many problems later. - -## Which editor should I use? - -These instructions are written for an editor called Geany. Its default -settings are actually not very good for writing Python code, but you -will learn to change the settings so you won't have trouble with -switching to another editor later if you need to. - -Different programmers have different favorite editors that they use, and -the choice of editor is a very personal thing. You can use almost any -editor you want if you make sure that you change its settings the same -way we change Geany's settings in this tutorial. - -## Bad editors +Python came with an editor called IDLE. You can use IDLE, but we recommend exploring other options first. -**[Skip this part](#installing-your-new-editor) if you are going to use -Geany.** - -But some editors are not good enough for actually making something with -them. These editors cannot be set up using these instructions because -they don't have some of the features we need, so **don't use these -editors for writing Python**: - -- Gedit -- IDLE -- Nano -- Notepad -- Pluma -- Wingware - -On the other hand, some editors have too many features for getting -started with programming. They are not bad, but they are not meant for -beginners. So **I don't recommend using these editors yet**: -- Emacs -- NetBeans -- PyCharm -- Spyder -- Vim - -These lists don't contain all bad editors, but these are editors that -people often try to use. If you know a bad editor and you think I should -mention it here, please [let me know](../contact-me.md). - -## Installing your new editor - -Installing Geany is easy. If you are using Windows or Mac OSX, go to -[the official Geany download -page](http://www.geany.org/Download/Releases) and click the correct -download link. If you are using Linux, just install Geany from the -package manager like any other program. For example, on Debian-based -distributions (e.g. Ubuntu) you can type `sudo apt install geany` on a -terminal. - -When you have Geany installed, you can launch it like any other program. -By default, it has a big sidebar and bottom area, but we don't need them -so you can start by dragging them away. Geany should look roughly like -this after that: +## Which editor? -![Geany without the sidebar and bottom area.](../images/geany.png) - -## Dark background +The choice of an editor is a very personal thing. There are many +editors, and most programmers have a favorite editor that they use for +everything and recommend to everyone. -Geany has a white background by default, but we'll tell Geany to use a -black background instead. Your eyes will thank you for doing this when -you have been programming for a few hours. +The editors can be broadly divided into three categories: -1. Click *Edit* at top and click *Preferences*. -2. Click *Editor* at left. -3. Click *Display* at top right. -4. Check *Invert syntax highlighting colors*. -5. Click *OK*. - -If you don't like the colors, you can install more [color -schemes](https://github.com/geany/geany-themes/) and then go to *View* -at top and click *Change Color Scheme*. - -## Opening and saving files - -Now it's time to create our first file. Geany creates a new file when -you open it for the first time, but it doesn't know that it's going to -be a Python file. We need to tell that to Geany by saving the file -first. You can use the *File* menu at top or the buttons below the -menus, just like in most other programs. - -Save the empty file on your desktop as `test.py`. The `.py` at the end -of the name is important, it tells Geany and other programs that it's a -Python file. - -## Automatic tab expanding - -Open a Python file in your editor. Then press the tab key, and the -cursor should move right. Now press the left arrow key. See how the -cursor jumps over the empty space? The empty space is actually a tab -character, but Python's style guide recommends using four spaces instead -of tab characters so we'll need to change Geany's settings. +#### The Basic Text Editors +These editors usually come with the operating system. They do not have features like +running code, auto-completion, etc. that make programming easier. They are usually used for relatively simple +text editing. Most programmers do not use these editors for programming. -1. Click *Edit* at top, then click Preferences. -2. Click *Editor* at left. -3. Click *Indentation* at top. -4. Select *Spaces* instead of *Tabs*. -5. Click *OK*. -6. Click *Project* at top and click *Apply Default Indentation*. - -Delete the whitespace, then press tab and press the arrow left again. -Now the cursor should move one space at a time. - -**TODO:** animated gifs that show what's going on. - -## Stripping spaces when pressing Enter - -Press Tab and then press Enter, and Geany should add spaces to the new -line automatically for you. If you are not using Geany, make sure that -your editor does this too. - -Now press the arrow up and then move left and right. You'll notice that -the previous line has spaces left over on it. Leaving useless spaces to -ends of lines like this is usually considered bad style, but it's easy -to tell Geany to get rid of them: - -1. Click *Edit* at top and click *Preferences* -2. Click *Editor* at left. -3. Click *Features* at top. -4. Check *Newline strips trailing spaces*. -5. Click *OK*. - -Press tab and Enter again. If you go back to the previous line now there -is no whitespace on it. - -**TODO:** again, animated gifs - -## Maximum line length - -You have probably noticed that Geany displays a thin, green-ish line at -right from where you type. The idea is that you should avoid writing -anything longer than where this line is. Your programs will work if they -contain lines that go past this marker, but it's not recommended because -shorter lines are nicer to work with in general. People with small -screens can work with your code, and people with large screens can have -multiple files opened at the same time. - -By default, there's room for 72 characters before the marker. Staying in -72 characters is not always easy, and that's why Python's style guide -recommends 79 instead of 72. Let's move Geany's marker to 79 characters: - -1. Click *Edit* at top and click *Preferences* -2. Click *Editor* at left. -3. Click *Display* at top. -4. Change 72 to 79. -5. Click *OK*. - -## Running things from the editor +A few popular ones in this category are: +- Notepad (Windows) +- Gedit (Linux) +- Notepad ++ (Windows) +- Nano (Linux/Mac OS) -This setting up stuff is boring! Let's write some code. - -We'll use this program to check if our editor is set up correctly. -Create a new file and type this into it: +#### Smart Text Editors +The text editors in this category have features like auto-completion, syntax highlighting, +running and debugging code, highlighting errors, etc. They are relatively easy to learn and have the necessary features +to start your programming journey. -```python -import sys -print(sys.version) -``` - -You don't need to understand this program yet, you'll learn what it does -later in this tutorial. You can type this program on the `>>>` prompt to -see what it does if you want to. - -Now save the program somewhere as `test.py`. It doesn't matter what the -name of the file is, but it needs to end with `.py`. This way Geany -knows that it's a Python file. - -Next we'll need to change Geany's settings so that it runs the program -correctly. You need to have a Python file opened to change these -settings. - -1. Click *Build* at top, then click *Set Build Commands*. -2. The *Execute* command is probably `python "%f"`. Change it to - `py "%f"` if you are using Windows and `python3 "%f"` if you are - using Linux or Mac OSX. -3. Click *OK*. - -Note that the first part of our *Execute* is the same thing [we type on -a PowerShell or terminal](installing-python.md#running-python). Actually -the whole command means "run this file using my Python". +A few popular ones in this category are: +- Visual Studio Code / VS Code (Windows/Linux/Mac OS) +- IDLE (Usually comes with Python) (Windows/Linux/Mac OS) +- Thonny (Windows/Linux/Mac OS) +- [Porcupine](https://github.com/Akuli/porcupine) (created by the author of this tutorial) (Windows/Linux/Mac OS) +- Geany (Windows/Linux/Mac OS) -Now press the F5 key at the top of your keyboard to run the file we -wrote. It should open up in a terminal or command prompt and print the -same Python version as the `>>>` prompt prints when we open it. If the -version starts with 2 it's too old, and you can [ask -me](../contact-me.md) or some other experienced programmer for help. - -## Important notes - -Now your editor is ready to go. There are a couple important things that -you need to keep in mind when reading the rest of this tutorial: +**We recommend that you look into a few of these editors and install your favorite one.** -- When a code example starts with `>>>`, type it to the `>>>` prompt. If - it doesn't, create or open a `.py` file with your editor, type the - code into it and run the file. -- When we type some code to the `>>>` prompt it [echoes back the - result](getting-started.md) [when it's not None](variables.md#none), - but code in a file does nothing to the result. For example, typing - `1 + 2` to the `>>>` prompt makes it echo `3`, but we need to do - `print(1 + 2)` if we want to do the same thing in a file. -- The editor doesn't replace the `>>>` prompt. The prompt is good for - trying things out quickly, and the editor is better when we want to - write longer programs. +#### IDEs and advanced editors +This category of text editors are usually professional grade pieces of software. They are mostly proprietary and paid. They have a steep +learning curve because of how many features they have. +These types of editors are generally not preferred +in the beginning stage. They are meant to be used for writing complex and large pieces of software. -## You're done! - -This probably felt like a lot of work, but don't worry, there will be no -more things like this in the future. Now we have installed and set up -all the tools we need and we can continue learning Python. +A few popular ones in this category are: +- Visual Studio (Not be confused with *Visual Studio Code*) (Windows) +- Pycharm (Windows/Linux/Mac OS) +- Vim (Windows/Linux/Mac OS) +- Emacs (Windows/Linux/Mac OS) + +As already mentioned, there are no "right" or "wrong" editors. The preference of an editor +is a personal choice and we recommend trying different editors. +The lists on this page don't contain all editors, but just a few of the most popular ones. + +## Editor or `>>>` prompt? + +So far we have used the `>>>` prompt for everything. But now we also +have an editor that lets us write longer programs. So why not just +always use the editor? + +The `>>>` prompt is meant to be used for experimenting with things. For +example, if you want to know what `"hello" + 123` does, just open the +prompt and run it. + +If you want to write something once and then run it many times, write +the code to a file. For example, if you want to make a program that asks +the user to enter a word and then echoes it back, write a program that +does that in a file and run it as many times as you want to. + +Note that if you write something like `'hello'` to the `>>>` prompt it +echoes it back, but if you make a file that contains nothing but a +`'hello'` it won't do anything when you run it. You need to use +`print('hello')` instead when your code is in a file. *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/exceptions.md b/basics/exceptions.md index 946d4f1..785fb18 100644 --- a/basics/exceptions.md +++ b/basics/exceptions.md @@ -249,9 +249,9 @@ text = input("Enter a number: ") try: number = int(text) except ValueError: - print("'%s' is not a number." % text, file=sys.stderr) + print(f"'{text}' is not a number.", file=sys.stderr) sys.exit(1) -print("Your number doubled is %d." % (number * 2)) +print(f"Your number doubled is {(number * 2)}.") ``` ## Raising exceptions @@ -319,7 +319,7 @@ it's usually better to use `sys.stderr` and `sys.exit`. ## Exception hierarchy Exceptions are organized like this. I made this tree with [this -program](https://github.com/Akuli/classtree/) on Python 3.4. You may +program](https://github.com/Akuli/classtree/) on Python 3.7. You may have more or less exceptions than I have if your Python is newer or older than mine, but they should be mostly similar. @@ -333,6 +333,7 @@ older than mine, but they should be mostly similar. ├── BufferError ├── EOFError ├── ImportError + │ └── ModuleNotFoundError ├── LookupError │ ├── IndexError │ └── KeyError @@ -357,7 +358,9 @@ older than mine, but they should be mostly similar. │ └── TimeoutError ├── ReferenceError ├── RuntimeError - │ └── NotImplementedError + │ ├── NotImplementedError + │ └── RecursionError + ├── StopAsyncIteration ├── StopIteration ├── SyntaxError │ └── IndentationError @@ -449,7 +452,7 @@ def greet(): try: greet() except OSError: - print("Cannot read '%s'!" % filename, file=sys.stderr) + print(f"Cannot read '{filename}'!", file=sys.stderr) if askyesno("Would you like to create a default greeting file?"): with open(filename, 'w') as f: print(default_greeting, file=f) @@ -458,9 +461,10 @@ except OSError: *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/files.md b/basics/files.md index 82705d0..5a8f7dd 100644 --- a/basics/files.md +++ b/basics/files.md @@ -365,9 +365,10 @@ else: *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/getting-started.md b/basics/getting-started.md index 57c0e97..9f00625 100644 --- a/basics/getting-started.md +++ b/basics/getting-started.md @@ -60,10 +60,86 @@ We didn't get an error... but `(3, 14)` is not at all what we expected! So from now on, let's use a dot with decimal numbers, because `3.14` worked just fine. Later we'll learn what `(3, 14)` is. +## Comments + +**Comments are text that don't do anything when they're run.** +They can be created by typing a `#` and then some text after it, +and they are useful when our code would be hard to understand without them. + +```python +>>> 1 + 2 # can you guess what the result is? +3 +>>> +``` + +Again, I put a space after the `#` and multiple spaces before it just to +make things easier to read. + +If we write a comment on a line with no code on it, the prompt changes +from `>>>` to `...`. To be honest, I have no idea why it does that and I +think it would be better if it would just stay as `>>>`. The prompt goes +back to `>>>` when we press Enter again. + +```python +>>> # hello there +... +>>> +``` + +## Strings + +Strings are small pieces of text that we can use in our programs. We can +create strings by simply writing some text in quotes. + +```python +>>> 'hello' +'hello' +>>> 'this is a test' +'this is a test' +>>> +``` + +Strings can also be written with "double quotes" instead of 'single +quotes'. This is useful when we need to put quotes inside the string. + +```python +>>> "hello there" +'hello there' +>>> "it's sunny" +"it's sunny" +>>> +``` + +It's also possible to add single quotes and double quotes into the same +string, but most of the time we don't need to do that so I'm not going +to talk about it now. + +It doesn't matter which quotes you use when the string doesn't need to +contain any quotes. If you think that one of the quote types looks nicer +than the other or you find it faster to type, go ahead and use that. + +Strings can be joined together easily with `+` or repeated with `*`: + +```python +>>> "hello" + "world" +'helloworld' +>>> "hello" * 3 +'hellohellohello' +>>> +``` + +Note that a `#` inside a string doesn't create a comment. + +```python +>>> "strings can contain # characters" +'strings can contain # characters' +>>> +``` + ## Using Python as a calculator ```diff --WARNING: This part contains boring math. Be careful! +---------- WARNING: This part contains boring math. Proceed with caution. ---------- ``` Let's type some math stuff into Python and see what it does. @@ -107,52 +183,39 @@ and `)` also work the same way. >>> ``` -Python also supports many other kinds of calculations, but most of the -time you don't need them. Actually you don't need even these -calculations most of the time, but these calculations are probably -enough when you need to calculate something. - -## Comments - -We can also type a `#` and then whatever we want after that. These bits -of text are known as **comments**, and we'll find uses for them later. +You can also leave out spaces to show what's calculated first. Python +ignores it, but our code will be easier to read for people. ```python ->>> 1 + 2 # can you guess what the result is? -3 +>>> 1 + 2*3 # now it looks like 2*3 is calculated first +7 >>> ``` -Again, I put a space after the `#` and multiple spaces before it just to -make things easier to read. - -If we write comment on a line with no code on it, the prompt changes -from `>>>` to `...`. To be honest, I have no idea why it does that and I -think it would be better if it would just stay as `>>>`. The prompt goes -back to `>>>` when we press Enter again. - -```python ->>> # hello there -... ->>> -``` +Python also supports many other kinds of calculations, but most of the +time you don't need them. Actually you don't need even these +calculations most of the time, but these calculations are probably +enough when you need to calculate something. ## Summary -[comment]: # (the first line in this summary is exactly same as in -what-is-programming.md, and it's supposed to be like this) +[comment]: # (the first line in this summary is exactly same as in) +[comment]: # (what-is-programming.md, and it's supposed to be like this) - Error messages are our friends. - We can enter any Python commands to the interactive `>>>` prompt, and it will echo back the result. - `+`, `-`, `*` and `/` work in Python just like in math. -- Pieces of text starting with a `#` are comments. +- Pieces of text starting with a `#` are comments and pieces of text in + quotes are strings. +- You can use single quotes and double quotes however you want. *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/handy-stuff-strings.md b/basics/handy-stuff-strings.md index be89c55..98d5cc4 100644 --- a/basics/handy-stuff-strings.md +++ b/basics/handy-stuff-strings.md @@ -241,82 +241,17 @@ Instead it's recommended to use string formatting. It means putting other things in the middle of a string. Python has multiple ways to format strings. One is not necessarily -better than others, they are just different. Here's a few ways to solve -our problem: +better than others; they each have their own advantages and disadvantages. +In this tutorial, we will focus on f-strings, which is the most common and usually the easiest way. -- `.format()`-formatting, also known as new-style formatting. This - formatting style has a lot of features, but it's a little bit more - typing than `%s`-formatting. - - ```python - >>> "Hello {}.".format(name) - 'Hello Akuli.' - >>> "My name is {} and I'm on the {} channel on {}.".format(name, channel, network) - "My name is Akuli and I'm on the ##learnpython channel on freenode." - >>> - ``` - -- `%s`-formatting, also known as old-style formatting. This has less - features than `.format()`-formatting, but `'Hello %s.' % name` is - shorter and faster to type than `'Hello {}.'.format(name)`. I like - to use `%s` formatting for simple things and `.format` when I need - more powerful features. - - ```python - >>> "Hello %s." % name - 'Hello Akuli.' - >>> "My name is %s and I'm on the %s channel on %s." % (name, channel, network) - "My name is Akuli and I'm on the ##learnpython channel on freenode." - >>> - ``` - - In the second example we had `(name, channel, network)` on the right - side of the `%` sign. It was a tuple, and we'll talk more about them - [later](lists-and-tuples.md#tuples). - - If we have a variable that may be a tuple we need to wrap it in another - tuple when formatting: - - ```python - >>> thestuff = (1, 2, 3) - >>> "we have %s" % thestuff - Traceback (most recent call last): - File "", line 1, in - TypeError: not all arguments converted during string formatting - >>> "we have %s and %s" % ("hello", thestuff) - 'we have hello and (1, 2, 3)' - >>> "we have %s" % (thestuff,) - 'we have (1, 2, 3)' - >>> - ``` - - Here `(thestuff,)` was a tuple that contained nothing but `thestuff`. - -- f-strings are even less typing, but new in Python 3.6. **Use this only if - you know that nobody will need to run your code on Python versions older - than 3.6.** Here the f is short for "format", and the content of the - string is same as it would be with `.format()` but we can use variables - directly. - - ```python - >>> f"My name is {name} and I'm on the {channel} channel on {network}." - "My name is Akuli and I'm on the ##learnpython channel on freenode." - >>> - ``` - -All of these formatting styles have many other features also: +`f` in f-strings stands for "format", f-strings are string literals that have an `f` at the beginning and curly braces containing expressions that will be replaced with their values at runtime. To create f-strings, you have to add an `f` or an `F` before the opening quotes of a string. ```python ->>> 'Three zeros and number one: {:04d}'.format(1) -'Three zeros and number one: 0001' ->>> 'Three zeros and number one: %04d' % 1 -'Three zeros and number one: 0001' +>>> f"My name is {name} and I'm on the {channel} channel on {network}." +"My name is Akuli and I'm on the ##learnpython channel on freenode." >>> ``` -If you need to know more about formatting I recommend reading -[this](https://pyformat.info/). - ## Other things We can use `in` and `not in` to check if a string contains another @@ -392,7 +327,7 @@ ValueError: could not convert string to float: 'hello' - Python has many string methods. Use [the documentation](https://docs.python.org/3/library/stdtypes.html#string-methods) - or `help(str)` when you don't rememeber something about them. + or `help(str)` when you don't remember something about them. - String formatting means adding other things to the middle of a string. There are multiple ways to do this in Python. You should know how to use at least one of these ways. @@ -424,14 +359,17 @@ ValueError: could not convert string to float: 'hello' print(message, "!!!") print(message, "!!!") ``` +3. Make a program to ask a string from the user and check if it is a palindrome.
+ (Hint: A string is a palindrome if it is the same when reversed. Google how to reverse a string.) The answers are [here](answers.md#handy-stuff-strings). *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/if.md b/basics/if.md index f509d1e..71655f2 100644 --- a/basics/if.md +++ b/basics/if.md @@ -245,7 +245,20 @@ else: Now the `else` belongs to the `if 1 == 2` part and **it has nothing to do with the `if 1 == 1` part**. On the other hand, the elif version **grouped the multiple ifs together** and the `else` belonged to all of -them. +them. Adding a blank line makes this obvious: + +```python +if 1 == 1: + print("hello") + +if 1 == 2: + print("this is weird") +else: + print("world") +``` + +In general, adding blank lines to appropriate places is a good idea. If +you are asked to "fix code", feel free to add missing blank lines. ## Summary @@ -276,6 +289,7 @@ them. something = input("Enter something: ") if something = 'hello': print("Hello for you too!") + elif something = 'hi' print('Hi there!') else: @@ -298,13 +312,15 @@ them. the user entered the correct password, a wrong password, or nothing at all by pressing Enter without typing anything. + The answers are [here](answers.md#if-else-and-elif). *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/installing-python.md b/basics/installing-python.md index cc693bf..249cc4b 100644 --- a/basics/installing-python.md +++ b/basics/installing-python.md @@ -40,8 +40,9 @@ me](../contact-me.md). ### Linux You already have Python 3, **there's no need to install anything**. You -may also have Python 2, but don't try to remove it. Some of your -programs are probably written in Python 2, so removing Python 2 would +may also have Python 2, but don't try to remove it. +Some of the programs that came with your operating system +are probably written in Python 2, so removing Python 2 would break them. ## Running Python @@ -77,9 +78,10 @@ Now you should have Python installed, and you should be able run it. *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/larger-program.md b/basics/larger-program.md index 4b88be1..4cd0cda 100644 --- a/basics/larger-program.md +++ b/basics/larger-program.md @@ -13,6 +13,8 @@ text displaying function = print text asking function = input ``` +**Save this example file to questions.txt**, we'll need it later. + This might seem useless to you right now, but a program like this can actually be really useful for learning different kinds of things. I originally wrote a program like this to study words of a foreign @@ -74,26 +76,34 @@ and then testing and fixing. Here are my versions of them: def ask_questions(answers): correct = [] wrong = [] + for question, answer in answers.items(): if input(question + ' = ').strip() == answer: print("Correct!") correct.append(question) else: - print("Wrong! The correct answer is %s." % answer) + print(f"Wrong! The correct answer is {answer}.") wrong.append(question) + return (correct, wrong) + def stats(correct, wrong, answers): print("\n**** STATS ****\n") print("You answered", len(correct), "questions correctly and", len(wrong), "questions wrong.") + if wrong: print("These would have been the correct answers:") for question in wrong: print(' ', question, '=', answers[question]) ``` -Let's try them out. +Note that these functions have some empty lines in them and there are +two empty lines between the functions. This makes the code a bit longer, +but it's a lot easier to read this way. + +Let's try out the functions. ```python >>> answers = read_questions('questions.txt') @@ -165,27 +175,33 @@ def read_questions(filename): answers[question.strip()] = answer.strip() return answers + def ask_questions(answers): correct = [] wrong = [] + for question, answer in answers.items(): - if input('%s = ' % question).strip() == answer: + if input(f'{question} = ').strip() == answer: print("Correct!") correct.append(question) else: - print("Wrong! The correct answer is %s." % answer) + print(f"Wrong! The correct answer is {answer}.") wrong.append(question) + return (correct, wrong) + def stats(correct, wrong, answers): print("\n**** STATS ****\n") print("You answered", len(correct), "questions correctly and", len(wrong), "questions wrong.") + if wrong: print("These would have been the correct answers:") for question in wrong: print(' ', question, '=', answers[question]) + def main(): filename = input("Name of the question file: ") answers = read_questions(filename) @@ -212,9 +228,10 @@ something else when it's imported. *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/lists-and-tuples.md b/basics/lists-and-tuples.md index 6726a46..379d019 100644 --- a/basics/lists-and-tuples.md +++ b/basics/lists-and-tuples.md @@ -141,6 +141,29 @@ We'll talk more about loops [in the next chapter](loops.md). >>> ``` +Another useful thing about lists is **list comprehension**. +It's a handy way to construct a list in single line. It often makes code cleaner, shorter and easier to read. + +```python +>>> numbers = [1,2,3,4,5] +>>> numbers_squared = [number ** 2 for number in numbers] +>>> numbers_squared +[1, 4, 9, 16, 25] +>>> +``` + +Without a list comprehension, doing the same thing looks like this: + +```python +>>> numbers = [1,2,3,4,5] +>>> numbers_squared = [] +>>> for number in numbers: +... numbers_squared.append(number**2) +>>> numbers_squared +[1, 4, 9, 16, 25] +>>> +``` + We can also use slicing and indexing to change the content: ```python @@ -230,10 +253,6 @@ like this: ![Different lists.](../images/differentlist.png) -If you're using Python 3.2 or older you need to do `a[:]` instead -of `a.copy()`. `a[:]` is a slice of the whole list, just like -`a[0:]`. - ## Tuples Tuples are a lot like lists, but they're immutable so they @@ -324,7 +343,16 @@ else: ## Exercises -1. Fix this program. +1. Fix this program: + + ```python + namelist = ('wub_wub', 'RubyPinch', 'go|dfish', 'Nitori') + namelist.append('pb122') + if 'pb122' in namelist: + print("Now I know pb122!") + ``` + +2. Fix this program. ```python print("Hello!") @@ -332,7 +360,7 @@ else: print("Your name is " + name + ".") ``` -2. Fix this program. +3. Fix this program. ```python namelist = ['wub_wub', 'RubyPinch', 'go|dfish', 'Nitori'] @@ -347,9 +375,10 @@ The answers are [here](answers.md#lists-and-tuples). *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/loops.md b/basics/loops.md index 9a72b90..7f693e1 100644 --- a/basics/loops.md +++ b/basics/loops.md @@ -300,10 +300,6 @@ Or if we just want to clear a list, we can use the `clear` >>> ``` -If you're using Python 3.2 or older you need to use `stuff[:]` instead -of `stuff.copy()` and `stuff[:] = []` instead of `stuff.clear()`. -`stuff[:]` is a slice of the whole list, just like `stuff[0:]`. - ## Summary - A loop means repeating something multiple times. @@ -381,7 +377,13 @@ while True: option = input("Choose an option: ") # Things like option == 0 don't work because option is a string - # and it needs to be compared with a string. + # and it needs to be compared with a string: + # >>> 0 == 0 + # True + # >>> '0' == '0' + # True + # >>> 0 == '0' + # False if option == '0': print("Bye!") break @@ -414,7 +416,7 @@ while True: print("I don't know anybody yet.") else: for name in namelist: - print("I know %s!" % name) + print(f"I know {name}!") else: print("I don't understand :(") @@ -424,7 +426,7 @@ while True: ## Exercises -1. This code is supposed to print each number between 1 and 5. Fix it. +1. This code is supposed to print the numbers 1,2,3,4,5. Fix it. ```python things = str([1, 2, 3, 4, 5]) @@ -468,18 +470,37 @@ while True: number = int(number) print(numbers) ``` - +5. Make a program that prints a pyramid like shown below. Ask the user to type the number of rows needed. + ``` + OUTPUT for 5 rows + 1 + 1 2 + 1 2 3 + 1 2 3 4 + 1 2 3 4 5 + ``` + +6. Make a program to get a pyramid like shown below where user can type the number of rows needed. + ``` + OUTPUT for 5 rows + 1 2 3 4 5 + 2 3 4 5 + 3 4 5 + 4 5 + 5 + ``` The answers are [here](answers.md#loops). *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See [LICENSE](../LICENSE). -[Previous](lists-and-tuples.md) | [Next](trey-hunner-zip-and-enumerate.md) | +[Previous](lists-and-tuples.md) | [Next](zip-and-enumerate.md) | [List of contents](../README.md#basics) diff --git a/basics/modules.md b/basics/modules.md index b9e3e00..59159cd 100644 --- a/basics/modules.md +++ b/basics/modules.md @@ -28,17 +28,17 @@ gave us? ```python >>> random - + >>> ``` So it's a module, and it comes from a path... but what does all that mean? -Now open the folder that contains your `random.py` is. On my -system it's `/usr/lib/python3.4`, but yours will probably be +Now open the folder that contains your `random.py`. On my +system it's `/usr/lib/python3.7`, but yours will probably be different. To open a folder in your file manager you can press -Windows-R on Windows or Alt+F2 on most GNU/Linux distributions, +Windows-R on Windows or Alt+F2 on most Linux distributions, and just type your path there. I don't have an up-to-date copy of OSX so unfortunately I have no idea what you need to do on OSX. @@ -53,6 +53,11 @@ All of these `.py` files can be imported like we just imported so we can use its randint variable with `random.randint` after importing it. +You're probably wondering how a computer can generate random numbers. +The random module does different things on different operating systems, +but on most systems it reads random noise that several programs on the +computer produce and creates random numbers based on that. + ## Where do modules come from? Create a `random.py` file with the following content: @@ -97,7 +102,7 @@ AttributeError: 'module' object has no attribute 'randint' >>> ``` -So first of all, what is that random variable? +So first of all, what is that `random` variable? ```python >>> random @@ -133,11 +138,11 @@ places that modules are searched from: >>> sys.path ['', - '/usr/lib/python3.4', - '/usr/lib/python3.4/plat-i386-linux-gnu', - '/usr/lib/python3.4/lib-dynload', - '/home/akuli/.local/lib/python3.4/site-packages', - '/usr/local/lib/python3.4/dist-packages', + '/usr/lib/python37.zip', + '/usr/lib/python3.7', + '/usr/lib/python3.7/lib-dynload', + '/home/akuli/.local/lib/python3.7/site-packages', + '/usr/local/lib/python3.7/dist-packages', '/usr/lib/python3/dist-packages'] >>> ``` @@ -229,14 +234,14 @@ hello >>> >>> # information about Python's version, behaves like a tuple >>> sys.version_info -sys.version_info(major=3, minor=4, micro=2, releaselevel='final', serial=0) ->>> sys.version_info[:3] # this is Python 3.4.2 -(3, 4, 2) +sys.version_info(major=3, minor=7, micro=3, releaselevel='final', serial=0) +>>> sys.version_info[:3] # this is Python 3.7.3 +(3, 7, 3) >>> >>> sys.exit() # exit out of Python ``` -**TODO:** why stderr instead of stdout. +**TODO:** why stderr instead of stdout, when to use `sys.stdin.readline()` instead of `input()` `sys.exit()` does the same thing as `sys.exit(0)`. The zero means that the program succeeded, and everything's fine. If our program has an @@ -285,7 +290,7 @@ The official documentation for the time module is >>> time.time() # return time in seconds since beginning of the year 1970 1474896325.2394648 >>> time.strftime('%d.%m.%Y %H:%M:%S') # format current time nicely -'26.09.2016 16:33:58' +'07.04.2017 19:08:33' >>> ``` @@ -364,8 +369,8 @@ for thing in things: ``` Measure how long it takes for the user to answer a question. -The `%.2f` rounds to 2 decimals, and you can find more formatting -tricks [here](https://pyformat.info/). +The `{:.2f}` rounds to 2 decimals, and you can find more formatting +tricks [here](https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals). ```python import time @@ -376,7 +381,7 @@ end = time.time() difference = end - start if answer == '3': - print("Correct! That took %.2f seconds." % difference) + print(f"Correct! That took {difference:.2f} seconds.") else: print("That's not correct...") ``` @@ -405,7 +410,7 @@ Check what a path points to. import os import sys -print("You are currently in %s." % os.getcwd()) +print(f"You are currently in {os.getcwd()}.") while True: path = input("A path, or nothing at all to quit: ") @@ -456,17 +461,14 @@ then typing in what you want to search for. - [webbrowser](https://pymotw.com/3/webbrowser/): open a web browser from Python -I also use these modules, but they don't come with Python so you'll -need to install them yourself if you want to use them: - -- [appdirs](https://github.com/activestate/appdirs): - an easy way to find out where to put setting files -- [requests](http://docs.python-requests.org/en/master/user/quickstart/): - an awesome networking library +There are also lots of awesome modules that don't come with Python. +You can search for those on the [Python package index](https://pypi.org/), +or PyPI for short. It's often better to find a library that does something +difficult than to spend a lot of time trying to do it yourself. I recommend reading [the official documentation about installing -modules](https://docs.python.org/3/installing/). If you're using -GNU/Linux also read the "Installing into the system Python on Linux" +modules](https://docs.python.org/3/installing/) from PyPI. If you're using +Linux, then also read the "Installing into the system Python on Linux" section at the bottom. ## Summary @@ -482,11 +484,14 @@ section at the bottom. - Python comes with many modules, and we can install even more modules if we want to. +**TODO:** exercises + *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/the-way-of-the-program.md b/basics/the-way-of-the-program.md index ebb44bc..7def31a 100644 --- a/basics/the-way-of-the-program.md +++ b/basics/the-way-of-the-program.md @@ -38,9 +38,10 @@ learned everything. *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/trey-hunner-zip-and-enumerate.md b/basics/trey-hunner-zip-and-enumerate.md deleted file mode 100644 index 3a211de..0000000 --- a/basics/trey-hunner-zip-and-enumerate.md +++ /dev/null @@ -1,147 +0,0 @@ -# Trey Hunner: zip and enumerate - -Now we know how [for loops](loops.md#for-loops) work in Python. But -for loops aren't limited to printing each item in a list, they can -do a lot more. - -To be able to understand for loop tricks we need to first know -assigning values to multiple variables at once. It works like this: - -```python ->>> a, b = 1, 2 ->>> a -1 ->>> b -2 ->>> -``` - -We can use `()` and `[]` around these values however we want and -everything will still work the same way. `[]` creates a list, and -`()` creates a tuple. - -```python ->>> [a, b] = (1, 2) ->>> a -1 ->>> b -2 ->>> -``` - -We can also have `[]` or `()` on one side but not on the other -side. - -```python ->>> (a, b) = 1, 2 ->>> a -1 ->>> b -2 ->>> -``` - -Python created a tuple automatically. - -```python ->>> 1, 2 -(1, 2) ->>> -``` - -If we're for looping over a list with pairs of values in it we -could do this: - -```python ->>> items = [('a', 1), ('b', 2), ('c', 3)] ->>> for pair in items: -... a, b = pair -... print(a, b) -... -a 1 -b 2 -c 3 ->>> -``` - -Or we can tell the for loop to unpack it for us. - -```python ->>> for a, b in items: -... print(a, b) -... -a 1 -b 2 -c 3 ->>> -``` - -Now you're ready to read [this awesome looping -tutorial](http://treyhunner.com/2016/04/how-to-loop-with-indexes-in-python/). -Read it now, then come back here and do the exercises. - -## Exercises - -1. Create a program that works like this. Here I entered everything - after the `>` prompt that the program displayed. - - ``` - Enter something, and press Enter without typing anything when you're done. - >hello there - >this is a test - >it seems to work - > - Line 1 is: hello there - Line 2 is: this is a test - Line 3 is: it seems to work - ``` - -2. Create a program that prints all letters from A to Z and a to z - next to each other: - - ``` - A a - B b - C c - ... - X x - Y y - Z z - ``` - - Start your program like this: - - ```python - uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' - lowercase = 'abcdefghijklmnopqrstuvwxyz' - ``` - - **Hint:** how do strings behave with `zip`? Try it out on the - `>>>` prompt and see. - -3. Can you make it print the indexes also? - - ``` - 1 A a - 2 B b - 3 C c - ... - 24 X x - 25 Y y - 26 Z z - ``` - -The answers are [here](answers.md). - -*** - -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a -star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). - -You may use this tutorial freely at your own risk. See -[LICENSE](../LICENSE). - -[Previous](loops.md) | [Next](dicts.md) | -[List of contents](../README.md#basics) diff --git a/basics/using-functions.md b/basics/using-functions.md index 123910a..c8255ff 100644 --- a/basics/using-functions.md +++ b/basics/using-functions.md @@ -228,9 +228,10 @@ should work normally. *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/variables.md b/basics/variables.md index 4b9c25b..f1a8030 100644 --- a/basics/variables.md +++ b/basics/variables.md @@ -86,8 +86,9 @@ Variable names are case-sensitive, like many other things in Python. ``` There are also words that cannot be used as variable names -because they have a special meaning. They are called **keywords**, and -we can run `help('keywords')` to see the full list if we want to. +because they are reserved by Python itself and have a special meaning. +They are called **keywords**, and we can run `help('keywords')` +to see the full list if we want to. We'll learn to use most of them later in this tutorial. Trying to use a keyword as a variable name causes a syntax error. @@ -300,9 +301,10 @@ what you are doing. We'll learn more about it later. *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/what-is-programming.md b/basics/what-is-programming.md index e02a853..269ce3f 100644 --- a/basics/what-is-programming.md +++ b/basics/what-is-programming.md @@ -104,7 +104,7 @@ should you do if you have a problem with the tutorial? 1. Try the example code yourself. 2. Read the code and the explanation for it again. 3. If there's something you haven't seen before in the tutorial and it's - not explained, try to find it from the previous chapters. + not explained, try to find it in the previous chapters. 4. If you can't find what you're looking for or you still have trouble understanding the tutorial or any other problems with the tutorial, please [tell me about it](../contact-me.md). I want to improve this @@ -135,8 +135,10 @@ have learned, and create something with it. ## But reading is boring! -Yes, I know. You can just try the code examples yourself and read the -rest of this tutorial only if you don't understand the code. +This chapter is probably the most boring chapter in the whole tutorial. +Other chapters contain much less text and much more code. You can also +get pretty far by just reading the code, and then reading the text only +if you don't understand the code. ## Summary @@ -155,9 +157,10 @@ rest of this tutorial only if you don't understand the code. *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/what-is-true.md b/basics/what-is-true.md index 495aea6..a344239 100644 --- a/basics/what-is-true.md +++ b/basics/what-is-true.md @@ -213,9 +213,10 @@ if value is None: ... # best *** -If you have trouble with this tutorial please [tell me about -it](../contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/basics/zip-and-enumerate.md b/basics/zip-and-enumerate.md new file mode 100644 index 0000000..f1332d3 --- /dev/null +++ b/basics/zip-and-enumerate.md @@ -0,0 +1,248 @@ +# zip and enumerate + +Now we know how [for loops](loops.md#for-loops) work in Python. But +for loops aren't limited to printing each item in a list, they can +do a lot more. + +To be able to understand for loop tricks we need to first know +assigning values to multiple variables at once. It works like this: + +```python +>>> a, b = 1, 2 +>>> a +1 +>>> b +2 +>>> +``` + +We can use `()` and `[]` around these values however we want and +everything will still work the same way. `[]` creates a list, and +`()` creates a tuple. + +```python +>>> [a, b] = (1, 2) +>>> a +1 +>>> b +2 +>>> +``` + +We can also have `[]` or `()` on one side but not on the other +side. + +```python +>>> (a, b) = 1, 2 +>>> a +1 +>>> b +2 +>>> +``` + +Python created a tuple automatically. + +```python +>>> 1, 2 +(1, 2) +>>> +``` + +If we're for looping over a list with pairs of values in it we +could do this: + +```python +>>> items = [('a', 1), ('b', 2), ('c', 3)] +>>> for pair in items: +... a, b = pair +... print(a, b) +... +a 1 +b 2 +c 3 +>>> +``` + +Or we can tell the for loop to unpack it for us. + +```python +>>> for a, b in items: +... print(a, b) +... +a 1 +b 2 +c 3 +>>> +``` + +This feature is often used with Python's built-in `zip()` and `enumerate()` functions. + + +## zip + +What comes to your mind when you hear the word `zip`? A mechanism extensively used to tie two parts of something, e.g. shirt or jacket. Python's `zip()` functions does pretty much the same, it helps us tie corresponding items together. + +```python +>>> users = ["Tushar", "Aman", "Anurag", "Sohit"] +>>> uids = ["usr122", "usr123", "usr124", "usr125"] +>>> user_details = zip(uids, users) +>>> print(list(user_details)) +[('usr122', 'Tushar'), ('usr123', 'Aman'), ('usr124', 'Anurag'), ('usr125', 'Sohit')] +>>> +``` + +Note that `print(user_details)` doesn't work as expected: + +``` +>>> print(user_details) + +>>> +``` + +This is because `zip()` is an iterator, i.e. lazy: it gives the items as needed, instead of calculating them and storing them into memory all at once like a list. So the zip object cannot show its elements before the elements are used, because it hasn't computed them yet. + +```python +>>> users = ["Tushar", "Aman", "Anurag", "Sohit"] +>>> uids = ["usr122", "usr123", "usr124", "usr125"] +>>> user_details = zip(uids, users) +``` + +If the lists are of different lengths, some items from the end of the longer list will be ignored. +```python +>>> users = ["Tushar", "Aman", "Anurag"] +>>> emails = ["tushar@example.com", "aman@example.com", "anurag@example.com", "sohit@example.com"] +>>> users_contact = zip(users, emails) +>>> print(list(users_contact)) +[('Tushar', 'tushar@example.com'), ('Aman', 'aman@example.com'), ('Anurag', 'anurag@example.com')] +>>> +``` + + +Here the shortest list is `users`, with length 3, so `zip(users, emails)` only takes the first 3 emails. +We do not recommend calling `zip()` with lists of different lengths, because ignoring items is usually not what you intended to do. + +### Using zip in a `for` loop + +It is very common to `for` loop over a `zip()`, and unpack the returned tuples in the `for` loop. +This is why we introduced unpacking in the beginning of this page. +When used this way, there's no need to convert the result of `zip(...)` to a list. + +```python +>>> roll_nums = [20, 25, 28] +>>> students = ["Joe", "Max", "Michel"] +>>> for roll_num, student in zip(roll_nums, students): +... print(f"Roll number of {student} is {roll_num}") +... +Roll number of Joe is 20 +Roll number of Max is 25 +Roll number of Michel is 28 +>>> +``` + +## enumerate + +`enumerate()` is an amazing Built-in function offered by python. When used, gives us the index and the item combined. + +```python +>>> even_nums = [2, 4, 6, 8, 10, 12] +>>> for index, item in enumerate(even_nums): +... print(f"Index of {item} is {index}") +... +Index of 2 is 0 +Index of 4 is 1 +Index of 6 is 2 +Index of 8 is 3 +Index of 10 is 4 +Index of 12 is 5 +>>> +``` + +It is also possible (but more difficult) to do this without `enumerate()`: + +```python +>>> even_nums = [2, 4, 6, 8, 10, 12] +>>> for index in range(0, len(even_nums)): +... print(f"Index of {even_nums[index]} is {index}") +... +Index of 2 is 0 +Index of 4 is 1 +Index of 6 is 2 +Index of 8 is 3 +Index of 10 is 4 +Index of 12 is 5 +>>> +``` + +Here: +* `range(0, len(even_nums))` gives 0,1,2,3,4,5, with the list length 6 excluded. These are the indexes of our list of length 6. +* `even_nums[index]` prints each element of `even_nums`, because `index` comes from the range of all indexes into that list. + +Because this is complicated to think about and easy to get wrong, it is better to use `enumerate()`. + +## Exercises + +1. Create a program that works like this. Here I entered everything + after the `>` prompt that the program displayed. + + ``` + Enter something, and press Enter without typing anything when you're done. + >hello there + >this is a test + >it seems to work + > + Line 1 is: hello there + Line 2 is: this is a test + Line 3 is: it seems to work + ``` + +2. Create a program that prints all letters from A to Z and a to z + next to each other: + + ``` + A a + B b + C c + ... + X x + Y y + Z z + ``` + + Start your program like this: + + ```python + uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + lowercase = 'abcdefghijklmnopqrstuvwxyz' + ``` + + **Hint:** how do strings behave with `zip`? Try it out on the + `>>>` prompt and see. + +3. Can you make it print the indexes also? + + ``` + 1 A a + 2 B b + 3 C c + ... + 24 X x + 25 Y y + 26 Z z + ``` + +The answers are [here](answers.md). + +*** + +If you have trouble with this tutorial, please +[tell me about it](../contact-me.md) and I'll make this tutorial better, +or [ask for help online](../getting-help.md). +If you like this tutorial, please [give it a +star](../README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). + +You may use this tutorial freely at your own risk. See +[LICENSE](../LICENSE). + +[Previous](loops.md) | [Next](dicts.md) | +[List of contents](../README.md#basics) diff --git a/classes.md b/classes.md index 42ecdc6..0306c61 100644 --- a/classes.md +++ b/classes.md @@ -2,9 +2,10 @@ This file has been moved [here](basics/classes.md). *** -If you have trouble with this tutorial please [tell me about -it](./contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](./contact-me.md) and I'll make this tutorial better, +or [ask for help online](./getting-help.md). +If you like this tutorial, please [give it a star](./README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/common.py b/common.py index 51c22eb..7180739 100644 --- a/common.py +++ b/common.py @@ -122,16 +122,6 @@ def askyesno(question, default=True): print("Please type y, n or nothing at all.") -def slashfix(path): - """Replace / with os.sep.""" - return path.replace('/', os.sep) - - -def slashfix_open(file, mode): - """An easy way to use slashfix() and open() together.""" - return open(slashfix(file), mode) - - @contextlib.contextmanager def backup(filename): """A context manager that backs up a file.""" diff --git a/contact-me.md b/contact-me.md index 0f5aeea..eff6885 100644 --- a/contact-me.md +++ b/contact-me.md @@ -13,14 +13,15 @@ it, there are a few ways to contact me: - Tell me on IRC. - I'm usually on ##learnpython and ##python-friendly on freenode. See + I'm regularly on ##learnpython on libera. See [Getting help](getting-help.md) for instructions to getting there. *** -If you have trouble with this tutorial please [tell me about -it](./contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](./contact-me.md) and I'll make this tutorial better, +or [ask for help online](./getting-help.md). +If you like this tutorial, please [give it a star](./README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/getting-help.md b/getting-help.md index 11cd9dd..e56e7a2 100644 --- a/getting-help.md +++ b/getting-help.md @@ -3,59 +3,63 @@ When you have a problem with Python, you're not alone! There are many places to ask for help in. +Regardless of where you ask for help, please: +- Don't ask "does someone know ...". Just ask about your problem right away. +- Make your question short. +- Include everything that other people will need to answer your question. + For example, if you are getting an error, include your code and the error message. + + ## IRC -IRC is the oldest chatting service I know, but as of 2016, it's still -in use, and a great way to get help in Python. You don't need to -register anywhere, just click one of the links below and you're good to -go. - -- [##learnpython](https://kiwiirc.com/client/chat.freenode.net/##learnpython) and - [##python-friendly](https://kiwiirc.com/client/chat.freenode.net/##python-friendly) - are beginner-friendly Python support channels. In my experience, - people here tend to understand beginners' problems better, so you - probably want to go to one of these. -- [#python](https://kiwiirc.com/client/chat.freenode.net/#python) is - the official Python channel. If you have questions about advanced - topics or you need help quickly, go there. However, this channel - requires - [registering on freenode](http://www.wikihow.com/Register-a-Nickname-on-Freenode). - -Make your question short. If you want to post a code example that is -more than two lines long, post it [here](http://dpaste.com/) first. +IRC is the oldest chatting service I know, but as of 2022, it's still +in use, and a good way to get help in Python. +An advantage with IRC is that you don't need to create an account to use it. + +To get started, go to https://web.libera.chat/ and type `##learnpython` or `#python` for the channel name. + +- `##learnpython` is a channel where I am regularly, but there's usually only about 20 people there, + so it could be that nobody answers your question, depending on what time it is. + I'm on `##learnpython` at about 7PM to 10PM UTC. + If you see `Akuli` in the user list, that's me :) +- `#python` is an active channel that I don't use much, but someone will likely answer your question pretty quickly. + +If you want to post more than 3 lines of code, +put it to [dpaste.com](https://dpaste.com/) first. Just copy-paste your code to the big text area and click the "Paste it" button, and then post a link to your paste on IRC. +Otherwise every line of your code will appear as a separate message on IRC, +so if your code is 15 lines, just pasting it in will produce 15 different messages. +This would be annoying. + + +## Discord -Do this: +If you have a discord account, you can click the "Explore Public Servers" button at bottom left. - i'm trying to check if this variable equals one but i keep - getting an error http://dpaste.com/yourpaste +![Discord's explore public servers button](images/discord-explore.png) -Don't do this: +You can then search for e.g. Python, and you should find many servers to choose from. +I am currently @Akuli on a server called "The Programmer's Hangout". - HEEEEELP MEEEEEEEEEEEEEEE!!! - File "hello.py", line 3 - if a = b: - ^ - SyntaxError: invalid syntax ## Websites to ask help on Personally, I've never asked a question on any of these sites. Getting help on IRC is much faster. -- [stackoverflow](http://stackoverflow.com/) is a question/answer site +- [stackoverflow](https://stackoverflow.com/) is a question/answer site for programmers. Search for your question first, maybe someone has already asked that and it has been answered. -- At the time of writing this, - [the learnpython subreddit](https://www.reddit.com/r/learnpython/) +- [The learnpython subreddit](https://www.reddit.com/r/learnpython/) is another good place to ask Python questions on. *** -If you have trouble with this tutorial please [tell me about -it](./contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](./contact-me.md) and I'll make this tutorial better, +or [ask for help online](./getting-help.md). +If you like this tutorial, please [give it a star](./README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See diff --git a/html-style.css b/html-style.css index e2e9337..b9524a9 100644 --- a/html-style.css +++ b/html-style.css @@ -1,9 +1,9 @@ /* This file is used by the HTML files that make-html.py creates. Customize this if you want to create HTML files with different - colors. See also make-html.py's --pygments-theme option. */ + colors. See also make-html.py's --pygments-style option. */ body { color: white; - background-color: #333333; + background-color: #222222; } a { color: orange; diff --git a/images/discord-explore.png b/images/discord-explore.png new file mode 100644 index 0000000..1162495 Binary files /dev/null and b/images/discord-explore.png differ diff --git a/linkcheck.py b/linkcheck.py index 4b271ef..2dcd4c5 100755 --- a/linkcheck.py +++ b/linkcheck.py @@ -38,7 +38,7 @@ [some website](http://github.com/) [another website](https://github.com/) - [local header](#some-header) + [local link](#some-title) """ import os @@ -47,7 +47,7 @@ import common -def check(filepath, target): +def check(this_file, target, title, titledict): """Check if a link's target is like it should be. Return an error message string or "ok". @@ -57,45 +57,98 @@ def check(filepath, target): # be added later. return "ok" - if '#' in target: - where = target.index('#') - if where == 0: - # It's a link to a title in the same file, we need to skip it. - return "ok" - target = target[:where] + path = posixpath.join(posixpath.dirname(this_file), target) + path = posixpath.normpath(path) - path = posixpath.join(posixpath.dirname(filepath), target) - realpath = common.slashfix(path) - if not os.path.exists(realpath): + if not os.path.exists(path): return "doesn't exist" + if target.endswith('/'): # A directory. - if os.path.isdir(realpath): - return "ok" - return "not a directory" + if not os.path.isdir(path): + return "not a directory" else: # A file. - if os.path.isfile(realpath): - return "ok" - return "not a file" + if not os.path.isfile(path): + return "not a file" + + if title is not None and title not in titledict[path]: + return "no title named %s" % title + return "ok" + + +def find_titles(filename): + """Read titles of a markdown file and return a list of them.""" + result = [] + + with open(filename, 'r') as f: + for line in f: + if line.startswith('```'): + # it's a code block, let's skip to the end of it to + # avoid detecting comments as titles + while f.readline().rstrip() != '```': + pass + if line.startswith('#'): + # found a title + result.append(common.header_link(line.lstrip('#').strip())) + + return result + + +def find_links(this_file): + """Read links of a markdown file. + + Return a list of (target, title, lineno) pairs where title can be None. + """ + result = [] + + with open(this_file, 'r') as f: + for match, lineno in common.find_links(f): + target = match.group(2) + if '#' in target: + file, title = target.split('#', 1) + if not file: + # link to this file, [blabla](#hi) + file = posixpath.basename(this_file) + else: + file = target + title = None + + result.append((file, title, lineno)) + + return result + + +def get_line(filename, lineno): + """Return the lineno'th line of a file.""" + with open(filename, 'r') as f: + for lineno2, line in enumerate(f, start=1): + if lineno == lineno2: + return line + raise ValueError("%s is less than %d lines long" % (filename, lineno)) def main(): - print("Searching and checking links...") - broken = 0 - total = 0 + print("Searching for titles and links...") + titledict = {} # {filename: [title1, title2, ...]} + linkdict = {} # {filename: [(file, title, lineno), ...]) for path in common.get_markdown_files(): - with common.slashfix_open(path, 'r') as f: - for match, lineno in common.find_links(f): - text, target = match.groups() - status = check(path, target) - if status != "ok": - # The .group(0) is not perfect, but it's good enough. - print(" file %s, line %d: %s" % (path, lineno, status)) - print(" " + match.group(0)) - print() - broken += 1 - total += 1 + titledict[path] = find_titles(path) + linkdict[path] = find_links(path) + + print("Checking the links...") + total = 0 + broken = 0 + + for filename, linklist in linkdict.items(): + for target, title, lineno in linklist: + status = check(filename, target, title, titledict) + if status != "ok": + print(" file %s, line %d: %s" % (filename, lineno, status)) + print(" %s" % get_line(filename, lineno)) + broken += 1 + total += 1 + print("%d/%d links seem to be broken." % (broken, total)) diff --git a/make-html.py b/make-html.py index 0a06260..b40ec3a 100755 --- a/make-html.py +++ b/make-html.py @@ -30,26 +30,34 @@ import argparse import os +import platform import posixpath import shutil import sys import textwrap import webbrowser +if platform.system() == 'Windows': + python = 'py' +else: + python = 'python3' + try: import mistune except ImportError: print("mistune isn't installed.", file=sys.stderr) - print("You can install it like this:") + print("You can install it by running this command on a terminal or ") + print("command prompt:") print() - print(">>> import pip") - print(">>> pip.main(['install', '--user', 'mistune'])") + print(" %s -m pip install mistune" % python) sys.exit(1) try: import pygments.formatters import pygments.lexers + import pygments.style import pygments.styles + import pygments.token except ImportError: # we can work without pygments, but we won't get colors pygments = None @@ -57,6 +65,18 @@ import common +if pygments is not None: + class TutorialStyle(pygments.style.Style): + background_color = '#111111' + styles = { + pygments.token.Comment: 'italic #336666', + pygments.token.Keyword: 'bold #6699cc', + pygments.token.Name.Builtin: '#9966ff', + pygments.token.String: '#ffff33', + pygments.token.Name.Exception: 'bold #ff0000', + } + + HTML_TEMPLATE = """\ @@ -72,12 +92,11 @@ """ -def mkdir_slashfix_open(filename, mode): - """Like common.slashfix_open(), but make directories as needed.""" - real_filename = common.slashfix(filename) - directory = os.path.dirname(real_filename) +def mkdir_and_open(filename, mode): + """Like open(), but make directories as needed.""" + directory = os.path.dirname(filename) os.makedirs(directory, exist_ok=True) - return open(real_filename, mode) + return open(filename, mode) def fix_filename(filename): @@ -93,7 +112,7 @@ def fix_filename(filename): return filename -class TutorialRenderer(mistune.Renderer): +class TutorialRenderer(mistune.HTMLRenderer): def __init__(self, pygments_style): super().__init__() @@ -129,14 +148,35 @@ def block_code(self, code, lang=None): if lang == 'python' and pygments is not None: # we can highlight it if code.startswith('>>> '): - lexer = pygments.lexers.PythonConsoleLexer() + lexer = pygments.lexers.PythonConsoleLexer(python3=True) else: - lexer = pygments.lexers.PythonLexer() + lexer = pygments.lexers.Python3Lexer() formatter = pygments.formatters.HtmlFormatter( style=self.pygments_style, noclasses=True) return pygments.highlight(code, lexer, formatter) - # we can't highlight it - return super().block_code(code, lang) + + elif lang == 'diff': + # http://stackoverflow.com/a/39413824 + result = [] + for line in code.split('\n'): + line = line.strip() + if not line: + continue + + if line.startswith('+'): + result.append('

%s

' + % line.strip('+')) + elif line.startswith('-'): + result.append('

%s

' + % line.strip('-')) + else: + result.append('

%s

' % line) + + return '\n'.join(result) + + else: + # we can't highlight it + return super().block_code(code, lang) def image(self, src, title, text): """Return an image inside a link.""" @@ -177,23 +217,23 @@ def main(): help="write the HTML files here, defaults to %(default)r") if pygments is not None: parser.add_argument( - '--pygments-style', metavar='STYLE', default='native', + '--pygments-style', metavar='STYLE', default=TutorialStyle, choices=list(pygments.styles.get_all_styles()), help=("the Pygments color style (see above), " - "%(default)r by default")) + "defaults to a custom style")) args = parser.parse_args() if pygments is None: print("Pygments isn't installed. You can install it like this:") print() - print(">>> import pip") - print(">>> pip.main(['install', '--user', 'pygments'])") + print(" %s -m pip install pygments" % python) print() print("You can also continue without Pygments, but the code examples") print("will not be colored.") if not common.askyesno("Continue without pygments?"): print("Interrupt.") return + args.pygments_style = None if os.path.exists(args.outdir): if not common.askyesno("%s exists. Do you want to remove it?" @@ -207,23 +247,23 @@ def main(): print("Generating HTML files...") for markdownfile in common.get_markdown_files(): - fixed_markdownfile = fix_filename(markdownfile) - htmlfile = posixpath.join(args.outdir, fixed_markdownfile) + fixed_file = fix_filename(markdownfile) + htmlfile = posixpath.join(args.outdir, fixed_file) print(' %-30.30s --> %-30.30s' % (markdownfile, htmlfile), end='\r') - with common.slashfix_open(markdownfile, 'r') as f: + with open(markdownfile, 'r') as f: markdown = f.read() renderer = TutorialRenderer(args.pygments_style) body = mistune.markdown(markdown, renderer=renderer) stylefile = posixpath.relpath( - 'style.css', posixpath.dirname(fixed_markdownfile)) + 'style.css', posixpath.dirname(fixed_file)) html = HTML_TEMPLATE.format( title=renderer.title, body=body, stylefile=stylefile, ) - with mkdir_slashfix_open(htmlfile, 'w') as f: + with mkdir_and_open(htmlfile, 'w') as f: print(html, file=f) print() diff --git a/update-ends.py b/update-ends.py index 820ef6a..9b2d1ef 100755 --- a/update-ends.py +++ b/update-ends.py @@ -35,9 +35,10 @@ END_TEMPLATE = """\ -If you have trouble with this tutorial please [tell me about -it]({toplevel}/contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it]({toplevel}/contact-me.md) and I'll make this tutorial better, +or [ask for help online]({toplevel}/getting-help.md). +If you like this tutorial, please [give it a star]({toplevel}/README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See @@ -85,7 +86,7 @@ def update_end(filename, end): separator. """ end = '\n***\n\n' + end - with common.slashfix_open(filename, 'r') as f: + with open(filename, 'r') as f: content = f.read() if content.endswith(end): # No need to do anything. @@ -96,11 +97,11 @@ def update_end(filename, end): # We need to remove the old ending first. print(" Removing old end:", filename) where = content.index('\n***\n') - with common.slashfix_open(filename, 'w') as f: + with open(filename, 'w') as f: f.write(content[:where]) print(" Adding end:", filename) - with common.slashfix_open(filename, 'a') as f: + with open(filename, 'a') as f: f.write(end) diff --git a/what-next.md b/what-next.md index bd2de84..8437d95 100644 --- a/what-next.md +++ b/what-next.md @@ -31,9 +31,10 @@ is a way to create generators *** -If you have trouble with this tutorial please [tell me about -it](./contact-me.md) and I'll make this tutorial better. If you -like this tutorial, please [give it a +If you have trouble with this tutorial, please +[tell me about it](./contact-me.md) and I'll make this tutorial better, +or [ask for help online](./getting-help.md). +If you like this tutorial, please [give it a star](./README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). You may use this tutorial freely at your own risk. See