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 89dad9f..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 @@ -30,19 +30,21 @@ to learn more about whatever you want after studying it. 4. [ThinkPython: The way of the program](basics/the-way-of-the-program.md) 5. [Variables, Booleans and None](basics/variables.md) 6. [Using functions](basics/using-functions.md) -7. [If, else and elif](basics/if.md) -8. [Handy stuff with strings](basics/handy-stuff-strings.md) -9. [Lists and tuples](basics/lists-and-tuples.md) -10. [Loops](basics/loops.md) -11. [Trey Hunner: zip and enumerate](basics/trey-hunner-zip-and-enumerate.md) -12. [Dictionaries](basics/dicts.md) -13. [Defining functions](basics/defining-functions.md) -14. [Writing a larger program](basics/larger-program.md) -15. [What is true?](basics/what-is-true.md) -16. [Files](basics/files.md) -17. [Exceptions](basics/exceptions.md) +7. [Setting up an editor](basics/editor-setup.md) +8. [If, else and elif](basics/if.md) +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. [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) +16. [What is true?](basics/what-is-true.md) +17. [Files](basics/files.md) 18. [Modules](basics/modules.md) -19. [Classes](basics/classes.md) +19. [Exceptions](basics/exceptions.md) +20. [Classes](basics/classes.md) +21. [Docstrings](basics/docstrings.md) ### Advanced @@ -51,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. @@ -65,8 +67,7 @@ than complex. - **Important:** [getting help](getting-help.md) - [Contact me](contact-me.md) -- [Setting up a text editor](editor-setup.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 2ed3c8c..ff1ecde 100644 --- a/TODO.md +++ b/TODO.md @@ -2,6 +2,12 @@ This tutorial is not complete. It still needs: +- replacement for the thinkpython linking file + - what is a type and why it matters + - also add `type()` calls to other places, especially when + introducing tuples (thanks kodec), functions and modules + - `3.14` and `'3.14'` + - `# 'hello'` and `'# hello'` - range somewhere - **More exercises and examples everywhere!** - especially "fix this" exercises @@ -18,6 +24,7 @@ This tutorial is not complete. It still needs: - advise to avoid multiple inheritance - last chapter: "What should I do now?" links to other resources - first of all: read zen and pep8 + - explanation of the zen, especially the "one right way" myth - GUI programming tutorials - easygui - tkinter in effbot (warn the readers about star imports) @@ -28,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 new file mode 100644 index 0000000..bbb46e5 --- /dev/null +++ b/advanced/README.md @@ -0,0 +1,32 @@ +[comment]: # (This file is automatically generated. Don't edit this) +[comment]: # (file manually, run update-readmes.py instead.) + +# Advanced + +If you want to learn more advanced techniques, you can also read this +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 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. + +1. [Handy data types](datatypes.md) +2. [Advanced stuff with functions](functions.md) +3. [Magic methods](magicmethods.md) +4. [Iterables, iterators and generators](iters.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). + +[List of contents](../README.md#list-of-contents) 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 4695100..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 @@ -21,9 +21,9 @@ like if they were dictionaries. This programming style is known as ## Sets Let's say we have a program that keeps track of peoples' names. We can -store the names in [a list](../basics/lists.md), and adding a new name -is easy as appending to that list. Lists remember their order and it's -possible to add the same thing multiple times. +store the names in [a list](../basics/lists-and-tuples.md), and adding a +new name is easy as appending to that list. Lists remember their order +and it's possible to add the same thing multiple times. ```python >>> names = ['wub_wub', 'theelous3', 'RubyPinch', 'go|dfish', 'Nitori'] @@ -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 new file mode 100644 index 0000000..23d1059 --- /dev/null +++ b/basics/README.md @@ -0,0 +1,42 @@ +[comment]: # (This file is automatically generated. Don't edit this) +[comment]: # (file manually, run update-readmes.py instead.) + +# Basics + +This section will get you started with using Python and you'll be able +to learn more about whatever you want after studying it. + +1. [What is programming?](what-is-programming.md) +2. [Installing Python](installing-python.md) +3. [Getting started with Python](getting-started.md) +4. [ThinkPython: The way of the program](the-way-of-the-program.md) +5. [Variables, Booleans and None](variables.md) +6. [Using functions](using-functions.md) +7. [Setting up an editor](editor-setup.md) +8. [If, else and elif](if.md) +9. [Handy stuff with strings](handy-stuff-strings.md) +10. [Lists and tuples](lists-and-tuples.md) +11. [Loops](loops.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) +16. [What is true?](what-is-true.md) +17. [Files](files.md) +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, +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). + +[List of contents](../README.md#list-of-contents) diff --git a/basics/answers.md b/basics/answers.md index 447673b..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')` @@ -272,6 +278,53 @@ isn't exactly like mine but it works just fine it's ok, and you can The third part calculates `result + n` but throws away the value. It was probably supposed to do `result += n` instead. +4. If you run this program you'll notice that nothing happened to the + numbers list. The reason is that the `number` variable only works + one way. It gets its values from the `numbers` list, but changing it + doesn't change the `numbers` list. In general, `thing = stuff` + changes the `thing` variable, and that's it. It doesn't change + anything else. + + An easy solution is to just create a new list: + + ```python + numbers = ['1', '2', '3'] + converted_numbers = [] + for number in numbers: + converted_numbers.append(int(number)) + 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. @@ -411,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 a59b4d2..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](modules.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 9f2d3a7..597d1eb 100644 --- a/basics/dicts.md +++ b/basics/dicts.md @@ -2,78 +2,81 @@ Now we know how [lists and tuples](lists-and-tuples.md) work and how to [for loop](loops.md#for-loops) over them. If we make some kind of -program that needs to keep track of people's usernames and passwords, +program that needs to keep track of people's names and favorite pets, we can use a list for that: ```python -userlist = [ - ('me', 'my password'), - ('you', 'your password'), +names_and_pets = [ + ('horusr', 'cats'), + ('caisa64', 'cats and dogs'), + ('__Myst__', 'cats'), ] ``` -Then to check if a username and password are correct we can do -`(username, password) in userlist`. Or we can add a new user easily by -appending a new `(username, password)` tuple to the userlist. +Then to check if cats are horusr's favorite pets we can do +`('horusr', 'cats') in names_and_pets`. Or we can add new people's +favorite pets easily by appending new `(name, pets)` tuples to the list. -But what if we need to check if a username exists, but we don't know -the password? `username in userlist` is always False because the user -list consists of `(username, password)` pairs instead of just -usernames, so we need to for loop over the whole userlist: +But what if we need to check if we know anything about someone's +favorite pets? `'caisa64' in names_and_pets` is always False because the +pet list consists of `(name, pets)` pairs instead of just names, so we +need to for loop over the whole pet list: ```python -username_exists = False -for user in userlist: - if user[0] == username: - username_exists = True +found_caisa64 = False +for pair in names_and_pets: + if pair[0] == 'caisa64': + found_caisa64 = True break -if username_exists: +if found_caisa64: # do something ``` -Or how about getting a user's password if we know the username? This +Or what if we need to find out what caisa64's favorite pets are? That also requires going through the whole list. ```python -password = None -for user in userlist: - if user[0] == username: - password = user[1] +pets = None +for pair in names_and_pets: + if pair[0] == 'caisa64': + pets = pair[1] break -# make sure password is not None and do something with it +# make sure pets is not None and do something with it ``` -As you can see, a list of `(username, password)` pairs is not an ideal -way to store our usernames and passwords. +As you can see, a list of `(name, pets)` pairs is not an ideal +way to store names and favorite pets. ## What are dictionaries? -A better way to store user information might be a dictionary: +A better way to store information about favorite pets might be a +dictionary: ```python -passwords = { - 'me': 'my password', - 'you': 'your password', +favorite_pets = { + 'horusr': 'cats', + 'caisa64': 'cats and dogs', + '__Myst__': 'cats', } ``` -Here `'me'` and `'you'` are **keys** in the dictionary, and -`'my password'` and `'your password'` are their **values**. Dictionaries -are often named by their values. This dictionary has passwords as its -values so I named the variable `passwords`. +Here `'horusr'` and `'caisa64'` are **keys** in the dictionary, and +`'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`. There are a few big differences between dictionaries and lists of pairs: - Dictionaries are not ordered. There are **no guarantees** about which - order the `username: password` pairs appear in when we do something + order the `name: pets` pairs appear in when we do something with the dictionary. - Checking if a key is in the dictionary is simple and fast. We don't need to for loop through the whole dictionary. - Getting the value of a key is also simple and fast. - We can't have the same key in the dictionary multiple times, but multiple different keys can have the same value. This means that - **multiple users can't have the same name, but they can have the - same passwords**. + **multiple people can't have the same name, but they can have the + same favorite pets**. But wait... this is a lot like variables are! Our variables are not ordered, getting a value of a variable is fast and easy and we can't @@ -85,18 +88,22 @@ variable names and values are what our variables point to. ```python >>> globals() -{'userlist': [('me', 'my password'), ('you', 'your password')], - 'passwords': {'me': 'my password', 'you': 'your password'}, +{'names_and_pets': [('horusr', 'cats'), + ('caisa64', 'cats and dogs'), + ('__Myst__', 'cats')], + 'favorite_pets': {'__Myst__': 'cats', + 'caisa64': 'cats and dogs', + 'horusr': 'cats'}, ...many other things we don't need to care about... } >>> ``` So if you have trouble remembering how dictionaries work just compare -them to variables. A dictionary is a perfect way to store our usernames -and passwords. We don't care about which order the users were added in, -it's impossible to add multiple users with the same username and -getting a user's password is easy. +them to variables. A dictionary is a perfect way to store these names +and favorite pets. We don't care about which order the names and pets +were added in, it's impossible to add the same name multiple times and +getting someone's favorite pets is easy. ## What can we do with dictionaries? @@ -104,10 +111,10 @@ Dictionaries have some similarities with lists. For example, both lists and dictionaries have a length. ```python ->>> len(userlist) # contains two elements -2 ->>> len(passwords) # contains two key:value pairs -2 +>>> len(names_and_pets) # contains three elements +3 +>>> len(favorite_pets) # contains three key:value pairs +3 >>> ``` @@ -115,26 +122,38 @@ We can get a value of a key with `the_dict[key]`. This is a lot easier and faster than for-looping over a list of pairs. ```python ->>> passwords['me'] -'my password' ->>> passwords['you'] -'your password' +>>> favorite_pets['caisa64'] +'cats and dogs' +>>> favorite_pets['__Myst__'] +'cats' >>> ``` -Trying to get the value of a non-existing key gives us an error, but we -can add new `key: value` pairs by doing `the_dict[key] = value`. +Trying to get the value of a non-existing key gives us an error. ```python ->>> passwords['lol'] +>>> favorite_pets['Akuli'] Traceback (most recent call last): File "", line 1, in -KeyError: 'lol' ->>> passwords["lol"] = "lol's password" ->>> passwords["lol"] -"lol's password" ->>> passwords -{'lol': "lol's password", 'you': 'your password', 'me': 'my password'} +KeyError: 'Akuli' +>>> +``` + +But we can add new `key: value` pairs or change the values of existing +keys by doing `the_dict[key] = value`. + +```python +>>> favorite_pets['Akuli'] = 'penguins' +>>> favorite_pets['Akuli'] +'penguins' +>>> favorite_pets['Akuli'] = 'dogs' +>>> favorite_pets['Akuli'] +'dogs' +>>> favorite_pets +{'__Myst__': 'cats', + 'Akuli': 'dogs', + 'horusr': 'cats', + 'caisa64': 'cats and dogs'} >>> ``` @@ -143,16 +162,17 @@ is in the dictionary checks if the dictionary has a key like that. This can be confusing at first but you'll get used to this. ```python ->>> 'me' in passwords +>>> 'Akuli' in favorite_pets True ->>> 'my password' in passwords +>>> 'dogs' in favorite_pets False ->>> for name in passwords: +>>> for name in favorite_pets: ... print(name) ... -lol -you -me +caisa64 +Akuli +__Myst__ +horusr >>> ``` @@ -160,8 +180,8 @@ Dictionaries have a values method that we can use if we want to do something with the values: ```python ->>> passwords.values() -dict_values(["lol's password", 'your password', 'my password']) +>>> favorite_pets.values() +dict_values(['dogs', 'cats', 'cats and dogs', 'cats']) >>> ``` @@ -170,17 +190,18 @@ behave a lot like lists and usually we don't need to convert them to lists. ```python ->>> for password in passwords.values(): -... print(password) +>>> for pets in favorite_pets.values(): +... print(pets) ... -lol's password -your password -my password +dogs +cats +cats and dogs +cats >>> ``` -We can do things like `list(passwords.values())` if we need a real list -for some reason, but doing that can slow down our program if the +We can do things like `list(favorite_pets.values())` if we need a real +list for some reason, but doing that can slow down our program if the dictionary is big. There's also a keys method, but usually we don't need it because the dictionary itself behaves a lot like a list of keys. @@ -188,16 +209,18 @@ If we need both keys and values we can use the items method with the `for first, second in thing` trick. ```python ->>> passwords.items() -dict_items([('lol', "lol's password"), - ('you', 'your password'), - ('me', 'my password')]) ->>> for name, password in passwords.items(): -... print(name + ": " + password) +>>> favorite_pets.items() +dict_items([('Akuli', 'dogs'), + ('__Myst__', 'cats'), + ('caisa64', 'cats and dogs'), + ('horusr', 'cats')]) +>>> for name, pets in favorite_pets.items(): +... print("{} are {}'s favorite pets".format(pets, name)) ... -lol: lol's password -you: your password -me: my password +dogs are Akuli's favorite pets +cats are __Myst__'s favorite pets +cats and dogs are caisa64's favorite pets +cats are horusr's favorite pets >>> ``` @@ -205,9 +228,9 @@ This is also useful for checking if the dictionary has a `key: value` pair. ```python ->>> ('me', 'my password') in passwords.items() # correct username and password +>>> ('horusr', 'cats') in favorite_pets.items() True ->>> ('me', 'whatever') in passwords.items() # wrong username or password +>>> ('horusr', 'dogs') in favorite_pets.items() False >>> ``` @@ -297,15 +320,18 @@ Running the program might look like this: and appears once in the sentence test appears 2 times in the sentence +**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 [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 new file mode 100644 index 0000000..be13ea5 --- /dev/null +++ b/basics/editor-setup.md @@ -0,0 +1,100 @@ +# Setting up an editor for programming + +An editor is a program that lets us write longer programs than we can +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 +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, +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. You can use IDLE, but we recommend exploring other options first. + + +## Which editor? + +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. + +The editors can be broadly divided into three categories: + +#### 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. + +A few popular ones in this category are: +- Notepad (Windows) +- Gedit (Linux) +- Notepad ++ (Windows) +- Nano (Linux/Mac OS) + +#### 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. + +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) + +**We recommend that you look into a few of these editors and install your favorite one.** + +#### 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. + +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, +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](using-functions.md) | [Next](if.md) | +[List of contents](../README.md#basics) diff --git a/basics/exceptions.md b/basics/exceptions.md index ad241ef..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,13 +461,14 @@ 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 [LICENSE](../LICENSE). -[Previous](files.md) | [Next](modules.md) | +[Previous](modules.md) | [Next](classes.md) | [List of contents](../README.md#basics) diff --git a/basics/files.md b/basics/files.md index 5f0e5c8..5a8f7dd 100644 --- a/basics/files.md +++ b/basics/files.md @@ -365,13 +365,14 @@ 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 [LICENSE](../LICENSE). -[Previous](what-is-true.md) | [Next](exceptions.md) | +[Previous](what-is-true.md) | [Next](modules.md) | [List of contents](../README.md#basics) diff --git a/basics/getting-started.md b/basics/getting-started.md index c7946cf..9f00625 100644 --- a/basics/getting-started.md +++ b/basics/getting-started.md @@ -18,7 +18,9 @@ NameError: name 'hello' is not defined ``` Oops! That didn't work. But like I wrote in the -[introduction](what-is-programming.md), **errors don't matter**. +[introduction](what-is-programming.md), error messages are our friends. +This error message tells us what's wrong and where, and we'll learn what +"name 'hello' is not defined" means [later](variables.md). Maybe we can press Enter without typing anything? @@ -58,164 +60,162 @@ 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. -What if we type a `#`? +## Comments -```python ->>> # ->>> -``` - -Nothing happened at all. Maybe we can type a `#` and then some text -after it? +**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 ->>> # hello there +>>> 1 + 2 # can you guess what the result is? +3 >>> ``` -Again, nothing happened. +Again, I put a space after the `#` and multiple spaces before it just to +make things easier to read. -If you're not using IDLE, the prompt will change from `>>>` to -`...`. Just press Enter again to get it back to `>>>`. +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 again +>>> # hello there ... >>> ``` -In Python, these pieces of text starting with a `#` are known as -**comments**. They don't change how the code works in any way, but -we can use them to explain what our code does. - -## Using Python as a calculator +## Strings -Maybe we could type mathematical statements? +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 ->>> 17 + 3 -20 ->>> 17 - 3 -14 ->>> 17 * 3 -51 ->>> 17 / 3 -5.666666666666667 ->>> +>>> 'hello' +'hello' +>>> 'this is a test' +'this is a test' +>>> ``` -It's working, Python just calculates the result and echoes it back. - -The spaces between numbers and operators don't affect anything, they -just make the code easier to read when they are used correctly. +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 ->>> 14 + 2 + 1 -17 ->>> 14 +2+ 1 -17 ->>> +>>> "hello there" +'hello there' +>>> "it's sunny" +"it's sunny" +>>> ``` -The evaluation order is similar to math. The parentheses `(` and `)` -also work the same way. +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 ->>> 1 + 2 * 3 # 2 * 3 is calculated first -7 ->>> (1 + 2) * 3 # 1 + 2 is calculated first -9 ->>> +>>> "hello" + "world" +'helloworld' +>>> "hello" * 3 +'hellohellohello' +>>> ``` -Square brackets `[]` and curly brackets `{}` cannot be used to change -the evaluation order. We'll learn more about what they do later. +Note that a `#` inside a string doesn't create a comment. ```python ->>> [1 + 2] * 3 -[3, 3, 3] ->>> {1 + 2} * 3 -Traceback (most recent call last): - File "", line 1, in -TypeError: unsupported operand type(s) for *: 'set' and 'int' ->>> +>>> "strings can contain # characters" +'strings can contain # characters' +>>> ``` -## More advanced math +## Using Python as a calculator -I decided to include this in my tutorial because some people might be -interested in this. Feel free to [skip this](#summary) if you're not -interested. +```diff +---------- WARNING: This part contains boring math. Proceed with caution. ---------- +``` -The `//` operator will divide and then throw away the dot and everything -after it. For example, `17 / 3` is `5.666666666666667`, and so `17 // 3` -is `5` because we throw away the `.666666666666667` part. +Let's type some math stuff into Python and see what it does. ```python +>>> 17 + 3 +20 +>>> 17 - 3 +14 +>>> 17 * 3 +51 >>> 17 / 3 5.666666666666667 ->>> 17 // 3 -5 >>> ``` -The `%` operator gets the division remainder. - -```python ->>> 17 % 3 -2 ->>> -``` +It's working, Python just calculates the result and echoes it back. -For example, if there were 17 apples that should be given evenly to 3 -people, everyone would get 5 apples and there would be 2 apples left -over. +I added a space on both sides of `+`, `-`, `*` and `/`. Everything would +work without those spaces too: ```python ->>> 17 // 3 -5 ->>> 17 % 3 -2 +>>> 4 + 2 + 1 +7 +>>> 4+2+1 +7 >>> ``` -This is also useful for converting time from minutes to seconds. 500 -seconds is 8 minutes and 20 seconds. +However, I recommend always adding the spaces because they make the code +easier to read. + +Things are calculated in the same order as in math. The parentheses `(` +and `)` also work the same way. ```python ->>> 500 // 60 -8 ->>> 500 % 60 -20 +>>> 1 + 2 * 3 # 2 * 3 is calculated first +7 +>>> (1 + 2) * 3 # 1 + 2 is calculated first +9 >>> ``` -`**` can be used to raise to a power, so 3² in math is `3**2` in Python. -Powers are calculated before `*` and `/`, but after `()`. +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 ->>> 2 ** 3 -8 ->>> 2 * 3 ** 2 # 3 ** 2 is calculated first -18 ->>> (2 * 3) ** 2 # 2 * 3 is calculated first -36 +>>> 1 + 2*3 # now it looks like 2*3 is calculated first +7 >>> ``` +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 -- Errors don't matter. +[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. -- Pieces of text starting with a `#` are comments. - `+`, `-`, `*` and `/` work in Python just like in math. +- 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 f1825c8..71655f2 100644 --- a/basics/if.md +++ b/basics/if.md @@ -40,19 +40,6 @@ An important thing to notice is that the line with a print is **indented**. You can press the tab key, or if it doesn't work just press space a few times. -IDLE does this a bit differently, so if you use IDLE, running the -example code looks more like this: - -```python ->>> its_raining = True ->>> if its_raining: - print("It's raining!") - - -It's raining! ->>> -``` - But why is that `if its_raining` instead of `if(its_raining)`? Earlier we learned that `if` is a **keyword**. @@ -66,12 +53,12 @@ SyntaxError: invalid syntax >>> ``` -**Functions** like `print` need `()` after their name to work. But `if` is -a keyword, not a function, so it doesn't need `()`. Python has separate -functions and keywords because it's possible to create custom functions, -but it's not possible to create custom keywords. That's why keywords are -usually used for "magic" things that would be difficult to do with just -functions. +**Functions** like `print` need `()` after their name to work. But `if` +is **a keyword**, not a function, so it doesn't need `()`. Python has +separate functions and keywords because it's possible to create custom +functions, but it's not possible to create custom keywords. That's why +keywords are usually used for "magic" things that would be difficult to +do with just functions. Also note that if statements check the condition once only, so if we set it to false later the if statement won't notice it. @@ -86,62 +73,6 @@ It's not raining, but this runs anyway. >>> ``` -## Storing code in files - -At this point it's easier to put our code into a file and use it -there. If you use IDLE, go to File at top left and select New File, or -just press Ctrl+N. - -![New File in IDLE](../images/idle-new.png) - -If you don't use IDLE, please take the time to -[set up your editor correctly](../editor-setup.md). When you're done your -editor should give you four spaces every time you press tab. - -Create a file called `rain.py`, and type the following content into it: - -```python -its_raining = True -if its_raining: - print("It's raining!") -``` - -You can save the file anywhere you want, for example on your desktop. -Give it a name that ends with `.py`, for example `rain.py`. The `.py` -is short for Python. - -Now we can run the rain program. Most editors (including IDLE) will -run our code when we press F5. If your editor doesn't, run it from -PowerShell, command prompt or terminal. You probably need to first go -to wherever you saved your file with `cd`. For example, if the file is -on your desktop, type `cd Desktop` before running the file. - -Running from IDLE looks like this: - - >>> - ========================= RESTART: /some/place/rain.py ========================= - It's raining! - >>> - -And running from the Windows PowerShell or command prompt looks like -this: - - C:\Users\You> cd Desktop - C:\Users\You\Desktop> py rain.py - It's raining! - C:\Users\You\Desktop> - -Running from a terminal looks like this: - - you@YourComputer:~$ cd Desktop - you@YourComputer:~/Desktop$ python3 rain.py - It's raining! - you@YourComputer:~/Desktop$ - -From now on, **if a code example starts with `>>>` run it on the -interactive prompt, and if it doesn't, write it to a file and run the -file**. - ## Using else What if we want to print a different message if it's not raining? We @@ -157,6 +88,9 @@ if its_not_raining: print("It's not raining.") ``` +Note that this code example doesn't start with `>>>`, so you should +[save it to a file and run the file](editor-setup.md). + Now our program will print a different value depending on what the value of `its_raining` is. @@ -208,19 +142,19 @@ else: print("Access denied.") ``` -The program prints different things depending on what we enter. +The program prints different things depending on what we enter: + +``` +Hello! +Enter your password: secret +Welcome! +``` - >>> ================================ RESTART ================================ - >>> - Hello! - Enter your password: secret - Welcome! - >>> ================================ RESTART ================================ - >>> - Hello! - Enter your password: lol - Access denied. - >>> +``` +Hello! +Enter your password: lol +Access denied. +``` Using the input function for passwords doesn't work very well because we can't hide the password with asterisks. There are better ways to get @@ -228,8 +162,7 @@ a password from the user, but you shouldn't worry about that just yet. ## Avoiding many levels of indentation with elif -If we have more than one condition to check, our code will end up -looking a bit messy. +If we have more than one condition to check, we could do this: ```python print("Hello!") @@ -253,6 +186,11 @@ else: print("I don't know what", word, "means.") ``` +This code is a mess. We need to indent more every time we want to check +for more words. Here we check for 5 different words, so we have 5 levels +of indentation. If we would need to check 30 words, the code would +become really wide and it would be hard to work with. + Instead of typing `else`, indenting more and typing an `if` we can simply type `elif`, which is short for `else if`. Like this: @@ -274,6 +212,54 @@ else: print("I don't know what", word, "means.") ``` +Now the program is shorter and much easier to read. + +Note that the `elif` parts only run if nothing before them matches, and +the `else` runs only when none of the `elifs` match. If we would have +used `if` instead, all possible values would be always checked and the +`else` part would run always except when word is `"gday m8"`. This is +why we use `elif` instead of `if`. + +For example, this program prints only `hello`... + +```python +if 1 == 1: + print("hello") +elif 1 == 2: + print("this is weird") +else: + print("world") +``` + +...but this prints `hello` *and* `world`: + +```python +if 1 == 1: + print("hello") +if 1 == 2: + print("this is weird") +else: + print("world") +``` + +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. 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 - If a code example starts with `>>>` run it on the interactive prompt. @@ -303,6 +289,7 @@ else: something = input("Enter something: ") if something = 'hello': print("Hello for you too!") + elif something = 'hi' print('Hi there!') else: @@ -325,17 +312,19 @@ else: 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 [LICENSE](../LICENSE). -[Previous](using-functions.md) | [Next](handy-stuff-strings.md) | +[Previous](editor-setup.md) | [Next](handy-stuff-strings.md) | [List of contents](../README.md#basics) diff --git a/basics/installing-python.md b/basics/installing-python.md index 66b1ecb..249cc4b 100644 --- a/basics/installing-python.md +++ b/basics/installing-python.md @@ -6,102 +6,71 @@ need to try out the code examples. You can use a website like installing Python. That way you don't need to open a web browser just to write code, and you can work without an Internet connection. +It doesn't matter which operating system you use because Python runs +great on Windows, Mac OSX, Linux and many other operating systems. +However, installing and launching Python are done differently on +different operating systems, so just follow your operating system's +instructions. + Let's get started! ## Downloading and installing Python ### Windows -Use the official Python installer, it will install Python and IDLE for -you. +Installing Python on Windows is a lot like installing any other program. 1. Go to [the official Python website](https://www.python.org/). -2. Move your mouse over the blue Downloads button, but don't click it, - Then click the button that downloads the latest version of Python. +2. Move your mouse over the blue Downloads button, but don't click it. + Then click the button that downloads the latest version of Python. 3. Run the installer. -4. Install Python like any other program. Make sure the py.exe - launcher gets installed. +4. Make sure that the launcher gets installed and click Install Now. -### Mac OSX + ![The py.exe launcher.](../images/py-exe.png) -I don't have an up-to-date copy of Mac OSX. If you would like to write -instructions for OSX, [tell me](../contact-me.md). +### Mac OSX -### GNU/Linux +At the time of writing this, Macs don't come with a Python 3 and you +need to install it yourself. It should be like installing any other +program, but unfortunately I don't have better instructions because I +don't have an up-to-date Mac and I have never installed Python on a Mac. +If you would like to write better instructions, [tell +me](../contact-me.md). -You already have Python, there's no need to download anything. +### Linux -If you want to use IDLE (see below), install it. The name of the -package is `idle3` on Debian-based distributions, like Ubuntu and Linux -Mint, and you can install it with a software manager like any other -program. On other distributions you can just search for idle using the -distribution's package manager. +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 the programs that came with your operating system +are probably written in Python 2, so removing Python 2 would +break them. ## Running Python -Now you have Python installed. There are several ways to run Python: - -1. Directly from PowerShell, command prompt or terminal. -2. Using IDLE. -3. Using something else. - -I'm not going to focus on the third option in this tutorial, but if you -know how to use Python with something else than PowerShell, command -prompt, a terminal or IDLE it's fine. Do whatever you want. - -### If you are not an advanced user and you have no idea what PowerShell, command prompt and terminal are +Next we'll learn to run Python on a PowerShell or terminal. There are +several other ways to run Python, but if you learn this way now it's +going to make things easier later. -Use IDLE. Experienced Python users will say that IDLE is garbage, but -don't listen to them. These people want you to use "better" -alternatives with more features, but that's exactly what you don't want -as a beginner. You should spend as little time as possible learning -your tools, and as much time as possible learning Python. Advanced -programming tools are not going to help you with this at all. - -Launch Python's IDLE like any other program. You should see something -like this: - -![IDLE](../images/idle.png) - -From now on, I'll instead show everything like this, so I don't have to -take more screenshots: - - Python 3.4.3 (default, Oct 14 2015, 20:28:29) - [GCC 4.8.4] on linux - Type "copyright", "credits" or "license()" for more information. - >>> - -The exact content of your Python's welcome message is probably different -than mine, it's ok. +### Windows -### If you like working with PowerShell, command prompt or terminal +1. Open a PowerShell from your start menu or start screen. +2. Type `py` and press Enter. You should see something like this: -On Windows. you should be able to run Python from a PowerShell window, -or a command prompt window if you don't have PowerShell. Open one of -these programs from the start menu or start screen, type there `py` and -press Enter. You should see something like this in it: + ![Python running in a PowerShell window.](../images/powershell.png) - C:\Users\You> py - Python 3.4.4 (v3.4.4:737efcadf5a6, Dec 20 2015, 19:28:18) - [MSC v.1600 32 bit (Intel)] on win32 - Type "help", "copyright", "credits" or "license" for more information. - >>> +### Other operating systems -On GNU/Linux or Mac OSX, you should have a terminal application installed -already. Run it and type `python3`: +1. Open a terminal. How exactly this is done depends on your operating + system, but most operating systems have some way to search for + programs. Search for a program called terminal and launch it. +2. Type `python3` and press Enter. You should see something like this: - you@YourComputer:~$ python3 - Python 3.4.3 (default, Oct 14 2015, 20:28:29) - [GCC 4.8.4] on linux - Type "help", "copyright", "credits" or "license" for more information. - >>> + ![Running Python on my terminal.](../images/terminal.png) -Now you can type `exit()` and press Enter to get out of Python. + Your terminal probably looks different than mine, it's OK. -You may also have an older version of Python installed, but don't remove -it. Your system may need it, so if you replace it with your own Python -some things might stop working. See -[this](https://docs.python.org/3/faq/installed.html) for more info. +Now you can type `exit()` and press Enter to get out of Python. Or you +can just close the PowerShell or Terminal window. ## Summary @@ -109,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 2686372..7f693e1 100644 --- a/basics/loops.md +++ b/basics/loops.md @@ -192,7 +192,7 @@ how about you >>> ``` -But there's `len()` and an index variable we need to increment and a +But we have `len()` and an index variable we need to increment and a while loop and many other things to worry about. That's a lot of work just for printing each item. @@ -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]) @@ -432,7 +434,8 @@ while True: print(thing) ``` -2. This code is supposed to print `[1, 2, 3, 4, 5, 6]`. Fix it. +2. This code is supposed to print `[1, 2, 3, 4, 5, 6]`. Fix it without + changing the `before` list. ```python before = [[1, 2], [3, 4], [5, 6]] @@ -459,17 +462,45 @@ while True: print("their sum is", result) ``` -The answers are [here](answers.md#loops) +4. This program is supposed to print `[1, 2, 3]`. Fix it. + + ```python + numbers = ['1', '2', '3'] + for number in numbers: + 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 88342ad..59159cd 100644 --- a/basics/modules.md +++ b/basics/modules.md @@ -1,7 +1,7 @@ # Modules -Let's say we want to generate a random number between 1 and -3. The random module is a really easy way to do this: +Let's say we want to generate a random number between 1 and 3. +The random module is a really easy way to do this: ```python >>> import random @@ -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. @@ -49,10 +49,15 @@ that opens: ![My Python's modules.](../images/modules.png) All of these `.py` files can be imported like we just imported -`random.py`. In random.py, there's a like like `randint = something`, +`random.py`. In random.py, there's a line like `randint = something`, 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: @@ -76,11 +81,13 @@ AttributeError: 'module' object has no attribute 'randint' But what was that? Why didn't it work? +**TODO:** update the `-i` instructions. + Let's go ahead and check what's wrong. If you don't use IDLE, you'll need to pass the `-i` option to Python, so if you would normally run -`python3 random.py` you should now do `python3 -i random.py`. This -will run the file and then give you a `>>>` prompt that we can use -to check what's wrong. If you use IDLE, just run the file normally. +`python3 random.py` you should now do `python3 -i random.py`. This will +run the file and then give you a `>>>` prompt that we can use to check +what's wrong. If you use IDLE, just run the file normally. We should end up with the same error message, and then a `>>>`. Like this: @@ -95,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 @@ -131,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'] >>> ``` @@ -211,12 +218,11 @@ The module name "sys" is short for "system", and it contains things that are built into Python. The official documentation is [here](https://docs.python.org/3/library/sys.html). +`sys.stdin`, `sys.stdout` and `sys.stderr` are [file objects](files.md), +just like the file objects that `open()` gives us. + ```python >>> import sys ->>> # special files that the print and input functions use ->>> # stdin is short for standard input ->>> # stdout is short for standard output ->>> # stderr is short for standard errors >>> print("Hello!", file=sys.stdout) # this is where prints go by default Hello! >>> print("Hello!", file=sys.stderr) # use this for error messages @@ -228,15 +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 ``` -If you use IDLE you'll notice that printing to `sys.stderr` makes -the message show up in red instead of the normal blue. +**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 @@ -251,9 +256,6 @@ if something_went_wrong: sys.exit(1) ``` -`sys.exit` doesn't work for getting out of IDLE's `>>>` prompt. You can -just close the window to get out of IDLE. - ### Mathematics There's no math.py anywhere, math is a built-in module like @@ -288,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' >>> ``` @@ -367,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 @@ -379,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...") ``` @@ -408,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: ") @@ -459,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 @@ -485,15 +484,18 @@ 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 [LICENSE](../LICENSE). -[Previous](exceptions.md) | [Next](classes.md) | +[Previous](files.md) | [Next](exceptions.md) | [List of contents](../README.md#basics) 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 1470b39..c8255ff 100644 --- a/basics/using-functions.md +++ b/basics/using-functions.md @@ -205,11 +205,11 @@ TypeError: 'str' object is not callable >>> ``` -The error message complains that strings aren't callable because we -just set `print` to the string `'hello'` and now we're trying to call -it like a function. As you can see, **this is not a good idea** at all. -Most editors (including IDLE) display built-in functions with a special -color so you don't need to worry about doing this accidentally. +The error message complains that strings aren't callable because we just +set `print` to the string `'hello'` and now we're trying to call it like +a function. As you can see, **this is not a good idea** at all. Most +[editors](editor-setup.md) display built-in functions with a special +color, so you don't need to worry about doing this accidentally. Exit out of Python and start it again, and `print("Hello World!")` should work normally. @@ -228,13 +228,14 @@ 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 [LICENSE](../LICENSE). -[Previous](variables.md) | [Next](if.md) | +[Previous](variables.md) | [Next](editor-setup.md) | [List of contents](../README.md#basics) diff --git a/basics/variables.md b/basics/variables.md index 4d897c3..f1a8030 100644 --- a/basics/variables.md +++ b/basics/variables.md @@ -54,33 +54,22 @@ NameError: name 'thingy' is not defined >>> ``` -Variables are simple to understand, but there's a few details that we +Variables are simple to understand, but there are a few details that we need to keep in mind: - Variables always point to a value, **they never point to other - variables**. That's why the arrows in our diagrams always go left - to right. + variables**. That's why the arrows in our diagrams always go left + to right. - Multiple variables can point to the same value, but one variable - cannot point to multiple values. + cannot point to multiple values. - The values that variables point to can point to other values also. - We'll learn more about that when we'll talk about - [lists](lists-and-tuples.md). + We'll learn more about that when we'll talk about + [lists](lists-and-tuples.md). Variables are an important part of most programming languages, and they allow programmers to write much larger programs than they could write without variables. -Variable names can be multiple characters long. They can contain -uppercase characters, numbers and some other characters, but most of the -time we should use simple, lowercase variable names. You can also use -underscores. - -```python ->>> magic_number = 123 ->>> greeting = "Hello World!" ->>> -``` - Variable names are case-sensitive, like many other things in Python. ```python @@ -96,9 +85,10 @@ Variable names are case-sensitive, like many other things in Python. >>> ``` -Python also has some 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. +There are also words that cannot be used as variable names +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. @@ -158,6 +148,36 @@ variable called hello and then type hello: >>> ``` +## Good and bad variable names + +Variable names can be multiple characters long. They can contain +uppercase characters, numbers and some other characters, but most of the +time we should use simple, lowercase variable names. We can also use +underscores. For example, these variable names are good: + +```python +>>> magic_number = 123 +>>> greeting = "Hello World!" +>>> +``` + +Don't use variable names like this, **these variables are _bad_**: + +```python +>>> magicNumber = 3.14 # looks weird +>>> Greeting = "Hello there!" # also looks weird +>>> x = "Hello again!" # what the heck is x? +>>> +``` + +All of these variables work just fine, but other Python programmers +don't want you to use them. Most Python code doesn't use variable names +that contain UpperCase letters like `magicNumber` and `Greeting`, so +other people reading your code will think it looks weird if you use +them. The problem with `x` is that it's too short, and people have no +idea what it is. Remember that mathematicians like figuring out what x +is, but programmers hate that. + ## Booleans There are two Boolean values, True and False. In Python, and in many @@ -224,7 +244,8 @@ None Another confusing thing is that if we do something weird to None we get error messages that talk about NoneType object. The NoneType object they -are talking about is always None. +are talking about is always None. We'll learn more about what attributes +and calling are later. ```python >>> None.hello # None has no attribute 'hello' @@ -240,9 +261,8 @@ TypeError: 'NoneType' object is not callable ## Other comparing operators -So far we've used `==`, but there are other operators also. At this -point, this list probably looks awfully long, but it's actually pretty -easy to learn. +So far we've used `==`, but there are other operators also. This list +probably looks awfully long, but it's actually quite easy to learn. | Usage | Description | True examples | |-----------|-----------------------------------|-----------------------| @@ -261,20 +281,30 @@ b are Booleans. | `a and b` | a is True and b is True | `1 == 1 and 2 == 2` | | `a or b` | a is True, b is True or they're both True | `False or 1 == 1`, `True or True` | -Another way to combine operations is chaining. For example, `a < b < c` -does the same thing as `a < b and b < c`. - `not` can be used for negations. If `value` is True, `not value` is False, and if `value` is False, `not value` is True. There's also `is`, but don't use it instead of `==` unless you know what you are doing. We'll learn more about it later. +## Summary + +- Variables have a name and a value. We can create or change variables + with `name = value`. +- `thing += stuff` does the same thing as `thing = thing + stuff`. +- Use lowercase variable names and remember that programmers hate + figuring out what x is. +- `=` means assigning and `==` means comparing. +- True and False are Booleans. Comparing values results in a Boolean. +- None is a value that we'll find useful later. When error messages say + `NoneType object` they mean None. + *** -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 305bce9..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 @@ -148,16 +150,17 @@ rest of this tutorial only if you don't understand the code. as needed. - Make sure you understand everything you read. - Experiment with things freely and don't fear mistakes. -- Error messages are your friends. +- Error messages are our friends. - Let me know if you have trouble with this tutorial. - Now we're ready to [install Python](installing-python.md) and [get started](getting-started.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/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 8d1778a..0306c61 100644 --- a/classes.md +++ b/classes.md @@ -1 +1,14 @@ 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, +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). + +[List of contents](./README.md#list-of-contents) diff --git a/common.py b/common.py index d207e27..7180739 100644 --- a/common.py +++ b/common.py @@ -34,11 +34,13 @@ import contextlib import itertools import os +import posixpath import re import shutil +import string -_LINK_REGEX = r'\[(.*?)\]\((.*?)\)' +_LINK_REGEX = r'!?\[(.*?)\]\((.*?)\)' def find_links(file): @@ -67,17 +69,33 @@ def find_links(file): def get_markdown_files(): """Yield the names of all markdown files in this tutorial. - This assumes that the README contains links to everything. The - yielded paths use / as the path separator. + The yielded paths use / as the path separator. """ - yield 'README.md' - with open('README.md', 'r') as f: - for match, lineno in find_links(f): - target = match.group(2) - # Currently the README doesn't link to itself, but I don't - # want to break things if it will in the future. - if target.endswith('.md') and target != 'README.md': - yield target + for root, dirs, files in os.walk('.'): + for file in files: + if not file.endswith('.md'): + continue + path = os.path.normpath(os.path.join(root, file)) + yield path.replace(os.sep, '/') + + +def header_link(title): + """Return a github-style link target for a title. + + >>> header_link('Hello there!') + 'hello-there' + """ + # This doesn't do the-title-1, the-title-2 etc. with multiple titles + # with same text, but usually this doesn't matter. + result = '' + for character in title: + if character in string.whitespace: + result += '-' + elif character in string.punctuation: + pass + else: + result += character.lower() + return result def askyesno(question, default=True): @@ -92,6 +110,7 @@ def askyesno(question, default=True): else: # no by default question += ' [y/N] ' + while True: result = input(question).upper().strip() if result == 'Y': @@ -100,16 +119,7 @@ def askyesno(question, default=True): return False if not result: return default - - -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) + print("Please type y, n or nothing at all.") @contextlib.contextmanager @@ -124,3 +134,23 @@ def backup(filename): else: # Everything's fine, we can safely get rid of the backup. os.remove(filename + '.backup') + + +def header_link(title): + """Return a github-style link target for a title. + + >>> header_link('Hello there!') + 'hello-there' + """ + # This doesn't handle multiple titles with the same text in the + # same file, but usually that's not a problem. GitHub makes + # links like the-title, the-title-1, the-title-2 etc. + result = '' + for character in title: + if character in string.whitespace: + result += '-' + elif character in string.punctuation: + pass + else: + result += character.lower() + return result 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/editor-setup.md b/editor-setup.md deleted file mode 100644 index 019f894..0000000 --- a/editor-setup.md +++ /dev/null @@ -1,150 +0,0 @@ -# Setting up an editor for programming - -Python comes with its IDLE, and you can use it in this tutorial. If you -don't like using it for some reason, you need [PowerShell, command -prompt or -terminal](basics/installing-python.md#if-you-like-working-with-powershell-command-prompt-or-terminal) -for trying out things. You also need an editor for writing code that -will be stored in files. - -If you use IDLE as your editor, **it comes with everything set up for -you, and you don't need to worry about setting up anything**. If you -don't, you probably need to change some settings to make your editor -suitable for Python use. - -Do **not** use word processors like Microsoft Word and LibreOffice -Writer for programming. They create their own files, but you need plain -text files for programming. - -Start by creating an empty file called `hello.py` and opening it with -your editor. Or just open your editor and save a file as `hello.py`. - -## Automatic tab expanding - -This is important. Never use tabs in Python. Nobody else is using tabs, -and the official style guide tells you to never use tabs. - -However, **you don't need to press the spacebar four times every time -you want to indent**. Your editor should give you four spaces when you -hit the tab key. Some editors also remove four spaces when you hit -backspace and there are four spaces before the cursor. - -### Geany - -1. Go to *Edit* at the top and select Preferences. -2. Go to *Editor* at left. -2. Go to *Indenting* at top. -4. Select *Spaces* instead of *Tabs*. - -### gedit and pluma - -1. Go to Edit at the top and select Preferences. -2. Go to Editor at top. -3. Change the indent width to 4 and select *Add spaces instead of tabs*. - -### GNU Emacs - -Emacs uses spaces with Python files by default. - -### GNU Nano - -Open your `~/.nanorc`. - - $ nano ~/.nanorc - -Add these lines to it: - - set tabsize 4 - set tabstospaces - -### Mousepad - -1. Go to *Document* at the top, then *Tab Size*. -2. Select 4. -3. Also select *Insert Spaces*. - -## Syntax highlighting - -If you type a keyword, like `if`, it should show up with a different -color than the rest of your text. `"Strings"`, `# comments` and -everything else should also have their own colors. This makes it much -easier to write code. - -Most of the editors below have syntax highlighting turned on by -default, but you can also change the colors. - -### Geany - -Install more [color schemes](https://www.geany.org/Download/Extras#colors), -then go to *View*, *Change Color Scheme*. - -### gedit and pluma - -Click *Fonts & Colors* in the preferences and select another color -theme. - -### GNU Emacs - -Type M-x, type `load-theme`, press Tab twice to see a list of theme -names, then enter a theme name and press Enter. If you want to -automatically set the theme when Emacs starts, add -`(load-theme 'your-theme-name)` to your `~/.emacs`. - -### Mousepad - -Click *View*, go to *Color Scheme* and select whatever you want. - -## Is your editor using Python 3? - -Some editors allow you to run your programs with a single keystroke, -usually by pressing F5. This tutorial is written for Python 3 or newer, -so your editor also needs to run the programs in Python 3 or newer. - -If you are unsure which Python your editor runs, create a test file -with the following contents: - -```python -import sys -print(sys.version) -``` - -If the version starts with 2, it's too old. - -### Geany - -1. Go to *Build*, then *Set Build Commands*. -2. Replace `python` or `python2` with `python3` everywhere. Or if you - are using Windows, run `python` on a terminal and enter these - commands: - - ```python - >>> import sys - >>> print(sys.executable) - ``` - - You'll get a path to your python.exe. Replace `python` in the build - commands with this path. Most importantly, your *Execute* command - should be `"C:\your\path" "%f"`. - -### gedit, pluma and Mousepad - -These editors don't support running programs with F5. - -### GNU Emacs - -Usually I write something in Emacs, then I press Ctrl+Z to suspend -Emacs, run the program myself and then I run `fg` to get back to Emacs. -If you know how to run Python programs in Emacs and you'd like to write -about it here, [tell me](contact-me.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). - -[List of contents](./README.md#list-of-contents) 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 new file mode 100644 index 0000000..b9524a9 --- /dev/null +++ b/html-style.css @@ -0,0 +1,10 @@ +/* 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-style option. */ +body { + color: white; + 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/images/geany.png b/images/geany.png new file mode 100644 index 0000000..04896d1 Binary files /dev/null and b/images/geany.png differ diff --git a/images/powershell.png b/images/powershell.png new file mode 100644 index 0000000..72aaf6a Binary files /dev/null and b/images/powershell.png differ diff --git a/images/py-exe.png b/images/py-exe.png new file mode 100644 index 0000000..bce75f2 Binary files /dev/null and b/images/py-exe.png differ diff --git a/images/terminal.png b/images/terminal.png new file mode 100644 index 0000000..da293f1 Binary files /dev/null and b/images/terminal.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 315177a..b40ec3a 100755 --- a/make-html.py +++ b/make-html.py @@ -28,31 +28,54 @@ """Create HTML files of the tutorial.""" +import argparse import os +import platform import posixpath import shutil -import string import sys +import textwrap import webbrowser -import common +if platform.system() == 'Windows': + python = 'py' +else: + python = 'python3' try: import mistune except ImportError: - print("mistune isn't installed. You can install it like this:") + print("mistune isn't installed.", file=sys.stderr) + 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 +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 = """\ @@ -60,6 +83,7 @@ {title} + {body} @@ -68,68 +92,39 @@ """ -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): - if posixpath.basename(filename) == 'README.md': - # 'README.md' -> 'index.html' - # 'some/place/README.md' -> 'some/place/index.html' - return filename[:-9] + 'index.html' + renames = [('README.md', 'index.html'), + ('LICENSE', 'LICENSE.txt')] + for before, after in renames: + if posixpath.basename(filename) == before: + # BEFORE -> AFTER + # some/place/BEFORE -> some/place/AFTER + return filename[:-len(before)] + after if filename.endswith('.md'): - return filename[:-3] + '.html' + filename = filename[:-3] + '.html' return filename -class TutorialRenderer(mistune.Renderer): +class TutorialRenderer(mistune.HTMLRenderer): - def __init__(self): + def __init__(self, pygments_style): super().__init__() + self.pygments_style = pygments_style self.title = None # will be set by header() - self._headercounts = {} - - def _get_header_link(self, title): - """Return a github-style link target for a title. - - >>> r = TutorialRenderer() - >>> r._get_header_link('Hello there!') - 'hello-there' - >>> r._get_header_link('Hello there!') - 'hello-there-1' - >>> r._get_header_link('Hello there!') - 'hello-there-2' - >>> - """ - result = '' - for character in title: - if character in string.whitespace: - result += '-' - elif character in string.punctuation: - pass - else: - result += character.lower() - - if result not in self._headercounts: - # this title appears in this file for the first time - self._headercounts[result] = 1 - return result - # there has been already a link with the same text on this page, - # we need to do thetitle, thetitle-1, thetitle-2, etc. - real_result = '%s-%d' % (result, self._headercounts[result]) - self._headercounts[result] += 1 - return real_result def header(self, text, level, raw): """Create a header that is also a link and a # link target.""" # "# raw" if level == 1: self.title = text - target = self._get_header_link(raw) + target = common.header_link(raw) content = super().header(text, level, raw) return '{1}'.format(target, content) @@ -153,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='tango', noclasses=True) + 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.""" @@ -173,51 +189,96 @@ def table(self, header, body): return result.replace('', '
', 1) +def wrap_text(text): + """Like textwrap.fill, but respects newlines.""" + result = [] + for part in text.split('\n'): + result.append(textwrap.fill(part)) + return '\n'.join(result) + + def main(): + desc = ("Create HTML files of the tutorial.\n\n" + "The files have light text on a dark background by " + "default, and you can edit html-style.css to change that.") + if pygments is not None: + desc += ( + " Editing the style file doesn't change the colors of the " + "code examples, but you can use the --pygments-style " + "option. Search for 'pygments style gallery' online or see " + "https://help.farbox.com/pygments.html to get an idea of " + "what different styles look like.") + + parser = argparse.ArgumentParser( + description=wrap_text(desc), + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + '-o', '--outdir', default='html', + help="write the HTML files here, defaults to %(default)r") + if pygments is not None: + parser.add_argument( + '--pygments-style', metavar='STYLE', default=TutorialStyle, + choices=list(pygments.styles.get_all_styles()), + help=("the Pygments color style (see above), " + "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 in color.") + print("will not be colored.") if not common.askyesno("Continue without pygments?"): print("Interrupt.") return + args.pygments_style = None - if os.path.exists('html'): - if not common.askyesno("html exists. Do you want to remove it?"): + if os.path.exists(args.outdir): + if not common.askyesno("%s exists. Do you want to remove it?" + % args.outdir): print("Interrupt.") return - if os.path.isdir('html'): - shutil.rmtree('html') + if os.path.isdir(args.outdir): + shutil.rmtree(args.outdir) else: - os.remove('html') + os.remove(args.outdir) print("Generating HTML files...") for markdownfile in common.get_markdown_files(): - htmlfile = posixpath.join('html', fix_filename(markdownfile)) - print(' ', markdownfile, '->', htmlfile) - with common.slashfix_open(markdownfile, 'r') as f: + fixed_file = fix_filename(markdownfile) + htmlfile = posixpath.join(args.outdir, fixed_file) + print(' %-30.30s --> %-30.30s' % (markdownfile, htmlfile), end='\r') + + with open(markdownfile, 'r') as f: markdown = f.read() - renderer = TutorialRenderer() + renderer = TutorialRenderer(args.pygments_style) body = mistune.markdown(markdown, renderer=renderer) - html = HTML_TEMPLATE.format(title=renderer.title, body=body) - with mkdir_slashfix_open(htmlfile, 'w') as f: + stylefile = posixpath.relpath( + 'style.css', posixpath.dirname(fixed_file)) + + html = HTML_TEMPLATE.format( + title=renderer.title, + body=body, + stylefile=stylefile, + ) + with mkdir_and_open(htmlfile, 'w') as f: print(html, file=f) + print() print("Copying other files...") - shutil.copytree('images', os.path.join('html', 'images')) - shutil.copy('LICENSE', os.path.join('html', 'LICENSE')) + shutil.copytree('images', os.path.join(args.outdir, 'images')) + shutil.copy('LICENSE', os.path.join(args.outdir, 'LICENSE.txt')) + shutil.copy('html-style.css', os.path.join(args.outdir, 'style.css')) print("\n*********************\n") - print("Ready! The files are in the html directory.") - print("Go to html and double-click index.html to read the tutorial.") + print("Ready! The files are in %r." % args.outdir) + print("You can go there and double-click index.html to read the tutorial.") print() if common.askyesno("Do you want to view the tutorial now?", default=False): print("Opening the tutorial...") - webbrowser.open(os.path.join('html', 'index.html')) + webbrowser.open(os.path.join(args.outdir, 'index.html')) if __name__ == '__main__': diff --git a/strip.py b/strip.py deleted file mode 100755 index c4888a7..0000000 --- a/strip.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 - -# This is free and unencumbered software released into the public -# domain. - -# Anyone is free to copy, modify, publish, use, compile, sell, or -# distribute this software, either in source code form or as a -# compiled binary, for any purpose, commercial or non-commercial, and -# by any means. - -# In jurisdictions that recognize copyright laws, the author or -# authors of this software dedicate any and all copyright interest in -# the software to the public domain. We make this dedication for the -# benefit of the public at large and to the detriment of our heirs -# and successors. We intend this dedication to be an overt act of -# relinquishment in perpetuity of all present and future rights to -# this software under copyright law. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY -# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -# For more information, please refer to - -"""Strip trailing whitespace from markdown files.""" - -import common - - -def fix(line): - return line.rstrip().expandtabs(4) - - -def needs_stripping(file): - with common.slashfix_open(file, 'r') as f: - for line in f: - line = line.rstrip('\n') - if line != fix(line): - return True - return False - - -def strip(file): - real_file = common.slashfix(file) - lines = [] - with open(real_file, 'r') as f: - for line in f: - lines.append(fix(line.rstrip('\n'))) - with common.backup(real_file): - with open(real_file, 'w') as f: - for line in lines: - print(line, file=f) - - -def main(): - for file in common.get_markdown_files(): - if needs_stripping(file): - print("Stripping", file, "...") - strip(file) - else: - print("No trailing whitespace or tabs in", file) - - -if __name__ == '__main__': - main() 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/update-readmes.py b/update-readmes.py new file mode 100755 index 0000000..1934dbb --- /dev/null +++ b/update-readmes.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 + +# This is free and unencumbered software released into the public +# domain. + +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a +# compiled binary, for any purpose, commercial or non-commercial, and +# by any means. + +# In jurisdictions that recognize copyright laws, the author or +# authors of this software dedicate any and all copyright interest in +# the software to the public domain. We make this dedication for the +# benefit of the public at large and to the detriment of our heirs +# and successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to +# this software under copyright law. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# For more information, please refer to + +"""Generate basics/README.md and advanced/README.md.""" + +import os +import posixpath + +import common + + +BEGINNING = """\ +[comment]: # (This file is automatically generated. Don't edit this) +[comment]: # (file manually, run update-readmes.py instead.) + +""" + + +def get_contents(): + """Read descriptions and contents lists from README. + + Return a {chaptername: content} dictionary. + """ + result = {} + current_section = None + + with open('README.md', 'r') as f: + # move to where the content list starts + while f.readline().strip() != "## List of contents": + pass + + for line in f: + if line.startswith('### '): + # new section + current_section = common.header_link(line.lstrip('#').strip()) + result[current_section] = line[2:] # one # instead of 3 + elif line.startswith('## '): + # end of content lists + break + elif current_section is not None: + # we are currently in a section + result[current_section] += line + + return result + + +def update_file(filename, content): + """Make sure that a file contains the content. + + Return True if the file changed and False if it didn't. + """ + try: + with open(filename, 'r') as f: + # ignore the end + old_content = f.read().split('\n***\n')[0].rstrip() + if old_content == content: + print("Has correct content:", filename) + return False + except FileNotFoundError: + # the file doesn't exist yet, we'll create it + pass + + print("Writing new content:", filename) + with open(filename, 'w') as f: + print(content, file=f) + return True + + +def main(): + something_changed = False + for directory, content in sorted(get_contents().items()): + if not os.path.exists(directory): + # something else under the list of contents than a chapter + # list, doesn't have a separate subdirectory + print("Not a directory:", directory) + continue + + # the links that point to the subdir must now point to the + # current directory, so we fix that + content = BEGINNING + content.replace(directory + '/', '').rstrip() + path = os.path.join(directory, 'README.md') + this_changed = update_file(path, content) + something_changed = something_changed or this_changed + + if something_changed: + print() + print("Run update-ends.py now so the files will have correct ends.") + + +if __name__ == '__main__': + main() diff --git a/what-next.md b/what-next.md index ffe1a86..8437d95 100644 --- a/what-next.md +++ b/what-next.md @@ -28,3 +28,16 @@ is a way to create generators | `from stuff import *` | imports everything ## Fun modules + +*** + +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). + +[List of contents](./README.md#list-of-contents)