diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..196ae35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# generated html files +html/ + +# python cache files +__pycache__/ diff --git a/LICENSE b/LICENSE index 24463d7..93baa59 100644 --- a/LICENSE +++ b/LICENSE @@ -1,26 +1,49 @@ - This is a slightly modified version of the MIT license. - You can read the original license here: +The instructions and text in this tutorial (the "software") are licensed +under the zlib License. - https://opensource.org/licenses/MIT + (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 + arising from the use of this software. + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: -Copyright (c) 2016 Akuli + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. -Permission is hereby granted, free of charge, to any person obtaining a -copy of this tutorial and associated code examples (the “Tutorial”), to -deal in the Tutorial without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Tutorial, and to permit persons to whom the -Tutorial is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Tutorial. -THE TUTORIAL 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 OR COPYRIGHT HOLDERS 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 -TUTORIAL OR THE USE OR OTHER DEALINGS IN THE TUTORIAL. +The code examples (the "software") are unlicensed: + + 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 diff --git a/README.md b/README.md index fb41aff..f8e350d 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,121 @@ -# Python programming tutorial - -[Python](https://en.wikipedia.org/wiki/Python_\(programming_language\)) -is a high-level, interactive and object-oriented programming language. -Its simple syntax makes it easy to learn and fast to work with, so it's -a great choice for a first programming language. - -No tutorial is good for everyone. This one 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 -probably want to read -[the official tutorial](https://docs.python.org/3/tutorial/) instead. - -This tutorial uses Python 3. Python 2 is getting outdated all the time, -and more and more projects are moving to Python 3. There are a few -popular libraries that don't support Python 3 that well at the time of -writing this, but you don't need to worry about that just yet. They -will probably support Python 3 by the time you've learned the basics -and you may actually need them. - -Here's a list of chapters in this tutorial. Read them one by one in the -order they are listed. **If you jump to a chapter without reading -everything before it, you will probably have hard time understanding -it.** - -1. [Quick introduction to this tutorial](introduction.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. [If, else and elif](if.md) -8. [ThinkPython: Lists](lists.md) -9. [Loops](loops.md) - -Other things this tutorial comes with: - -- [Getting help](getting-help.md) +# Python programming tutorial for beginners + +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.6 or any newer Python with this tutorial. **Don't +use Python 2 because it's no longer supported.** + +## List of contents + +The tutorial consists of two sections: + +### 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?](basics/what-is-programming.md) +2. [Installing Python](basics/installing-python.md) +3. [Getting started with Python](basics/getting-started.md) +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. [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. [Exceptions](basics/exceptions.md) +20. [Classes](basics/classes.md) +21. [Docstrings](basics/docstrings.md) + +### 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](advanced/datatypes.md) +2. [Advanced stuff with functions](advanced/functions.md) +3. [Magic methods](advanced/magicmethods.md) +4. [Iterables, iterators and generators](advanced/iters.md) + +### Other things this tutorial comes with + +- **Important:** [getting help](getting-help.md) - [Contact me](contact-me.md) -- [Setting up a text editor](editor-setup.md) -- [Answers for the exercises](answers.md) +- Answers for exercises in [basics](basics/answers.md) and + [advanced](advanced/answers.md) sections +- [The TODO list](TODO.md) + +## Frequently asked questions + +### How can I thank you for writing and sharing this tutorial? + +You can star this tutorial. Starring is free for you, but it tells me +and other people that you like this tutorial. + +Go [here](https://github.com/Akuli/python-tutorial) if you aren't here +already and click the "Star" button in the top right corner. You will be +asked to create a GitHub account if you don't already have one. + +### How can I read this tutorial without an Internet connection? + +1. Go [here](https://github.com/Akuli/python-tutorial) if you aren't + here already. +2. Click the big green "Clone or download" button in the top right of + the page, then click "Download ZIP". -I'm Akuli and I have written most of this tutorial, but the following -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. + ![Download ZIP](images/download-me.png) + +3. Extract the ZIP and open it. Unfortunately I don't have any more + specific instructions because how exactly this is done depends on + which operating system you run. +4. Run `make-html.py` and follow the instructions. + +If you have git and you know how to use it, you can also clone the +repository instead of downloading a zip and extracting it. An advantage +with doing it this way is that you don't need to download the whole +tutorial again to get the latest version of it, all you need to do is to +pull with git and run `make-html.py` again. + +## Authors + +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. *** -You may use this tutorial at your own risk. See [LICENSE](LICENSE). +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/TODO.md b/TODO.md new file mode 100644 index 0000000..ff1ecde --- /dev/null +++ b/TODO.md @@ -0,0 +1,52 @@ +# Stuff that will (hopefully) be added to this tutorial + +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 +- classes part 1: + + Akuli: I would probably add an example of refactoring + a bunch of functions working with shared global data + into a class because it's a pretty typical usecase + +- classes part 2 + - non-public `_variables` + - "singular" inheritance, inheritance of built-in classes + - using super + - 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) + - pyqt5 in zetcode (warn about mixedCase) + - gtk+ 3 in readthedocs + - a pygame tutorial + - David Beazley's metaprogramming and other talks + - "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, +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/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 new file mode 100644 index 0000000..ca52c7f --- /dev/null +++ b/advanced/answers.md @@ -0,0 +1,13 @@ + +*** + +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/datatypes.md b/advanced/datatypes.md new file mode 100644 index 0000000..456d777 --- /dev/null +++ b/advanced/datatypes.md @@ -0,0 +1,360 @@ +# Handy data types in the standard library + +[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 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 +types can do a lot of the work for you. + +> If it looks like a duck and quacks like a duck, it must be a duck. + +Many things in this tutorial are not really something but they behave +like something. For example, we'll learn about many classes that behave +like dictionaries. They are not dictionaries, but we can use them just +like if they were dictionaries. This programming style is known as +**duck-typing**. + +## Sets + +Let's say we have a program that keeps track of peoples' names. We can +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'] +>>> names.append('Akuli') +>>> names.append('Akuli') +>>> names +['wub_wub', 'theelous3', 'RubyPinch', 'go|dfish', 'Nitori', 'Akuli', 'Akuli'] +>>> +``` + +This is usually what we need, but sometimes it's not. Sometimes we just +want to store a bunch of things. We don't need to have the same thing +twice and we don't care about the order. + +This is when sets come in. They are like lists without order or +duplicates, or keys of [dictionaries](../basics/dicts.md) without the +values. We can create a set just like a dictionary, but without `:`. + +```python +>>> names = {'wub_wub', 'theelous3', 'RubyPinch', 'go|dfish', 'Nitori'} +>>> names +{'RubyPinch', 'theelous3', 'go|dfish', 'wub_wub', 'Nitori'} +>>> type(names) + +>>> 'wub_wub' in names +True +>>> +``` + +We can also convert anything [iterable](../basics/loops.md#summary) to a +set [by calling the +class](../basics/classes.md#what-are-classes). + +```python +>>> set('hello') +{'o', 'e', 'h', 'l'} +>>> type(set('hello')) + +>>> +``` + +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. + +Note that `{}` is a dictionary because dictionaries are used more often +than sets, so we need `set()` if we want to create an empty set. + +```python +>>> type({'a', 'b'}) + +>>> type({'a'}) + +>>> type({}) + +>>> type(set()) # set() is an empty set + +>>> +``` + +Sets have a `remove` method just like lists have, but they have an `add` +method instead of `append`. + +```python +>>> names = {'theelous3', 'wub_wub'} +>>> names.add('Akuli') +>>> names +{'wub_wub', 'Akuli', 'theelous3'} +>>> names.remove('theelous3') +>>> names +{'wub_wub', 'Akuli'} +>>> +``` + +That's the boring part. Now let's have a look at some really handy +things we can do with sets: + +```python +>>> a = {'RubyPinch', 'theelous3', 'go|dfish'} +>>> b = {'theelous3', 'Nitori'} +>>> a & b # names in a and b +{'theelous3'} +>>> a | b # names in a, b or both +{'Nitori', 'theelous3', 'go|dfish', 'RubyPinch'} +>>> a ^ b # names in a or b, but not both +{'RubyPinch', 'Nitori', 'go|dfish'} +>>> a - b # names in a but not in b +{'go|dfish', 'RubyPinch'} +>>> +``` + +## Named tuples + +It can be tempting to make a class that just contains a bunch of data +and that's it. + +```python +class Website: + + def __init__(self, url, founding_year, free_to_use): + self.url = url + self.founding_year = founding_year + self.free_to_use = free_to_use + + +github = Website('https://github.com/', 2008, True) +``` + +You should avoid making classes like this. This class has only one +method, so it doesn't really need to be a class. We could just use a +tuple instead: + +```python +github = ('https://github.com/', 2008, True) +``` + +The problem with this is that if someone reading our code sees something +like `website[1] > 2010` it doesn't make much sense, like +`website.founding_year > 2010` would. + +In cases like this, `collections.namedtuple` is handy: + +```python +>>> Website = collections.namedtuple('Website', 'url founding_year free_to_use') +>>> github = Website('https://github.com/', 2008, True) +>>> github[1] +2008 +>>> for thing in github: +... print(thing) +... +https://github.com/ +2008 +True +>>> github.founding_year +2008 +>>> github +Website(url='https://github.com/', founding_year=2008, free_to_use=True) +>>> +``` + +As you can see, our `github` behaves like a tuple, but things like +`github.founding_year` also work and `github` looks nice when we have a +look at it on the `>>>` prompt. + +## Deques + +To understand deques, we need to first learn about a list method I +haven't talked about earlier. It's called `pop` and it works like this: + +```python +>>> names = ['wub_wub', 'theelous3', 'Nitori', 'RubyPinch', 'go|dfish'] +>>> names +['wub_wub', 'theelous3', 'Nitori', 'RubyPinch', 'go|dfish'] +>>> names.pop() +'go|dfish' +>>> names +['wub_wub', 'theelous3', 'Nitori', 'RubyPinch'] +>>> names.pop() +'RubyPinch' +>>> names +['wub_wub', 'theelous3', 'Nitori'] +>>> +``` + +The list shortens from the end by one when we pop from it, and we also +get the removed item back. So we can add an item to the end of a list +using `append`, and we can remove an item from the end using `pop`. + +It's also possible to do these things in the beginning of a list, but +lists were not designed to be used that way and it would be slow if our +list would be big. The `collections.deque` class makes appending and +popping from both ends easy and fast. It works just like lists, but it +also has `appendleft` and `popleft` methods. + +```python +>>> names = collections.deque(['theelous3', 'Nitori', 'RubyPinch']) +>>> names +deque(['theelous3', 'Nitori', 'RubyPinch']) +>>> names.appendleft('wub_wub') +>>> names.append('go|dfish') +>>> names +deque(['wub_wub', 'theelous3', 'Nitori', 'RubyPinch', 'go|dfish']) +>>> names.popleft() +'wub_wub' +>>> names.pop() +'go|dfish' +>>> names +deque(['theelous3', 'Nitori', 'RubyPinch']) +>>> +``` + +The deque behaves a lot like lists do, and we can do `list(names)` if we +need a list instead of a deque for some reason. + +Deques are often used as queues. It means that items are always added to +one end and popped from the other end. + +## Counting things + +Back in [the dictionary chapter](../basics/dicts.md#examples) we learned +to count the number of words in a sentence like this: + +```python +sentence = input("Enter a sentence: ") +counts = {} +for word in sentence.split(): + if word in counts: + counts[word] += 1 + else: + counts[word] = 1 +``` + +This code works just fine, but there are easier ways to do this. For +example, we could use the `get` method. It works so that +`the_dict.get('hi', 'hello')` tries to give us `the_dict['hi']` but +gives us `'hello'` instead if `'hi'` is not in the dictionary. + +```python +>>> the_dict = {'hi': 'this is working'} +>>> the_dict.get('hi', 'lol its not there') +'this is working' +>>> the_dict.get('hello', 'lol its not there') +'lol its not there' +>>> +``` + +So we could write code like this instead: + +```python +sentence = input("Enter a sentence: ") +counts = {} +for word in sentence.split(): + counts[word] = counts.get(word, 0) + 1 +``` + +Counting things like this is actually so common that there's [a +class](../basics/classes.md) just for that. It's called +`collections.Counter` and it works like this: + +```python +>>> import collections +>>> words = ['hello', 'there', 'this', 'test', 'is', 'a', 'hello', 'test'] +>>> counts = collections.Counter(words) +>>> counts +Counter({'test': 2, 'hello': 2, 'is': 1, 'this': 1, 'there': 1, 'a': 1}) +>>> +``` + +Now `counts` is a Counter object. It behaves a lot like a dictionary, +and everything that works with a dictionary should also work with a +counter. We can also convert the counter to a dictionary by doing +`dict(the_counter)` if something doesn't work with a counter. + +```python +>>> for word, count in counts.items(): +... print(word, count) +... +test 2 +is 1 +this 1 +there 1 +a 1 +hello 2 +>>> +``` + +## Combining dictionaries + +We can add together strings, lists, tuples and sets easily. + +```python +>>> "hello" + "world" +'helloworld' +>>> [1, 2, 3] + [4, 5] +[1, 2, 3, 4, 5] +>>> (1, 2, 3) + (4, 5) +(1, 2, 3, 4, 5) +>>> {1, 2, 3} | {4, 5} +{1, 2, 3, 4, 5} +>>> +``` + +But how about dictionaries? They can't be added together with `+`. + +```python +>>> {'a': 1, 'b': 2} + {'c': 3} +Traceback (most recent call last): + File "", line 1, in +TypeError: unsupported operand type(s) for +: 'dict' and 'dict' +>>> +``` + +Usually it's easiest to do this: + +```python +>>> dict1 = {'a': 1, 'b': 2} +>>> dict2 = {'c': 3} +>>> {**dict1, **dict2} +{'a': 1, 'b': 2, 'c': 3} +``` + +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 +>>> merged = {} +>>> merged.update({'a': 1, 'b': 2}) +>>> merged.update({'c': 3}) +>>> merged +{'a': 1, 'b': 2, 'c': 3} +>>> +``` + +## Summary + +- Duck typing means requiring some behavior instead of some type. For + example, instead of making a function that takes a list we could make + a function that takes anything [iterable](../basics/loops.md#summary). +- Sets and the collections module are handy. Use them. + +*** + +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/docstrings.md) | [Next](functions.md) | +[List of contents](../README.md#advanced) diff --git a/advanced/functions.md b/advanced/functions.md new file mode 100644 index 0000000..8f54e60 --- /dev/null +++ b/advanced/functions.md @@ -0,0 +1,303 @@ +# Advanced things with functions + +Now we know [how to define functions](../basics/defining-functions.md). +Functions can take arguments, and they will end up with local variables +that have the same name. Like this: + +```python +def print_box(message, border='*'): + print(border * (len(message) + 4)) + print(border, message, border) + print(border * (len(message) + 4)) + +print_box("hello") +``` + +In this chapter we'll learn more things we can do with defining +functions and how they are useful. + +## Multiple return values + +Function can take multiple arguments, but they can only return one +value. But sometimes it makes sense to return multiple values as well: + +```python +def login(): + username = input("Username: ") + password = input("Password: ") + # how the heck are we going to return these? +``` + +The best solution is to return a tuple of values, and just unpack that +wherever the function is called: + +```python +def login(): + ... + return (username, password) + + +username, password = login() +... +``` + +That gets kind of messy if there are more than three values to return, +but I have never needed to return more than three values. If you think +you need to return four or more values you probably want to use [a +class](../basics/classes.md) instead. + +For example, instead of this... + +```python +def get_new_info(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) +``` + +...you could do this: + +```python +class User: + # you probably want to make many other user related things too, add + # them here + + def change_info(self): + print(f"Changing user information of {self.username}.") + self.username = input("New username: ") + self.password = input("New password: ") + self.fullname = input("Full name: ") + self.phonenumber = input("Phone number: ") +``` + +## \*args + +Sometimes you might see code like this: + +```python +def thing(*args, **kwargs): + ... +``` + +Functions like this are actually quite easy to understand. Let's make a +function that takes `*args` and prints it. + +```python +>>> def thing(*args): +... print("now args is", args) +... +>>> thing() +now args is () +>>> thing(1, 2, 3) +now args is (1, 2, 3) +>>> +``` + +So far we have learned that if we want to call a function like +`thing(1, 2, 3)`, then we need to define the arguments when defining the +function like `def thing(a, b, c)`. But `*args` just magically gets +whatever positional arguments the function is given and turns them into +a tuple, and never raises errors. Of course, we could also use whatever +variable name we wanted instead of `args`. + +Our function with nothing but `*args` takes no keyword arguments: + +```python +>>> thing(a=1) +Traceback (most recent call last): + File "", line 1, in +TypeError: thing() got an unexpected keyword argument 'a' +>>> +``` + +We can also save our arguments to a variable as a list, and then pass +them to a function by adding a `*`. Actually it doesn't need to be a +list or a tuple, anything [iterable](../basics/loops.md#summary) will +work. + +```python +>>> stuff = ['hello', 'world', 'test'] +>>> print(*stuff) +hello world test +>>> +``` + +## \*\*kwargs + +`**kwargs` is the same thing as `*args`, but with keyword arguments +instead of positional arguments. + +```python +>>> def thing(**kwargs): +... print('now kwargs is', kwargs) +... +>>> thing(a=1, b=2) +now kwargs is {'b': 2, 'a': 1} +>>> thing(1, 2) +Traceback (most recent call last): + File "", line 1, in +TypeError: thing() takes 0 positional arguments but 2 were given +>>> def print_box(message, border): +... print(border * len(message)) +... print(message) +... print(border * len(message)) +... +>>> kwargs = {'message': "Hello World!", 'border': '*'} +>>> print_box(**kwargs) +************ +Hello World! +************ +>>> +``` + +Sometimes it's handy to capture all arguments our function takes. We can +combine `*args` and `**kwargs` easily: + +```python +>>> def thing(*args, **kwargs): +... print("now args is", args, "and kwargs is", kwargs) +... +>>> thing(1, 2, a=3, b=4) +now args is (1, 2) and kwargs is {'b': 4, 'a': 3} +>>> +``` + +This is often used for calling a function from another "fake function" +that represents it. We'll find uses for this later. + +```python +>>> def fake_print(*args, **kwargs): +... print(*args, **kwargs) +... +>>> print('this', 'is', 'a', 'test', sep='-') +this-is-a-test +>>> fake_print('this', 'is', 'a', 'test', sep='-') +this-is-a-test +>>> +``` + +## Keyword-only arguments + +Let's say that we have a function that moves a file. It probably takes +`source` and `destination` arguments, but it might also take other +arguments that customize how it moves the file. For example, it might +take an `overwrite` argument that makes it remove `destination` before +moving if it exists already or a `backup` argument that makes it do a +backup of the file just in case the moving fails. So our function would +look like this: + +```python +def move(source, destination, overwrite=False, backup=False): + if overwrite: + print("deleting", destination) + if backup: + print("backing up") + print("moving", source, "to", destination) +``` + +Then we can move files like this: + +```python +>>> move('file1.txt', 'file2.txt') +moving file1.txt to file2.txt +>>> move('file1.txt', 'file2.txt', overwrite=True) +deleting file2.txt +moving file1.txt to file2.txt +>>> +``` + +This works just fine, but if we accidentally give the function three +filenames, bad things will happen: + +```python +>>> move('file1.txt', 'file2.txt', 'file3.txt') +deleting file2.txt +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 `'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. + +The solution is to change our move function so that `overwrite` and +`backup` are keyword-only: + +```python +def move(source, destination, *, overwrite=False, backup=False): + ... +``` + +The `*` between `destination` and `overwrite` means that `overwrite` and +`backup` must be given as keyword arguments. The basic idea is really +simple: now it's impossible to overwrite by doing `move('file1.txt', +'file2.txt', True)` and the overwrite must be always given like +`overwrite=True`. + +```python +>>> move('file1.txt', 'file2.txt') +moving file1.txt to file2.txt +>>> move('file1.txt', 'file2.txt', True) +Traceback (most recent call last): + File "", line 1, in +TypeError: move() takes 2 positional arguments but 3 were given +>>> move('file1.txt', 'file2.txt', 'file3.txt') +Traceback (most recent call last): + File "", line 1, in +TypeError: move() takes 2 positional arguments but 3 were given +>>> move('file1.txt', 'file2.txt', overwrite='file3.txt') +deleting file2.txt +moving file1.txt to file2.txt +>>> +``` + +Doing `overwrite='file3.txt'` doesn't make much sense and it's easy to +notice that something's wrong. + +## When should we use these things? + +There's nothing wrong with returning a tuple from a function, and you +are free to do that whenever you need it. + +We don't need `*args` and `**kwargs` for most of the functions we write. +When we need to make something that takes whatever arguments it's given +or call a function with arguments that come from a list we need `*args` +and `**kwargs`, and there's no need to avoid them. + +I don't recommend using keyword-only arguments with functions like our +`print_box`. It's easy enough to guess or remember what +`print_box('hello', '-')` does, and there's no need to deny that. It's +much harder to remember what `move('file1.txt', 'file2.txt', True, False)` +does, so using keyword-only arguments makes sense. + +## Summary + +- If you want to return multiple values from a function you can return + a tuple. +- Defining a function that takes `*args` as an argument makes `args` a + tuple of positional arguments. `**kwargs` is the same thing with + dictionaries and keyword arguments. +- Adding a `*` in a function definition makes all arguments after it + keyword-only. This is useful when using positional arguments would + look implicit, like the True and False in + `move('file1.txt', 'file2.txt', True, False)`. + +*** + +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](datatypes.md) | [Next](magicmethods.md) | +[List of contents](../README.md#advanced) diff --git a/advanced/iters.md b/advanced/iters.md new file mode 100644 index 0000000..450e763 --- /dev/null +++ b/advanced/iters.md @@ -0,0 +1,476 @@ +# Iterables, iterators and generators + +So far we have used for loops with many different kinds of things. + +```python +>>> for name in ['theelous3', 'RubyPinch', 'go|dfish']: +... print(name) +... +theelous3 +RubyPinch +go|dfish +>>> for letter in 'abc': +... print(letter) +... +a +b +c +>>> +``` + +For looping over something is one way to **iterate** over it. Some other +things also iterate, for example, `' '.join(['a', 'b', 'c'])` iterates +over the list `['a', 'b', 'c']`. If we can iterate over something, then +that something is **iterable**. For example, strings and lists are +iterable, but integers and floats are not. + +```python +>>> for thing in 123: +... print(thing) +... +Traceback (most recent call last): + File "", line 1, in +TypeError: 'int' object is not iterable +>>> +``` + +## Iterators + +Lists and strings don't change when we iterate over them. + +```python +>>> word = 'hi' +>>> for character in word: +... print(character) +... +h +i +>>> word +'hi' +>>> +``` + +We can also iterate over [files](../basics/files.md), but they remember +their position and we get the content once only if we iterate over them +twice. + +```python +>>> with open('test.txt', 'w') as f: +... print("one", file=f) +... print("two", file=f) +... +>>> a = [] +>>> b = [] +>>> with open('test.txt', 'r') as f: +... for line in f: +... a.append(line) +... for line in f: +... b.append(line) +... +>>> a +['one\n', 'two\n'] +>>> b +[] +>>> +``` + +We have also used [enumerate](../basics/zip-and-enumerate.md) +before, and it actually remembers its position also: + +```python +>>> e = enumerate('hello') +>>> for pair in e: +... print(pair) +... +(0, 'h') +(1, 'e') +(2, 'l') +(3, 'l') +(4, 'o') +>>> for pair in e: +... print(pair) +... +>>> +``` + +**Iterators are iterables that remember their position.** For example, +`open('test.txt', 'r')` and `enumerate('hello')` return iterators. +Iterators can only be used once, so we need to create a new iterator if +we want to do another for loop. + +All iterators are iterables, but not all iterables are iterators. Like +this: + +![Iterables and iterators.](../images/iters.png) + +## Iterating manually + +Iterators have a magic method called `__next__` that gets next value and +moves the iterator forward. + +```python +>>> e = enumerate('abc') +>>> e.__next__() +(0, 'a') +>>> e.__next__() +(1, 'b') +>>> e.__next__() +(2, 'c') +>>> +``` + +There's also a built-in `next()` function that does the same thing: + +```python +>>> e = enumerate('abc') +>>> next(e) +(0, 'a') +>>> next(e) +(1, 'b') +>>> next(e) +(2, 'c') +>>> +``` + +Here `e` remembers its position, and every time we call `next(e)` it +gives us the next element and moves forward. When it has no more values +to give us, calling `next(e)` raises a StopIteration: + +```python +>>> next(e) +Traceback (most recent call last): + File "", line 1, in +StopIteration +>>> +``` + +There is usually not a good way to check if the iterator is at the end, +and it's best to just try to get a value from it and +[catch](../basics/exceptions.md#catching-exceptions) StopIteration. + +That's actually what for loops do. For example, +this code... + +```python +for pair in enumerate('hello'): + print(pair) +``` + +...does roughly the same thing as this code: + +```python +e = enumerate('hello') +while True: + try: + pair = next(e) + except StopIteration: + # it's at the end, time to stop + break + # we got a pair + print(pair) +``` + +The for loop version is much simpler to write and I wrote the while loop +version just to show how the for loop works. + +## Converting to iterators + +Now we know what iterating over an iterator does. But how about +iterating over a list or a string? They are not iterators, so we can't +call `next()` on them: + +```python +>>> next('abc') +Traceback (most recent call last): + File "", line 1, in +TypeError: 'str' object is not an iterator +>>> +``` + +There's a built-in function called `iter()` that converts anything +iterable to an iterator. + +```python +>>> i = iter('abc') +>>> i + +>>> next(i) +'a' +>>> next(i) +'b' +>>> next(i) +'c' +>>> next(i) +Traceback (most recent call last): + File "", line 1, in +StopIteration +>>> +``` + +Calling `iter()` on anything non-iterable gives us an error. + +```python +>>> iter(123) +Traceback (most recent call last): + File "", line 1, in +TypeError: 'int' object is not iterable +>>> +``` + +If we try to convert an iterator to an iterator using `iter()` we just +get back the same iterator. + +```python +>>> e = enumerate('abc') +>>> iter(e) is e +True +>>> +``` + +So code like this... + +```python +for thing in stuff: + print(thing) +``` + +...works roughly like this: + +```python +iterator = iter(stuff) +while True: + try: + thing = next(iterator) + except StopIteration: + break + 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 +`__iter__` method that returns self and a `__next__` method that gets +the next value. I'm not going to talk about it now because there's a +much simpler way to implement iterators. Let's make a function that +creates an iterator that behaves like `iter([1, 2, 3])` using the +`yield` keyword: + +```python +>>> def thingy(): +... yield 1 +... yield 2 +... yield 3 +... +>>> +``` + +We can only `yield` inside a function, yielding elsewhere raises an +error. + +```python +>>> yield 'hi' + File "", line 1 +SyntaxError: 'yield' outside function +>>> +``` + +Let's try out our thingy function and see how it works. + +```python +>>> thingy() + +>>> +``` + +What the heck? We don't return anything from the function, but it still +doesn't return None! + +Putting a `yield` anywhere in a function makes it return **generators**. +**Generators are iterators** with some more features that we don't need +to care about. + +![Generators.](../images/generators.png) + +The generators that thingy gives us work just like other iterators: + +```python +>>> t = thingy() +>>> t + +>>> next(t) +1 +>>> next(t) +2 +>>> next(t) +3 +>>> next(t) +Traceback (most recent call last): + File "", line 1, in +StopIteration +>>> for number in thingy(): +... print(number) +... +1 +2 +3 +>>> +``` + +This whole thing may feel kind of insane. If we add some parts between +the yields, when do they run? How does Python know when to run what? + +Let's find out. + +```python +>>> def printygen(): +... print("starting") +... yield 1 +... print("between 1 and 2") +... yield 2 +... print("between 2 and 3") +... yield 3 +... print("end") +... +>>> p = printygen() +>>> +``` + +That's weird! We called it, but it didn't print "starting"! + +Let's see what happens if we call `next()` on it. + +```python +>>> got = next(p) +starting +>>> got +1 +>>> +``` + +Now it started, but it's frozen! It's just stuck on that `yield 1`. + +An easy way to think about this is to compare it to our computers. +When we suspend a computer it goes into some kind of stand-by mode, +and we can later continue using the computer all of our programs are +still there just like they were when we left. + +A similar thing happens here. Our function is running, but it's just +stuck at the yield and waiting for us to call `next()` on it again. + +```python +>>> next(p) +between 1 and 2 +2 +>>> next(p) +between 2 and 3 +3 +>>> next(p) +end +Traceback (most recent call last): + File "", line 1, in +StopIteration +>>> +``` + +Here's a drawing of what's going on: + +![A picture of printygen.](../images/freeze-melt.png) + +The good news is that **usually we don't need to worry about when +exactly the parts between the yields run**. Actually we don't even need +to use `iter()` and `next()` most of the time, but I think it's nice to +know how for loops work. + +`yield` is useful when we want the function to output so many things +that making a list of them would be too slow or the list wouldn't fit in +the computer's memory. So instead of this... + +```python +def get_things(): + result = [] + # code that appends things to result + return result +``` + +...we can do this: + +```python +def get_things(): + # code that yields stuff +``` + +Both of these functions can be used like this: + +```python +for thing in get_things(): + # do something with thing +``` + +It's actually possible to create an iterator that yields an infinite +number of things: + +```python +>>> def count(): +... current = 1 +... while True: +... yield current +... current += 1 +... +>>> c = count() +>>> next(c) +1 +>>> next(c) +2 +>>> next(c) +3 +>>> next(c) +4 +>>> +``` + +[The itertools module](https://docs.python.org/3/library/itertools.html) +contains many useful things like this. For example, `itertools.count(1)` +does the same thing as our `count()`. + +## Summary + +- An iterable is something that we can for loop over. +- An iterator is an iterable that remembers its position. +- For loops create an iterator of the iterable and call its `__next__` + method until it raises a StopIteration. +- Functions that contain yields return generators. Calling `next()` on a + 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, +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](magicmethods.md) | [Next](../README.md) | +[List of contents](../README.md#advanced) diff --git a/advanced/magicmethods.md b/advanced/magicmethods.md new file mode 100644 index 0000000..c333bbe --- /dev/null +++ b/advanced/magicmethods.md @@ -0,0 +1,243 @@ +# Magic methods + +In [the class tutorial](../basics/classes.md) we learned to define a +class like this: + +```python +class Website: + + def __init__(self, url, founding_year, free_to_use): + self.url = url + self.founding_year = founding_year + self.free_to_use = free_to_use + + def info(self): + print("URL:", self.url) + print("Founding year:", self.founding_year) + print("Free to use:", self.free_to_use) +``` + +After doing that we can create a new Website object like +`Website('https://github.com/', 2008, True)`. Python first creates the +Website object, and then calls `__init__` with the arguments we passed +to Website to set it up. Methods that have a name `__like_this__` and a +special meaning are called **magic methods** or **special methods**. + +Most magic methods define what the object has or what it can do, like +"does it have a length" or "can we for loop over it". There are other +magic methods that do other things also, like `__init__`. + +Some magic methods have a default implementation that is used if the +class doesn't define anything else. For example, if we don't define an +`__init__` then our class will take no arguments and it won't have any +attributes by default. We'll learn more about this when we'll talk about +[inheritance](classes2.md). + +**TODO:** write a `classes2.md`. + +## Custom length + +Let's get started by defining an object that has a length: + +```python +>>> class Thing: +... def __len__(self): +... return 5 +... +>>> t = Thing() +>>> t +<__main__.Thing object at 0x7f05e4597198> +>>> t.__len__() +5 +>>> len(t) +5 +>>> +``` + +This is what most magic methods are like. So far we have learned to use +`len()` with lists, strings and other built-in types, but now we can +call `len()` on our own Thing object. Many things can be fully +customized with magic methods. + +Note that magic methods like `__len__` need to be defined in the class, +just attaching an attribute called `__len__` doesn't work: + +```python +>>> class EmptyThing: +... pass +... +>>> def length(): +... return 5 +... +>>> e = EmptyThing() +>>> e.__len__ = length +>>> e.__len__() +5 +>>> len(e) +Traceback (most recent call last): + File "", line 1, in +TypeError: object of type 'EmptyThing' has no len() +>>> +``` + +You don't really need to worry about why Python works like this, but +it's explained +[here](https://docs.python.org/3/reference/datamodel.html#special-method-lookup) +if you want to know more about it. + +## String representations + +You have probably noticed that typing something to the interactive `>>>` +prompt is not quite the same thing as printing it. For example, +strings behave like this: + +```python +>>> 'hello' +'hello' +>>> print('hello') +hello +>>> +``` + +If you want to print something the way it's displayed on the `>>>` +prompt you can use the `repr()` function. Here "repr" is short for +"representation". + +```python +>>> message = 'hello' +>>> print("the message is", repr(message)) +the message is 'hello' +>>> +``` + +Combining `repr()` with [string +formatting](../basics/handy-stuff-strings.md#string-formatting) is also +easy. + +```python +>>> print(f"the message is {repr(message)}") +the message is 'hello' +>>> +``` + +The `__repr__` magic method can be used to customize this. For example, +we can do this: + +```python +>>> class Website: +... def __repr__(self): +... return '' +... +>>> w = Website() +>>> w.__repr__() +'' +>>> str(w) +'' +>>> print(w) + +>>> w + +>>> +``` + +The `__repr__` method can return any string, but usually you should +follow one of these styles: + +1. A piece of code that describes how another, similar object can be + created. + + ```python + >>> class Website: + ... def __init__(self, name, founding_year): + ... self.name = name + ... self.founding_year = founding_year + ... def __repr__(self): + ... return f'Website(name={repr(self.name)}, founding_year={repr(self.founding_year)})' + ... + >>> github = Website('GitHub', 2008) + >>> github + Website(name='GitHub', founding_year=2008) + >>> + ``` + + This is useful for simple data containers like this Website class. + +2. A description of the object wrapped between `<` and `>`. + + ```python + >>> class Website: + ... def __init__(self, name, founding_year): + ... self.name = name + ... self.founding_year = founding_year + ... def __repr__(self): + ... return f'' + ... + >>> github = Website('GitHub', 2008) + >>> github + + >>> + ``` + + This style is good when you want to tell more about the object than + you can by showing the `__init__` arguments. Python's built-in + things also use this style more: + + ```python + >>> import random + >>> random + + >>> + ``` + +## Other magic methods + +There are many more magic methods, and I don't see any reason to list +them all here. [The official +documentation](https://docs.python.org/3/reference/datamodel.html) has +more information about magic methods if you need it. We'll go through +using the most important magic methods in the rest of this tutorial, so +if you just keep reading you'll learn more about them. + +## When should we use magic methods? + +There's nothing wrong with using `__init__` everywhere, but other than +that, magic methods are usually not needed. `website.has_user(user)` and +`user in website.userlist` are way better than something weird that we +could do with magic methods like `user @ website`. People expect +`website.has_user(user)` check if a user has registered on the website, +but nobody can guess what `user @ website` does. Explicit is better than +implicit, and simple is better than complex. + +On the other hand, using magic methods when needed can turn something +good into something great. Especially the `__repr__` method is useful +because people can get a good idea of what an object is by just looking +at it on the `>>>` prompt or printing it. I recommend using `__repr__` +methods in things that other people will import and use in their +projects, but `__repr__` methods aren't worth it for simple scripts that +are not meant to be imported. + +## Summary + +- Magic methods define what instances of a class can do and how, like + "does it have a length" or "what does it look like when I print it". +- Python uses magic methods to implement many things internally, and we + can customize everything by implementing the magic methods + ourselves. +- Defining custom `__repr__` methods is often a good idea when making + things that other people will import and use in their own projects, + and the `__init__` method is very useful for many things. + Other than that, magic methods are usually not worth it. + +*** + +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](functions.md) | [Next](iters.md) | +[List of contents](../README.md#advanced) diff --git a/answers.md b/answers.md deleted file mode 100644 index a2fb54d..0000000 --- a/answers.md +++ /dev/null @@ -1,202 +0,0 @@ -# Answers - -These are answers for exercises in the chapters. In programming, there's always more than one way to do things, so if your solution wasn't exactly like mine it's not necessarily wrong. Some Python users say that there should be only one right way, but that goal will never be fully reached. - -## ThinkPython: The way of the program - -1. The strings get added together. -2. We get an error. -3. We get a floating point number. - -## Using if, else and elif - -1. Just ask the word and print word * 1000. - - ```py - word = input("Enter a word: ") - print(word * 1000) - ``` - -2. Add a space to the word before printing. - - ```py - word = input("Enter a word: ") - word += " " - print(word * 1000) - ``` - - We can also add the space right away on the first line: - - ```py - word = input("Enter a word: ") + " " - print(word * 1000) - ``` - - Of course, there are 999 spaces between 1000 words and this will - print 1000 spaces instead, so there will be a useless space at the - end, but it doesn't matter. If we really want to get rid of the - space, we can do something like this instead: - - ```py - no_space = input("Enter a word: ") - yes_space = no_space + " " - print(yes_space * 999 + no_space) - ``` - -3. Like this: - - ```py - first = input("Enter a word: ") - second = input("Enter another word: ") - words = first + " " + second + " " - print(words * 1000) - ``` - -4. You can compare the word against an empty string (`""` or `''`) to - check if it's empty. In this example, the password is "secret". - - ```py - word = input("Enter your password: ") - - if word == "secret": - print("Welcome!") - elif word == "": - print("You didn't enter anything.") - else: - print("Access denied.") - ``` - -5. Simply check the username first, then the password in indented - blocks of code. - - ```py - username = input("Enter your username: ") - password = input("Enter your password: ") - - if username == "foo": - if password == "biz": - print("Welcome foo!") - else: - print("Wrong password!") - elif username == "bar": - if password == "baz": - print("Welcome bar!") - else: - print("Wrong password!") - else: - print("Wrong username.") - ``` - - Example output: - - ``` - >>> ================================ RESTART ================================ - >>> - Enter your username: foo - Enter your password: biz - Welcome foo! - >>> ================================ RESTART ================================ - >>> - Enter your username: bar - Enter your password: baz - Welcome bar! - >>> ================================ RESTART ================================ - >>> - Enter your username: spam - Enter your password: eggs - Wrong username. - >>> ================================ RESTART ================================ - >>> - Enter your username: foo - Enter your password: lol - Wrong password! - >>> - ``` - -## Loops - -1. For loop over the users, each user is a list that contains a - username and a password. - - ```py - users = [ - ['foo', 'biz'], - ['bar', 'baz'], - ] - - username = input("Username: ") - password = input("Password: ") - - logged_in = False - for user in users: - if username == user[0] and password == user[1]: - logged_in = True - break - - if logged_in: - print("Welcome, " + username + "!") - else: - print("Wrong username or password.") - ``` - -2. Just put the whole thing in a `while True:`. Remember that a break - will always break the innermost loop it's in. - - ```py - users = [ - ['foo', 'biz'], - ['bar', 'baz'], - ] - - while True: - username = input("Username: ") - password = input("Password: ") - - logged_in = False - for user in users: - if username == user[0] and password == user[1]: - logged_in = True - break # break the for loop - - if logged_in: - print("Welcome, " + username + "!") - break # break the while loop - else: - print("Wrong username or password.") - ``` - -3. Add a for loop that works as an attempt counter. - - ```py - users = [ - ['foo', 'biz'], - ['bar', 'baz'], - ] - - for attempts_left in [3, 2, 1, 0]: - if attempts_left == 0: - print("No attempts left!") - break # break the outer for loop - - print(attempts_left, "attempts left.") - username = input("Username: ") - password = input("Password: ") - - logged_in = False - for user in users: - if username == user[0] and password == user[1]: - logged_in = True - break # break the inner for loop - - if logged_in: - print("Welcome, " + username + "!") - break # break the outer for loop - else: - print("Wrong username or password.") - ``` - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Back to the list of contents](README.md) 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 new file mode 100644 index 0000000..766ec70 --- /dev/null +++ b/basics/answers.md @@ -0,0 +1,476 @@ +# Answers + +These are my answers for exercises in the chapters. If your solution +isn't exactly like mine but it works just fine it's ok, and you can +[ask me](../contact-me.md) why I didn't do it like you did it. + +## ThinkPython: The way of the program + +1. With +, the strings get added together, and with * we get an error. +2. With + we get an error, and with * the string is repeated multiple times. +3. Python calculates the result and echoes it. + +## If, else and elif + +1. Problems and solutions: + + - The first line should be `print("Hello!")` or `print('Hello!')`, + not `print(Hello!)`. `Hello!` is a piece of text, so we need to + tell Python that it's a string by putting quotes around it. + - The second line will create an error message that says that + there's no variable called `something`. But we are trying to + create it here, so we need `=` instead of `==`. `=` is + assigning, `==` is comparing. + - The last line should have a comma between the arguments, like + `print('You entered:', something)`. + +2. The broken code has mostly the same issues as exercise 1. Here are + 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.")` + +3. We can simply ask the word with input and print `word * 1000`. + + ```python + word = input("Enter a word: ") + print(word * 1000) + ``` + +4. We can add a space to the word before we print it. + + ```python + word = input("Enter a word: ") + word += " " + print(word * 1000) + ``` + + We can also add the space right away on the first line: + + ```python + word = input("Enter a word: ") + " " + print(word * 1000) + ``` + + Of course, there are 999 spaces between 1000 words and this will + print 1000 spaces instead, so there will be a useless space at the + end, but it doesn't matter. If we really want to get rid of the + space, we can do something like this instead: + + ```python + no_space = input("Enter a word: ") + yes_space = no_space + " " + print(yes_space*999 + no_space) + ``` + +5. Like this: + + ```python + first = input("Enter a word: ") + second = input("Enter another word: ") + words = first + " " + second + " " + print(words * 1000) + ``` + +6. We can compare the word against an empty string (`""` or `''`) to + check if it's empty. In this example, the password is "seKr3t". + + ```python + word = input("Enter your password: ") + + if word == "seKr3t": + print("Welcome!") + elif word == "": + print("You didn't enter anything.") + else: + print("Access denied.") + ``` + + Again, this is not a good way to ask a real password from the user. + +## Handy stuff: Strings + +1. The program is not like you might expect it to be. It actually works + 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. I recommend replacing the last line with this: + + ```python + print(f"You entered {word1}, {word2}, {word3} and {word4}.") + ``` + +2. If we have a look at `help(str.upper)` it looks like this: + + S.upper() -> str + + Return a copy of S converted to uppercase. + + We have two problems. First of all, the broken code uses + `message.upper` instead of `message.upper()`. It also expects the + message to magically make itself uppercased, but strings can't be + changed in-place so it doesn't work. + + The solution is to do `message.upper()` and save the value we got + from that to a variable: + + ```python + message = input("What do you want me to say? ") + uppermessage = message.upper() + print(uppermessage, "!!!") + print(uppermessage, "!!!") + print(uppermessage, "!!!") + ``` + + Or we can reuse the same variable name: + + ```python + message = input("What do you want me to say? ") + message = message.upper() + print(message, "!!!") + print(message, "!!!") + print(message, "!!!") + ``` + + Or we can convert the message to uppercase right away on the first + line: + + ```python + message = input("What do you want me to say? ").upper() + print(message, "!!!") + 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. 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 + Traceback (most recent call last): + File "program.py", line 3, in + print("Your name is " + name + ".") + TypeError: Can't convert 'tuple' object to str implicitly + + So Python is trying to convert a tuple to a string. But + `"Your name is "` and `"."` are strings, so maybe `name` is a + tuple? Let's change the last line to just `print(name)` so our + program looks like this: + + ```python + print("Hello!") + name = input("Enter your name: "), + print(name) + ``` + + Let's run it. + + Hello! + Enter your name: my name + ('my name',) + + `name` is indeed a tuple! The problem is the second line. Look + carefully, there's a comma after `input("Enter your name: ")`. + Python created a tuple automatically, but that's not what we + wanted. If we remove the comma, everything works just fine. + +3. Again, the code gives us a weird error message. + + Enter your name: my name + Traceback (most recent call last): + File "program.py", line 3, in + if input("Enter your name: ") in namelist: + TypeError: argument of type 'NoneType' is not iterable + + Now we need to remember that when the error messages talk about + `NoneType` [they always mean None](variables.md#none). So + `namelist` seems to be None. Let's make the program a bit simpler + for working on the namelist: + + ```python + namelist = ['wub_wub', 'RubyPinch', 'go|dfish', 'Nitori'] + namelist = namelist.extend('theelous3') + print(namelist) + ``` + + Now fixing the namelist is easier, so I'll just go through the + problems and solutions: + + - `namelist` is None. It should be `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')` + or `namelist.extend(['theelous3'])` instead to solve this problem. + +## Loops + +1. The problem is that `things` is a string because we converted it to a + string with `str`, so the for loop loops over the characters `[`, + `1`, `,` and so on. Replace `str([1, 2, 3, 4, 5])` with + `[1, 2, 3, 4, 5]`. + +2. The code appends each list in `before` to `after`, so the `number` + variable actually pointed to a list like `[1, 2]`. An easy solution + is to just write two for loops inside each other: + + ```python + before = [[1, 2], [3, 4], [5, 6]] + after = [] + for sublist in before: + for number in sublist: + after.append(number) + print(after) + ``` + + Lists also have an extend method that appends each item from another + list, so we can also use that: + + ```python + before = [[1, 2], [3, 4], [5, 6]] + after = [] + for sublist in before: + after.extend(sublist) + print(after) + ``` + +3. The code has some empty lines in it, and they divide it nicely into + three parts. All of these parts have some problems, so I'll go + through them one by one. + + The first part makes a variable called `input`. The problem is that + now the rest of the program [can't use the input + function](using-functions.md#variables-names-and-builtin-things). It + doesn't really matter here because the rest of the program doesn't + use it anyway, but I still recommend using some other variable name, + like `inputlist`. + + The second part runs `numbers = []` three times. It was probably + meant to be ran once before the loop started, like this: + + ```python + numbers = [] + for string in inputlist: + numbers.append(int(string)) + ``` + + 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. + + ```python + print("Enter something, and press Enter without typing anything", + "when you're done.") + + lines = [] + while True: + line = input('>') + if line == '': + break + lines.append(line) + + for index, line in enumerate(lines, start=1): + print("Line", index, "is:", line) + ``` + +2. Let's start by trying out `zip` with strings: + + ```python + >>> for pair in zip('ABC', 'abc'): + ... print(pair) + ... + ('A', 'a') + ('B', 'b') + ('C', 'c') + >>> + ``` + + Great, it works just like it works with lists. Now let's create + the letter printing program: + + ```python + uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + lowercase = 'abcdefghijklmnopqrstuvwxyz' + + for upper, lower in zip(uppercase, lowercase): + print(upper, lower) + ``` + +3. This one is a bit more difficult than the other two because we + need to combine `zip` and `enumerate`. One way to do this is + to pass a `zip` result to `enumerate`, like this: + + ```python + uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + lowercase = 'abcdefghijklmnopqrstuvwxyz' + + for index, letterpair in enumerate(zip(uppercase, lowercase), start=1): + upper, lower = letterpair + print(index, upper, lower) + ``` + + We can also save the zip result to a variable. I would + probably do this. + + ```python + uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + lowercase = 'abcdefghijklmnopqrstuvwxyz' + + letterzip = zip(uppercase, lowercase) + for index, letterpair in enumerate(letterzip, start=1): + upper, lower = letterpair + print(index, upper, lower) + ``` + + Another alternative is to pass an `enumerate` result to `zip`. This is + a bit more complicated, so I wouldn't do it this way. + + ```python + uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + lowercase = 'abcdefghijklmnopqrstuvwxyz' + + for upper, indexlowerpair in zip(uppercase, enumerate(lowercase, start=1)): + index, lower = indexlowerpair + print(index, upper, lower) + ``` + +## Defining functions + +1. The problem with the first example is that name is a local variable. + I explained how to fix this in [the output section](defining-functions.md#output): + + ```python + def ask_name(): + name = input("Enter your name: ") + return name + + print("Your name is", ask_name()) + ``` + +2. If you run the next example, you get something like this: + + + + The problem is that we print the actual `get_greeting` function, + but we need to **call** it like `get_greeting()`: + + ```python + def get_greeting(): + return "Hello World!" + + print(get_greeting()) + ``` + +3. See [the return or print section](defining-functions.md#return-or-print). + + The greet function prints a greeting. + + ```python + >>> greet("World") + Hello World + >>> + ``` + + But it also returns None because we don't tell it to return anything else. + + ```python + >>> return_value = greet("World") + Hello World + >>> print(return_value) + None + >>> + ``` + + This code from the exercise does the same thing as the code above + does, but without a temporary `return_value` variable: + + ```python + >>> print(greet("World")) + Hello World + None + >>> + ``` + +*** + +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/classes.md b/basics/classes.md new file mode 100644 index 0000000..0b2a284 --- /dev/null +++ b/basics/classes.md @@ -0,0 +1,429 @@ +# 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. + +## What are classes? + +Python comes with many classes that we know already. + +```python +>>> str + +>>> int + +>>> list + +>>> dict + +>>> +``` + +Calling these classes as if they were functions makes a new **instance** +of them. For example, `str()` makes a `str` instance, also known as a +string. + +```python +>>> str() +'' +>>> int() +0 +>>> list() +[] +>>> dict() +{} +>>> +``` + +We can also get an instance's class with `type()`: + +```python +>>> type('') + +>>> type(0) + +>>> type([]) + +>>> type({}) + +>>> +``` + +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 + +In Python, `pass` does nothing. + +```python +>>> pass +>>> +``` + +Let's use it to define an empty class. + +```python +>>> class Website: +... pass +... +>>> Website + +>>> +``` + +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 +names for your classes. + +Now we can make a Website instance by calling the class. + +```python +>>> github = Website() +>>> github +<__main__.Website object at 0x7f36e4c456d8> +>>> type(github) + +>>> +``` + +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 github to our Website. + +```python +>>> github.url = 'https://github.com/' +>>> github.founding_year = 2008 +>>> github.free_to_use = True +>>> +``` + +We can also access the information easily. + +```python +>>> github.url +'https://github.com/' +>>> github.founding_year +2008 +>>> github.free_to_use +True +>>> +``` + +As you can see, our Website is mutable, like lists are, not immutable +like strings are. We can change the website in-place without creating a +new Website. + +`url`, `founding_year` and `free_to_use` are not variables, they are +**attributes**. More specifically, they are **instance attributes**. +The biggest difference is that we need to use a dot for setting and +getting values of attributes, but we don't need that with variables. + +Modules also use instance attributes for accessing their content. For +example, when we do `random.randint`, `random` is a module instance and +`randint` is one of its attributes. + +If we make another Website, does it have the same `url`, `founding_year` +and `free_to_use`? + +```python +>>> effbot = Website() +>>> effbot.url +Traceback (most recent call last): + File "", line 1, in +AttributeError: 'Website' object has no attribute 'url' +>>> +``` + +It doesn't. We'd need to define the attributes for effbot also. + +The attributes are stored in a dictionary called `__dict__`. It's not +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 +>>> github.__dict__ +{'free_to_use': True, + 'founding_year': 2008, + 'url': 'https://github.com/'} +>>> effbot.__dict__ +{} +>>> +``` + +## Class attributes + +What happens if we set an attribute of the `Website` class to some value +instead of doing that to an instance? + +```python +>>> Website.is_online = True +>>> Website.is_online +True +>>> +``` + +Seems to be working, but what happened to the instances? + +```python +>>> github.is_online +True +>>> effbot.is_online +True +>>> +``` + +What was that? Setting `Website.is_online` to a value also set +`github.is_online` and `effbot.is_online` to that value! + +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 +>>> github.__dict__ +{'free_to_use': True, + 'founding_year': 2008, + 'url': 'https://github.com/'} +>>> effbot.__dict__ +{} +>>> +``` + +`Website.is_online` is `Website`'s class attribute, and in Python you can +access class attributes through instances also, so in this case +`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. `github.is_online = True`. + +## Functions and methods + +Let's [define a function](defining-functions.md) that prints information +about a website. + +```python +>>> def website_info(website): +... print("URL:", website.url) +... print("Founding year:", website.founding_year) +... print("Free to use:", website.free_to_use) +... +>>> website_info(github) +URL: https://github.com/ +Founding year: 2008 +Free to use: True +>>> +``` + +Seems to be working. We should be able to get information about all +websites, so maybe we should attach the `website_info` function to the +Website class? + +```python +>>> Website.info = website_info +>>> Website.info(github) +URL: https://github.com/ +Founding year: 2008 +Free to use: True +>>> +``` + +It's working, but `Website.info(github)` is a lot of typing, so +wouldn't `github.info()` be much better? + +```python +>>> github.info() +URL: https://github.com/ +Founding year: 2008 +Free to use: True +>>> +``` + +What the heck happened? We didn't define a `github.info`, it just +magically worked! + +`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 `github.info()`! + +But is `github.info` the same thing as `Website.info`? + +```python +>>> Website.info + +>>> github.info +> +>>> +``` + +It's not. + +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(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 +example `'hello'.lower()` is same as `str.lower('hello')`. + +## Defining methods when defining the class + +Maybe we could define a method when we make the class instead of adding +it later? + +```python +>>> class Website: +... 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) +... +>>> 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 `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 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? + +```python +>>> class Website: +... def initialize(self, url, founding_year, free_to_use): +... self.url = url +... self.founding_year = founding_year +... self.free_to_use = free_to_use +... def info(self): +... print("URL:", self.url) +... print("Founding year:", self.founding_year) +... print("Free to use:", self.free_to_use) +... +>>> github = Website() +>>> github.initialize('https://github.com/', 2008, True) +>>> github.info() +URL: https://github.com/ +Founding year: 2008 +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 +`github`, for example with `github.url`. + +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 +class with arguments and they will be given to `__init__`. Like this: + +```python +>>> class Website: +... def __init__(self, url, founding_year, free_to_use): +... self.url = url +... self.founding_year = founding_year +... self.free_to_use = free_to_use +... def info(self): +... print("URL:", self.url) +... print("Founding year:", self.founding_year) +... print("Free to use:", self.free_to_use) +... +>>> github = Website('https://github.com/', 2008, True) +>>> github.info() +URL: https://github.com/ +Founding year: 2008 +Free to use: True +>>> +``` + +Classes have many other magic methods too, but I'm not going to cover +them in this tutorial. + +## When should I use classes? + +Don't do this: + +```python +class MyProgram: + + def __init__(self): + print("Hello!") + word = input("Enter something: ") + print("You entered " + word + ".") + + +program = MyProgram() +``` + +You should avoid using things like `print` and `input` in the `__init__` +method. The `__init__` method should be simple and it should just set +things up. + +Usually you shouldn't use a class if you're only going to make one +instance of it, and you don't need a class either if you're only going +to have one method. In this example `MyProgram` has only one method and +only one instance. + +Make functions instead, or just write your code without any functions if +it's short enough for that. This program does the same thing and it's +much more readable: + +```python +print("Hello!") +word = input("Enter something: ") +print("You entered " + word + ".") +``` + +## Summary + +- Object-orientated programming is programming with custom data types. + In Python that means using classes and instances. +- Use CapsWords for class names and lowercase_words_with_underscores for + other names. This makes it easy to see which objects are classes and + which objects are instances. +- Calling a class as if it was a function makes a new instance of it. +- `foo.bar = baz` sets `foo`'s attribute `bar` to `baz`. +- Use class attributes for functions and instance attributes for other + things. +- Functions as class attributes can be accessed as instance methods. + They get their instance as the first argument. Call that `self` when + you define the method. +- `__init__` is a special method, and it's ran when a new instance of a + class is created. It does nothing by default. +- Don't use classes if your code is easier to read without them. + +*** + +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](docstrings.md) | +[List of contents](../README.md#basics) diff --git a/basics/defining-functions.md b/basics/defining-functions.md new file mode 100644 index 0000000..9b78b94 --- /dev/null +++ b/basics/defining-functions.md @@ -0,0 +1,585 @@ +# Defining custom functions + +It's probably been a while since you read about using functions. +[Read about it again](using-functions.md) if you need to. + +## Why should I use custom functions? + +Have a look at this code: + +```python +print("************") +print("Hello World!") +print("************") + +print("*************") +print("Enter a word:") +print("*************") + +word = input() + +if word == 'python': + print("*******************") + print("You entered Python!") + print("*******************") +else: + print("**************************") + print("You didn't enter Python :(") + print("**************************") +``` + +Then compare it to this code: + +```python +print_box("Hello World!") +print_box("Enter a word:") +word = input() +if word == 'python': + print_box("You entered Python!") +else: + print_box("You didn't enter Python :(") +``` + +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](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 + +The `pass` keyword does nothing. + +```python +>>> pass +>>> +``` + +Let's use it to define a function that does nothing. + +```python +>>> def do_nothing(): +... pass +... +>>> do_nothing + +>>> +``` + +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() +>>> +``` + +There we go. It did nothing at all. + +Maybe we could just do something in the function instead? + +```python +>>> def print_hi(): +... print("Hi!") +... +>>> print_hi() +Hi! +>>> +``` + +It's working. How about printing a variable in the function? + +```python +>>> def print_message(): +... print(message) +... +>>> message = "Hello World!" +>>> print_message() +Hello World! +>>> +``` + +Again, it works. How about setting a variable in the function? + +```python +>>> def get_username(): +... username = input("Username: ") +... +>>> get_username() +Username: me +>>> username +Traceback (most recent call last): + File "", line 1, in +NameError: name 'username' is not defined +>>> +``` + +That was weird! Why didn't that work? + +## Locals and globals + +So far we have used nothing but **global variables**. They are called +globals because the same variables are available anywhere in our +program, even in functions. + +```python +>>> a = 1 +>>> b = "hi" +>>> c = "hello" +>>> def print_abc(): +... print(a, b, c) +... +>>> print_abc() +1 hi hello +>>> +``` + +But there are also **local variables**. They exist only **inside** +functions, and they are deleted when the function exits. + +```python +>>> def thingy(): +... d = "hello again, i'm a local variable" +... print('inside thingy:', d) +... +>>> thingy() +inside thingy: hello again, i'm a local variable +>>> d +Traceback (most recent call last): + File "", line 1, in +NameError: name 'd' is not defined +>>> +``` + +Let's draw a diagram of these variables: + +![Locals and globals.](../images/locals-and-globals.png) + +However, modifying a global variable in-place from a function is easy. + +```python +>>> stuff = ['global stuff'] +>>> def add_stuff(): +... stuff.append('local stuff') +... +>>> add_stuff() +>>> stuff +['global stuff', 'local stuff'] +>>> +``` + +This only works for changing in-place, we cannot assign a new value to +the variable. + +```python +>>> def set_stuff_to_something_new(): +... stuff = ['more local stuff'] +... +>>> set_stuff_to_something_new() +>>> stuff +['global stuff', 'local stuff'] +>>> +``` + +## Input + +**Note:** This section has nothing to do with the `input` function that +is used like `word = input("enter something: ")`. + +So far our functions seem to be really isolated from the rest of our +code, and it sucks! But they really are not as isolated as you might +think they are. + +Let's think about what the print function does. It takes an argument +and prints it. Maybe a custom function could also take an argument? + +```python +>>> def print_twice(message): +... print(message) +... print(message) +... +>>> +``` + +Here `message` is an argument. When we call the function we'll get a +local variable called message that will point to whatever we passed +to `print_twice`. + +This function can be called in two ways: + +- Using a **positional argument**. + + This is the recommended way for functions that take only one or two + arguments. I would do this in my code. + + ```python + >>> print_twice("hi") + hi + hi + >>> + ``` + + When the function was running it had a local `message` variable + that pointed to `"hi"`. The function printed it twice. + + Positional arguments are great for simple things, but if our + function takes many positional arguments it may be hard to tell + which argument is which. + +- Using a **keyword argument**: + + ```python + >>> print_twice(message="hi") + hi + hi + >>> + ``` + + The name "keyword argument" is a little bit confusing because + keyword arguments don't actually have anything to do with keywords + (`if`, `else` etc). Keyword arguments are just a way to give names + for our arguments. + + Keyword arguments are great when our function needs to take many + arguments, because each argument has a name and it's easy to see + which argument is which. + + Also note that there are no spaces around the `=` sign. This is + just a small style detail that Python programmers like to do + because `message = "hi"` and `some_function(message="hi")` do two + completely different things. + +Now it's time to solve our box printing problem: + +```python +def print_box(message): + print('*' * len(message)) + print(message) + print('*' * len(message)) +``` + +## Default values + +What if we want to print different characters instead of always +printing stars? + +We could change our `print_box` function to take two arguments: + +```python +def print_box(message, character): + print(character * len(message)) + print(message) + print(character * len(message)) +``` + +Then we could change our code to always call `print_box` with a star as +the second argument: + +```python +print_box("Hello World", "*") +... +``` + +But we don't need to change our existing code. We can make the second +argument **optional** by giving it a default value. + +```python +def print_box(message, character='*'): + print(character * len(message)) + print(message) + print(character * len(message)) +``` + +We can print a row of stars using the function without specifying a +different character in two ways: + +- Using a positional argument. + + ```python + print_box("Hello World!") + ``` + +- Using a keyword argument. + + ```python + print_box(message="Hello World!") + ``` + +Or we can give it a different character in a few different ways if we +need to: + +- Using two positional arguments. + + ```python + print_box("Enter a word:", "?") + ``` + +- Using two keyword arguments. + + ```python + print_box(message="Enter a word:", character="?") + print_box(character="?", message="Enter a word:") + ``` + +- Using one positional argument and one keyword argument. + + I would probably do this. If an argument has a default value, I + like to use a keyword argument to change it if needed. + + ```python + print_box("Enter a word:", character="?") + ``` + + However, this doesn't work: + + ```python + print_box(character="?", "Enter a word:") + ``` + + The problem is that we have a keyword argument before a positional + argument. Python doesn't allow this. We don't need to worry about + this, because if we accidentally call a function like this we + will get an error message. + +## Output + +The built-in input function [returns a value](using-functions.md#return-values). +Can our function return a value too? + +```python +>>> def times_two(thing): +... return thing * 2 +... +>>> times_two(3) +6 +>>> times_two(5) +10 +>>> +``` + +Yes, it can. Now typing `times_two(3)` to the prompt does the same +thing as typing `6` to the prompt. + +We can call the `times_two` function and use the result however we +want, just like we can use built-in functions: + +```python +>>> times_two(2) + times_two(3) # calculate 4 + 6 +10 +>>> print('2 * 5 is', times_two(5)) +2 * 5 is 10 +>>> +``` + +Note that **returning from a function ends it immediately**. + +```python +>>> def return_before_print(): +... return None +... print("This never gets printed.") +... +>>> return_before_print() +>>> +``` + +If we don't have any return statements or we have a return statement +that doesn't specify what to return, our function will return None. + +```python +>>> def return_none_1(): +... pass +... +>>> def return_none_2(): +... return +... +>>> print(return_none_1()) +None +>>> print(return_none_2()) +None +>>> +``` + +## Return or 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 +about the `input()` function. It asks the user to enter something, and +then the user enters something and that value is returned. If the input +function would print the value instead of returning it, things like +`name = input("Name: ")` wouldn't work and assigning the result to a +variable would be much more difficult. Printing things is fine when we +know that we'll only need to print the result and we'll never need to +assign it to a variable. + +If our function returns a value we can always print it, like this: + +```python +>>> def return_hi(): +... return "hi" +... +>>> print(return_hi()) +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. + +```python +def ask_yes_no(prompt): + while True: + answer = input(prompt + ' (y or n) ') + if answer == 'y' or answer == 'Y': + return True # returning ends the function + if answer == 'n' or answer == 'N': + return False + print("Answer 'y' or 'n'.") + +if ask_yes_no("Do you like ice cream?"): + print("You like ice cream!") +else: + print("You don't like ice cream.") +``` + +Ask questions with multiple answers. + +```python +def ask_until_correct(prompt, correct_options, + error_message="I don't know what you meant."): + while True: + answer = input(prompt + ' ') + if answer in correct_options: + return answer + print(error_message) + + +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(f"Your favorite color is {choice}!") +``` + +## Summary + +- Functions are a way to write code once, and then use that same + code in multiple places. +- Variables inside functions are **locals**, and variables outside + functions are **globals**. Functions can access all variables, but + by default, they can only create and change the value of local + variables. +- Functions can take **arguments** and they can behave differently + depending on what arguments they get. Arguments are just local + variables. +- Functions can also **return** one value, like the built-in input + 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 + +**There are many things to learn about functions, and I don't expect +you to learn everything at once.** However, there are also many free +exercises about defining functions you can do. + +1. What's wrong with this code? + + ```python + def ask_name(): + name = input("Enter your name: ") + + ask_name() + print("Your name is", name) + ``` + +2. How about this code? + + ```python + def get_greeting(): + return "Hello World!" + + print(get_greeting) + ``` + +3. Why does this print None after greeting the world? + + ```python + def greet(target): + print("Hello", target) + + print(greet("World")) + ``` + +4. Find more exercises about defining functions online. + +Answers for the first, second and third exercise are +[here](answers.md#defining-functions). + +*** + +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](dicts.md) | [Next](larger-program.md) | +[List of contents](../README.md#basics) diff --git a/basics/dicts.md b/basics/dicts.md new file mode 100644 index 0000000..597d1eb --- /dev/null +++ b/basics/dicts.md @@ -0,0 +1,337 @@ +# Dictionaries + +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 names and favorite pets, +we can use a list for that: + +```python +names_and_pets = [ + ('horusr', 'cats'), + ('caisa64', 'cats and dogs'), + ('__Myst__', 'cats'), +] +``` + +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 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 +found_caisa64 = False +for pair in names_and_pets: + if pair[0] == 'caisa64': + found_caisa64 = True + break +if found_caisa64: + # do something +``` + +Or what if we need to find out what caisa64's favorite pets are? That +also requires going through the whole list. + +```python +pets = None +for pair in names_and_pets: + if pair[0] == 'caisa64': + pets = pair[1] + break +# make sure pets is not None and do something with it +``` + +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 information about favorite pets might be a +dictionary: + +```python +favorite_pets = { + 'horusr': 'cats', + 'caisa64': 'cats and dogs', + '__Myst__': 'cats', +} +``` + +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 `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 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 +have multiple variables with the same name. + +Variables are actually stored in a dictionary. We can get that +dictionary with the globals function. In this dictionary, keys are +variable names and values are what our variables point to. + +```python +>>> globals() +{'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 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? + +Dictionaries have some similarities with lists. For example, both +lists and dictionaries have a length. + +```python +>>> len(names_and_pets) # contains three elements +3 +>>> len(favorite_pets) # contains three key:value pairs +3 +>>> +``` + +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 +>>> favorite_pets['caisa64'] +'cats and dogs' +>>> favorite_pets['__Myst__'] +'cats' +>>> +``` + +Trying to get the value of a non-existing key gives us an error. + +```python +>>> favorite_pets['Akuli'] +Traceback (most recent call last): + File "", line 1, in +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'} +>>> +``` + +For looping over a dictionary gets its keys, and checking if something +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 +>>> 'Akuli' in favorite_pets +True +>>> 'dogs' in favorite_pets +False +>>> for name in favorite_pets: +... print(name) +... +caisa64 +Akuli +__Myst__ +horusr +>>> +``` + +Dictionaries have a values method that we can use if we want to do +something with the values: + +```python +>>> favorite_pets.values() +dict_values(['dogs', 'cats', 'cats and dogs', 'cats']) +>>> +``` + +The values method returned a `dict_values` object. Things like this +behave a lot like lists and usually we don't need to convert them to +lists. + +```python +>>> for pets in favorite_pets.values(): +... print(pets) +... +dogs +cats +cats and dogs +cats +>>> +``` + +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. + +If we need both keys and values we can use the items method with the +`for first, second in thing` trick. + +```python +>>> 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)) +... +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 +>>> +``` + +This is also useful for checking if the dictionary has a `key: value` +pair. + +```python +>>> ('horusr', 'cats') in favorite_pets.items() +True +>>> ('horusr', 'dogs') in favorite_pets.items() +False +>>> +``` + +## Limitations + +Sometimes it might be handy to use lists as dictionary keys, but it +just doesn't work. I'm not going to explain why Python doesn't allow +this because usually we don't need to worry about that. + +```python +>>> stuff = {['a', 'b']: 'c', ['d', 'e']: 'f'} +Traceback (most recent call last): + File "", line 1, in +TypeError: unhashable type: 'list' +>>> +``` + +On the other hand, tuples work just fine: + +```python +>>> stuff = {('a', 'b'): 'c', ('d', 'e'): 'f'} +>>> stuff +{('a', 'b'): 'c', ('d', 'e'): 'f'} +>>> +``` + +The values of a dictionary can be anything. + +```python +>>> stuff = {'a': [1, 2, 3], 'b': [4, 5, 6]} +>>> stuff +{'a': [1, 2, 3], 'b': [4, 5, 6]} +>>> +``` + +## Summary + +- Dictionaries consist of `key: value` pairs. +- Variables are stored in a dictionary with their names as keys, so + dictionaries behave a lot like variables: + - Dictionaries are not ordered. + - Setting or getting the value of a key is simple and fast. + - Dictionaries can't contain the same key more than once. +- For-looping over a dictionary loops over its keys, and checking if + something is in the dictionary checks if the dictionary has a key + like that. The `values()` and `items()` methods return things that + behave like lists of values or `(key, value)` pairs instead. + +## Examples + +This program counts how many times words appear in a sentence. +`sentence.split()` creates a list of words in the sentence, see +`help(str.split)` for more info. + +```python +sentence = input("Enter a sentence: ") + +counts = {} # {word: count, ...} +for word in sentence.split(): + if word in counts: + # we have seen this word before + counts[word] += 1 + else: + # this is the first time this word occurs + counts[word] = 1 + +print() # display an empty line +for word, count in counts.items(): + if count == 1: + # "1 times" looks weird + print(word, "appears once in the sentence") + else: + print(word, "appears", count, "times in the sentence") +``` + +Running the program might look like this: + + Enter a sentence: this is a test and this is quite long because this is a test + + is appears 3 times in the sentence + long appears once in the sentence + a appears 2 times in the sentence + because appears once in the sentence + this appears 3 times in the sentence + quite appears once in the sentence + 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, +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](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 new file mode 100644 index 0000000..785fb18 --- /dev/null +++ b/basics/exceptions.md @@ -0,0 +1,474 @@ +# Exceptions + +So far we have made programs that ask the user to enter a string, and +we also know how to convert that to an integer. + +```python +text = input("Enter something: ") +number = int(text) +print("Your number doubled:", number*2) +``` + +That works. + +``` +Enter a number: 3 +Your number doubled: 6 +``` + +But that doesn't work if the user does not enter a number. + +```python +Enter a number: lol +Traceback (most recent call last): + File "/some/place/file.py", line 2, in + number = int(text) +ValueError: invalid literal for int() with base 10: 'lol' +``` + +So how can we fix that? + +## What are exceptions? + +In the previous example we got a ValueError. ValueError is an +**exception**. In other words, ValueError is an error that can occur +in our program. If an exception occurs, the program will stop and we +get an error message. The interactive prompt will display an error +message and keep going. + +```python +>>> int('lol') +Traceback (most recent call last): + File "", line 1, in +ValueError: invalid literal for int() with base 10: 'lol' +>>> +``` + +Exceptions are [classes](classes.md). + +```python +>>> ValueError + +>>> +``` + +We can also create exceptions. We won't get an error message by doing +that, but we'll use this for displaying our own error messages later. + +```python +>>> the_problem = ValueError('oh no') +>>> the_problem +ValueError('oh no',) +>>> +``` + +## Catching exceptions + +If we need to try to do something and see if we get an exception, we +can use `try` and `except`. This is also known as **catching** the +exception. + +```python +>>> try: +... print(int('lol')) +... except ValueError: +... print("Oops!") +... +Oops! +>>> +``` + +The except part doesn't run if the try part succeeds. + +```python +>>> try: +... print("Hello World!") +... except ValueError: +... print("What the heck? Printing failed!") +... +Hello World! +>>> +``` + +ValueError is raised when something gets an invalid value, but the +value's type is correct. In this case, `int` can take a string as an +argument, but the string needs to contain a number, not `lol`. If +the type is wrong, we will get a TypeError instead. + +```python +>>> 123 + 'hello' +Traceback (most recent call last): + File "", line 1, in +TypeError: unsupported operand type(s) for +: 'int' and 'str' +>>> +``` + +Exceptions always interrupt the code even if we catch them. Here the +print never runs because it's after the error but inside the `try` +block. Everything after the try block runs normally. + +```python +>>> try: +... 123 + 'hello' +... print("This doesn't get printed.") +... except TypeError: +... print("Oops!") +... +Oops! +>>> +``` + +Does an `except ValueError` also catch TypeErrors? + +```python +>>> try: +... print(123 + 'hello') +... except ValueError: +... print("Oops!") +... +Traceback (most recent call last): + File "", line 2, in +TypeError: unsupported operand type(s) for +: 'int' and 'str' +>>> +``` + +No, it doesn't. But maybe we could except for both ValueError and +TypeError? + +```python +>>> try: +... int('lol') +... except ValueError: +... print('wrong value') +... except TypeError: +... print('wrong type') +... +wrong value +>>> try: +... 123 + 'hello' +... except ValueError: +... print('wrong value') +... except TypeError: +... print('wrong type') +... +wrong type +>>> +``` + +Seems to be working. + +We can also also catch multiple exceptions by catching +[a tuple](lists-and-tuples.md#tuples) of exceptions: + +```python +>>> try: +... 123 + 'hello' +... except (ValueError, TypeError): +... print('wrong value or type') +... +wrong value or type +>>> try: +... int('lol') +... except (ValueError, TypeError): +... print('wrong value or type') +... +wrong value or type +>>> +``` + +Catching `Exception` will catch all errors. We'll learn more about why +it does that in a moment. + +```python +>>> try: +... 123 + 'hello' +... except Exception: +... print("Oops!") +... +Oops! +>>> try: +... int('lol') +... except Exception: +... print("Oops!") +... +Oops! +>>> +``` + +It's also possible to catch an exception and store it in a variable. +Here we are catching an exception that Python created and storing it in +`our_error`. + +```python +>>> try: +... 123 + 'hello' +... except TypeError as e: +... our_error = e +... +>>> our_error +TypeError("unsupported operand type(s) for +: 'int' and 'str'",) +>>> type(our_error) + +>>> +``` + +## When should we catch exceptions? + +Do **not** do things like this: + +```python +try: + # many lines of code +except Exception: + print("Oops! Something went wrong.") +``` + +There's many things that can go wrong in the `try` block. If something +goes wrong all we have is an oops message that doesn't tell us which +line caused the problem. This makes fixing the program really annoying. +If we want to catch exceptions we need to be specific about what exactly +we want to catch and where instead of catching everything we can in the +whole program. + +There's nothing wrong with doing things like this: + +```python +try: + with open('some file', 'r') as f: + content = f.read() +except OSError: # we can't read the file but we can work without it + content = some_default_content +``` + +Usually catching errors that the user has caused is also a good idea: + +```python +import sys + +text = input("Enter a number: ") +try: + number = int(text) +except ValueError: + print(f"'{text}' is not a number.", file=sys.stderr) + sys.exit(1) +print(f"Your number doubled is {(number * 2)}.") +``` + +## Raising exceptions + +Now we know how to create exceptions and how to handle errors that +Python creates. But we can also create error messages manually. This +is known as **raising an exception** and **throwing an exception**. + +Raising an exception is easy. All we need to do is to type `raise` +and then an exception we want to raise: + +```python +>>> raise ValueError("lol is not a number") +Traceback (most recent call last): + File "", line 1, in +ValueError: lol is not a number +>>> +``` + +Of course, we can also raise an exception from a variable. + +```python +>>> oops = ValueError("lol is not a number") +>>> raise oops +Traceback (most recent call last): + File "", line 1, in +ValueError: lol is not a number +>>> +``` + +If we [define a function](defining-functions.md) that raises an +exception and call it we'll notice that the error message also +says which functions we ran to get to that error. + +```python +>>> def oops(): +... raise ValueError("oh no!") +... +>>> def do_the_oops(): +... oops() +... +>>> do_the_oops() +Traceback (most recent call last): + File "", line 1, in + File "", line 2, in do_the_oops + File "", line 2, in oops +ValueError: oh no! +>>> +``` + +If our code was in a file we would also see the line of code +that raised the error. + +## When should we raise exceptions? + +Back in [the module chapter](modules.md) we learned to display error +messages by printing to `sys.stderr` and then calling `sys.exit(1)`, so +when should we use that and when should we raise an exception? + +Exceptions are meant for **programmers**, so if we are writing something +that other people will import we should use exceptions. If our program +is working like it should be and the **user** has done something wrong, +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.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. + + Exception + ├── ArithmeticError + │ ├── FloatingPointError + │ ├── OverflowError + │ └── ZeroDivisionError + ├── AssertionError + ├── AttributeError + ├── BufferError + ├── EOFError + ├── ImportError + │ └── ModuleNotFoundError + ├── LookupError + │ ├── IndexError + │ └── KeyError + ├── MemoryError + ├── NameError + │ └── UnboundLocalError + ├── OSError + │ ├── BlockingIOError + │ ├── ChildProcessError + │ ├── ConnectionError + │ │ ├── BrokenPipeError + │ │ ├── ConnectionAbortedError + │ │ ├── ConnectionRefusedError + │ │ └── ConnectionResetError + │ ├── FileExistsError + │ ├── FileNotFoundError + │ ├── InterruptedError + │ ├── IsADirectoryError + │ ├── NotADirectoryError + │ ├── PermissionError + │ ├── ProcessLookupError + │ └── TimeoutError + ├── ReferenceError + ├── RuntimeError + │ ├── NotImplementedError + │ └── RecursionError + ├── StopAsyncIteration + ├── StopIteration + ├── SyntaxError + │ └── IndentationError + │ └── TabError + ├── SystemError + ├── TypeError + ├── ValueError + │ └── UnicodeError + │ ├── UnicodeDecodeError + │ ├── UnicodeEncodeError + │ └── UnicodeTranslateError + └── Warning + ├── BytesWarning + ├── DeprecationWarning + ├── FutureWarning + ├── ImportWarning + ├── PendingDeprecationWarning + ├── ResourceWarning + ├── RuntimeWarning + ├── SyntaxWarning + ├── UnicodeWarning + └── UserWarning + +Catching an exception also catches everything that's under it in this +tree. For example, catching `OSError` catches errors that we typically +get when [processing files](files.md), and catching Exception catches +all of these errors. You don't need to remember this tree, running +`help('builtins')` should display a larger tree that this is a part of. + +There are also a few exceptions that are not in this tree like +SystemExit and KeyboardInterrupt, but most of the time we shouldn't +catch them. Catching Exception doesn't catch them either. + +## Summary + +- Exceptions are classes and they can be used just like all other classes. +- ValueError and TypeError are some of the most commonly used exceptions. +- The `try` and `except` keywords can be used for attempting to do + something and then doing something else if we get an error. This is + known as catching exceptions. +- It's possible to raise exceptions with the `raise` keyword. This + is also known as throwing exceptions. +- Raise exceptions if they are meant to be displayed for programmers and + use `sys.stderr` and `sys.exit` otherwise. + +## Examples + +Keep asking a number from the user until it's entered correctly. + +```python +while True: + try: + number = int(input("Enter a number: ")) + break + except ValueError: + print("That's not a valid number! Try again.") + +print("Your number doubled is:", number * 2) +``` + +This program allows the user to customize the message it prints by +modifying a file the greeting is stored in, and it can create the +file for the user if it doesn't exist already. This example also uses +things from [the file chapter](files.md), [the function defining +chapter](defining-functions.md) and [the module chapter](modules.md). + +```python +# These are here so you can change them to customize the program +# easily. +default_greeting = "Hello World!" +filename = "greeting.txt" + + +import sys + +def askyesno(question): + while True: + answer = input(question + ' (y or n) ') + if answer == 'Y' or answer == 'y': + return True + if answer == 'N' or answer == 'n': + return False + +def greet(): + with open(filename, 'r') as f: + for line in f: + print(line.rstrip('\n')) + +try: + greet() +except OSError: + 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) + greet() +``` + +*** + +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](classes.md) | +[List of contents](../README.md#basics) diff --git a/basics/files.md b/basics/files.md new file mode 100644 index 0000000..5a8f7dd --- /dev/null +++ b/basics/files.md @@ -0,0 +1,378 @@ +# Managing files in Python + +## What are files, directories and paths? + +These are simple thing that many computer users already know, but I'll go +through them just to make sure you know them also. + +### Files + +- Each file has a **name**, like `hello.py`, `mytext.txt` or + `coolimage.png`. Usually the name ends with an **extension** that + describes the content, like `py` for Python, `txt` for text or `png` + for "portable network graphic". +- With just names identifying the files, it wouldn't be possible to have + two files with the same name. That's why files also have a + **location**. We'll talk more about this in a moment. +- Files have **content** that consists of + [8-bit bytes](https://www.youtube.com/watch?v=Dnd28lQHquU). + +### Directories/folders + +Directories are a way to group files. They also have a name and a location +like files, but instead of containing data directly like files do they +contain other files and directories. + +### Paths + +Directories and files have a path, like `C:\Users\me\hello.py`. That just +means that there's a folder called `C:`, and inside it there's a folder +called `Users`, and inside it there's a folder called `me` and inside it +there's a `hello.py`. Like this: + +``` +C: +└── Users + └── me + └── hello.py +``` + +`C:\Users\me\hello.py` is an **absolute path**. But there are also +**relative paths**. For example, if we are in `C:\Users`, `me\hello.py` +is same as `C:\Users\me\hello.py`. The place we are in is sometimes +called **current directory**, **working directory** or +**current working directory**. + +So far we've talked about Windows paths, but not all computers run +Windows. For example, an equivalent to `C:\Users\me\hello.py` is +`/home/me/hello.py` on my Ubuntu, and if I'm in `/home`, `me/hello.py` +is same as `/home/me/hello.py`. + +``` +/ +└── home + └── me + └── hello.py +``` + +## Writing to a file + +Let's create a file and write a hello world to it. + +```python +>>> with open('hello.txt', 'w') as f: +... print("Hello World!", file=f) +... +>>> +``` + +Doesn't seem like it did anything. But actually it created a `hello.txt` +somewhere on our system. On Windows it's probably in `C:\Users\YourName`, +and on most other systems it should be in `/home/yourname`. You can open +it with notepad or any other plain text editor your system comes with by +opening the folder that contains the file and then double-clicking the +file. + +So how does that code work? + +First of all, we open a path with `open`, and it gives us a Python file +object that is assigned to the variable `f`. + +```python +>>> f +<_io.TextIOWrapper name='hello.txt' mode='w' encoding='UTF-8'> +>>> +``` + +File objects are not the same thing as paths and filenames, so if we try +to use `'hello.txt'` like we used `f` it doesn't work. + +The first argument we passed to `open` was the path we wanted to write. +Our path was more like a filename than a path, so the file ended up in +the current working directory. + +The second argument was `w`. It's short for write, and that just means +that we'll create a new file. There's some other modes we can use also: + +| Mode | Short for | Meaning | +|-------|-----------|-----------------------------------------------------------------------| +| `r` | read | Read from an existing file. | +| `w` | write | Write to a file. **If the file exists, its old content is removed.** | +| `a` | append | Write to the end of a file, and keep the old content. | + +But what is that `with ourfile as f` crap? That's just a fancy way to make +sure that the file gets closed, no matter what happens. As we can see, +the file was indeed closed. + +```python +>>> f.closed +True +>>> +``` + +When we had opened the file we could just print to it. The print is just +like any other print, but we also need to specify that we want to print +to the file we opened using `file=f`. + +## Reading from files + +After opening a file with the `r` mode we can for loop over it, just +like it was a list. So let's go ahead and read everything in the file +we created to a list of lines. + +```python +>>> lines = [] +>>> with open('hello.txt', 'r') as f: +... for line in f: +... lines.append(line) +... +>>> lines +['Hello World!\n'] +>>> +``` + +Trying to open a non-existent file with `w` or `a` creates the file for +us, but doing that with `r` gives us an error instead. We'll learn more +about errors [later](exceptions.md). + +```python +>>> with open('this-doesnt-exist.txt', 'r') as f: +... print("It's working!") +... +Traceback (most recent call last): + File "", line 1, in +FileNotFoundError: [Errno 2] No such file or directory: 'this-doesnt-exist.txt' +>>> +``` + +So now we have the hello world in the `lines` variable, but it's +`['Hello World!\n']` instead of `['Hello World!']`. So what the heck is +that `\n` doing there? + +`\n` means newline. Note that it needs to be a backslash, so `/n` +doesn't have any special meaning like `\n` has. When we wrote the file +with print it actually added a `\n` to the end of it. It's recommended +to end the content of files with a newline character, but it's not +necessary. + +Let's see how that works if we have more than one line in the file. + +```python +>>> with open('hello.txt', 'w') as f: +... print("Hello one!", file=f) +... print("Hello two!", file=f) +... print("Hello three!", file=f) +... +>>> lines = [] +>>> with open('hello.txt', 'r') as f: +... for line in f: +... lines.append(line) +... +>>> lines +['Hello one!\n', 'Hello two!\n', 'Hello three!\n'] +>>> +``` + +There we go, each of our lines now ends with a `\n`. When we for +loop over the file it's divided into lines based on where the `\n` +characters are, not based on how we printed to it. + +But how to get rid of that `\n`? [The rstrip string +method](https://docs.python.org/3/library/stdtypes.html#str.rstrip) is +great for this: + +```python +>>> stripped = [] +>>> for line in lines: +... stripped.append(line.rstrip('\n')) +... +>>> stripped +['Hello one!', 'Hello two!', 'Hello three!'] +>>> +``` + +It's also possible to read lines one by one. Files have [a readline +method](https://docs.python.org/3/library/io.html#io.TextIOBase.readline) +that reads the next line, and returns `''` if we're at the end of the file. + +```python +>>> with open('hello.txt', 'r') as f: +... first_line = f.readline() +... second_line = f.readline() +... +>>> first_line +'Hello one!\n' +>>> second_line +'Hello two!\n' +``` + +There's only one confusing thing about reading files. If we try +to read the same file object twice we'll find out that it only gets read +once: + +```python +>>> first = [] +>>> second = [] +>>> with open('hello.txt', 'r') as f: +... for line in f: +... first.append(line) +... for line in f: +... second.append(line) +... +>>> first +['Hello one!\n', 'Hello two!\n', 'Hello three!\n'] +>>> second +[] +>>> +``` + +File objects remember their position. When we tried to read the +file again it was already at the end, and there was nothing left +to read. But if we open the file again, we get a new file object that +is in the beginning and everything works. + +```python +>>> first = [] +>>> second = [] +>>> with open('hello.txt', 'r') as f: +... for line in f: +... first.append(line) +... +>>> with open('hello.txt', 'r') as f: +... for line in f: +... second.append(line) +... +>>> first +['Hello one!\n', 'Hello two!\n', 'Hello three!\n'] +>>> second +['Hello one!\n', 'Hello two!\n', 'Hello three!\n'] +>>> +``` + +Usually it's best to just read the file once, and use the +content we have read from it multiple times. + +If we need all of the content as a string, we can use [the read +method](https://docs.python.org/3/library/io.html#io.TextIOBase.read). + +```python +>>> with open('hello.txt', 'r') as f: +... full_content = f.read() +... +>>> full_content +'Hello one!\nHello two!\nHello three!\n' +>>> +``` + +We can also open full paths, like `open('C:\\Users\\me\\myfile.txt', 'r')`. +The reason why we need to use `\\` when we really mean `\` is that +backslash has a special meaning. There are special characters like +`\n`, and `\\` means an actual backslash. + +[comment]: # (GitHub's syntax highlighting screws up with backslashes.) + +``` +>>> print('C:\some\name') +C:\some +ame +>>> print('C:\\some\\name') +C:\some\name +>>> +``` + +Another way to create paths is to tell Python to escape them by +adding an `r` to the beginning of the string. In this case the `r` +is short for "raw", not "read". + +```python +>>> r'C:\some\name' +'C:\\some\\name' +>>> +``` + +If our program is not meant to be ran on Windows and the paths +don't contain backslashes we don't need to double anything or use +`r` in front of paths. + +```python +>>> print('/some/name') +/some/name +>>> +``` + +Doing things like `open('C:\\Users\\me\\myfile.txt', 'r')` is not +recommended because the code needs to be modified if someone wants to +run the program on a different computer that doesn't have a +`C:\Users\me` folder. Just use `open('myfile.txt', 'r')`. + +## Examples + +This program prints the contents of files: + +```python +while True: + filename = input("Filename or path, or nothing at all to exit: ") + if filename == '': + break + + with open(filename, 'r') as f: + # We could read the whole file at once, but this is + # faster if the file is very large. + for line in f: + print(line.rstrip('\n')) +``` + +This program stores the user's username and password in a file. +Plain text files are definitely not a good way to store usernames +and passwords, but this is just an example. + +```python +# Ask repeatedly until the user answers 'y' or 'n'. +while True: + answer = input("Have you been here before? (y/n) ") + if answer == 'Y' or answer == 'y': + been_here_before = True + break + elif answer == 'N' or answer == 'n': + been_here_before = False + break + else: + print("Enter 'y' or 'n'.") + +if been_here_before: + # Read username and password from a file. + with open('userinfo.txt', 'r') as f: + username = f.readline().rstrip('\n') + password = f.readline().rstrip('\n') + + if input("Username: ") != username: + print("Wrong username!") + elif input("Password: ") != password: + print("Wrong password!") + else: + print("Correct username and password, welcome!") + +else: + # Write username and password to a file. + username = input("Username: ") + password = input("Password: ") + with open('userinfo.txt', 'w') as f: + print(username, file=f) + print(password, file=f) + + print("Done! Now run this program again and select 'y'.") +``` + +*** + +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](modules.md) | +[List of contents](../README.md#basics) diff --git a/basics/getting-started.md b/basics/getting-started.md new file mode 100644 index 0000000..9f00625 --- /dev/null +++ b/basics/getting-started.md @@ -0,0 +1,225 @@ +# Getting started with Python + +[Launch Python](installing-python.md). + +The `>>>` means that Python is ready and we can enter a command. The +basic idea is really simple: we enter a command, press Enter, enter +another command, press Enter and keep going. + +You probably don't know any Python commands yet. Let's see what happens +if we just write something and press Enter. + +```python +>>> hello +Traceback (most recent call last): + File "", line 1, in +NameError: name 'hello' is not defined +>>> +``` + +Oops! That didn't work. But like I wrote in the +[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? + +```python +>>> +>>> +>>> +>>> +``` + +That worked. How about numbers? + +```python +>>> 123 +123 +>>> -123 +-123 +>>> 3.14 +3.14 +>>> -12.3 +-12.3 +>>> +``` + +There we go, it echoes them back. + +In some countries, decimal numbers are written with a comma, like `3,14` +instead of `3.14`. Maybe Python knows that? + +```python +>>> 3,14 +(3, 14) +>>> +``` + +We didn't get an error... but `(3, 14)` is not at all what we expected! +So from now on, let's use a dot with decimal numbers, because `3.14` +worked just fine. Later we'll learn what `(3, 14)` is. + +## Comments + +**Comments are text that don't do anything when they're run.** +They can be created by typing a `#` and then some text after it, +and they are useful when our code would be hard to understand without them. + +```python +>>> 1 + 2 # can you guess what the result is? +3 +>>> +``` + +Again, I put a space after the `#` and multiple spaces before it just to +make things easier to read. + +If we write a comment on a line with no code on it, the prompt changes +from `>>>` to `...`. To be honest, I have no idea why it does that and I +think it would be better if it would just stay as `>>>`. The prompt goes +back to `>>>` when we press Enter again. + +```python +>>> # hello there +... +>>> +``` + +## Strings + +Strings are small pieces of text that we can use in our programs. We can +create strings by simply writing some text in quotes. + +```python +>>> 'hello' +'hello' +>>> 'this is a test' +'this is a test' +>>> +``` + +Strings can also be written with "double quotes" instead of 'single +quotes'. This is useful when we need to put quotes inside the string. + +```python +>>> "hello there" +'hello there' +>>> "it's sunny" +"it's sunny" +>>> +``` + +It's also possible to add single quotes and double quotes into the same +string, but most of the time we don't need to do that so I'm not going +to talk about it now. + +It doesn't matter which quotes you use when the string doesn't need to +contain any quotes. If you think that one of the quote types looks nicer +than the other or you find it faster to type, go ahead and use that. + +Strings can be joined together easily with `+` or repeated with `*`: + +```python +>>> "hello" + "world" +'helloworld' +>>> "hello" * 3 +'hellohellohello' +>>> +``` + +Note that a `#` inside a string doesn't create a comment. + +```python +>>> "strings can contain # characters" +'strings can contain # characters' +>>> +``` + +## Using Python as a calculator + +```diff +---------- WARNING: This part contains boring math. Proceed with caution. ---------- +``` + +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 +>>> +``` + +It's working, Python just calculates the result and echoes it back. + +I added a space on both sides of `+`, `-`, `*` and `/`. Everything would +work without those spaces too: + +```python +>>> 4 + 2 + 1 +7 +>>> 4+2+1 +7 +>>> +``` + +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 +>>> 1 + 2 * 3 # 2 * 3 is calculated first +7 +>>> (1 + 2) * 3 # 1 + 2 is calculated first +9 +>>> +``` + +You can also leave out spaces to show what's calculated first. Python +ignores it, but our code will be easier to read for people. + +```python +>>> 1 + 2*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 + +[comment]: # (the first line in this summary is exactly same as in) +[comment]: # (what-is-programming.md, and it's supposed to be like this) + +- Error messages are our friends. +- We can enter any Python commands to the interactive `>>>` prompt, and + it will echo back the result. +- `+`, `-`, `*` and `/` work in Python just like in math. +- Pieces of text starting with a `#` are comments 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, +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](installing-python.md) | [Next](the-way-of-the-program.md) | +[List of contents](../README.md#basics) diff --git a/basics/handy-stuff-strings.md b/basics/handy-stuff-strings.md new file mode 100644 index 0000000..98d5cc4 --- /dev/null +++ b/basics/handy-stuff-strings.md @@ -0,0 +1,379 @@ +# Handy stuff: Strings + +Python strings are just pieces of text. + +```python +>>> our_string = "Hello World!" +>>> our_string +'Hello World!' +>>> +``` + +So far we know how to add them together. + +```python +>>> "I said: " + our_string +'I said: Hello World!' +>>> +``` + +We also know how to repeat them multiple times. + +```python +>>> our_string * 3 +'Hello World!Hello World!Hello World!' +>>> +``` + +Python strings are [immutable](https://docs.python.org/3/glossary.html#term-immutable). +That's just a fancy way to say that +they cannot be changed in-place, and we need to create a new string to +change them. Even `some_string += another_string` creates a new string. +Python will treat that as `some_string = some_string + another_string`, +so it creates a new string but it puts it back to the same variable. + +`+` and `*` are nice, but what else can we do with strings? + +## Slicing + +Slicing is really simple. It just means getting a part of the string. +For example, to get all characters between the second place between the +characters and the fifth place between the characters, we can do this: + +```python +>>> our_string[2:5] +'llo' +>>> +``` + +So the syntax is like `some_string[start:end]`. + +This picture explains how the slicing works: + +![Slicing with non-negative values](../images/slicing1.png) + +But what happens if we slice with negative values? + +```python +>>> our_string[-5:-2] +'orl' +>>> +``` + +It turns out that slicing with negative values simply starts counting +from the end of the string. + +![Slicing with negative values](../images/slicing2.png) + +If we don't specify the beginning it defaults to 0, and if we don't +specify the end it defaults to the length of the string. For example, we +can get everything except the first or last character like this: + +```python +>>> our_string[1:] +'ello World!' +>>> our_string[:-1] +'Hello World' +>>> +``` + +Remember that strings can't be changed in-place. + +```python +>>> our_string[:5] = 'Howdy' +Traceback (most recent call last): + File "", line 1, in +TypeError: 'str' object does not support item assignment +>>> +``` + +There's also a step argument we can give to our slices, but I'm not +going to talk about it now. + +## Indexing + +So now we know how slicing works. But what happens if we forget the `:`? + +```python +>>> our_string[1] +'e' +>>> +``` + +That's interesting. We got a string that is only one character long. But +the first character of `Hello World!` should be `H`, not `e`, so why did +we get an e? + +Programming starts at zero. Indexing strings also starts at zero. The +first character is `our_string[0]`, the second character is +`our_string[1]`, and so on. + +```python +>>> our_string[0] +'H' +>>> our_string[1] +'e' +>>> our_string[2] +'l' +>>> our_string[3] +'l' +>>> our_string[4] +'o' +>>> +``` + +So string indexes work like this: + +![Indexing with non-negative values](../images/indexing1.png) + +How about negative values? + +```python +>>> our_string[-1] +'!' +>>> +``` + +We got the last character. + +But why didn't that start at zero? `our_string[-1]` is the last +character, but `our_string[1]` is not the first character! + +That's because 0 and -0 are equal, so indexing with -0 would do the same +thing as indexing with 0. + +Indexing with negative values works like this: + +![Indexing with negative values](../images/indexing2.png) + +## String methods + +Python's strings have many useful methods. +[The official documentation](https://docs.python.org/3/library/stdtypes.html#string-methods) +covers them all, but I'm going to just show some of the most commonly +used ones briefly. Python also comes with built-in documentation about +the string methods and we can run `help(str)` to read it. We can also +get help about one string method at a time, like `help(str.upper)`. + +Again, nothing can modify strings in-place. Most string methods +return a new string, but things like `our_string = our_string.upper()` +still work because the new string is assigned to the old variable. + +Also note that all of these methods are used like `our_string.stuff()`, +not like `stuff(our_string)`. The idea with that is that our string +knows how to do all these things, like `our_string.stuff()`, we don't +need a separate function that does these things like `stuff(our_string)`. +We'll learn more about methods [later](classes.md). + +Here's an example with some of the most commonly used string methods: + +```python +>>> our_string.upper() +'HELLO WORLD!' +>>> our_string.lower() +'hello world!' +>>> our_string.startswith('Hello') +True +>>> our_string.endswith('World!') +True +>>> our_string.endswith('world!') # Python is case-sensitive +False +>>> our_string.replace('World', 'there') +'Hello there!' +>>> our_string.replace('o', '@', 1) # only replace one o +'Hell@ World!' +>>> ' hello 123 '.lstrip() # left strip +'hello 123 ' +>>> ' hello 123 '.rstrip() # right strip +' hello 123' +>>> ' hello 123 '.strip() # strip from both sides +'hello 123' +>>> ' hello abc'.rstrip('cb') # strip c's and b's from right +' hello a' +>>> our_string.ljust(30, '-') +'Hello World!------------------' +>>> our_string.rjust(30, '-') +'------------------Hello World!' +>>> our_string.center(30, '-') +'---------Hello World!---------' +>>> our_string.count('o') # it contains two o's +2 +>>> our_string.index('o') # the first o is our_string[4] +4 +>>> our_string.rindex('o') # the last o is our_string[7] +7 +>>> '-'.join(['hello', 'world', 'test']) +'hello-world-test' +>>> 'hello-world-test'.split('-') +['hello', 'world', 'test'] +>>> our_string.upper()[3:].startswith('LO WOR') # combining multiple things +True +>>> +``` + +The things in square brackets that the split method gave us and +we gave to the join method were lists. We'll talk more about +them [later](lists-and-tuples.md). + +## String formatting + +To add a string in the middle of another string, we can do something +like this: + +```python +>>> name = 'Akuli' +>>> 'My name is ' + name + '.' +'My name is Akuli.' +>>> +``` + +But that gets complicated if we have many things to add. + +```python +>>> channel = '##learnpython' +>>> network = 'freenode' +>>> "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." +>>> +``` + +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 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. + +`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 +>>> 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." +>>> +``` + +## Other things + +We can use `in` and `not in` to check if a string contains another +string. + +```python +>>> our_string = "Hello World!" +>>> "Hello" in our_string +True +>>> "Python" in our_string +False +>>> "Python" not in our_string +True +>>> +``` + +We can get the length of a string with the `len` function. The name +`len` is short for "length". + +```python +>>> len(our_string) # 12 characters +12 +>>> len('') # no characters +0 +>>> len('\n') # python thinks of \n as one character +1 +>>> +``` + +We can convert strings, integers and floats with each other with +`str`, `int` and `float`. They aren't actually functions, but they +behave a lot like functions. We'll learn more about what they really +are [later](classes.md). + +```python +>>> str(3.14) +'3.14' +>>> float('3.14') +3.14 +>>> str(123) +'123' +>>> int('123') +123 +>>> +``` + +Giving an invalid string to `int` or `float` produces an error +message. + +```python +>>> int('lol') +Traceback (most recent call last): + File "", line 1, in +ValueError: invalid literal for int() with base 10: 'lol' +>>> float('hello') +Traceback (most recent call last): + File "", line 1, in +ValueError: could not convert string to float: 'hello' +>>> +``` + +## Summary + +- Slicing returns a copy of a string with indexes from one index to + another index. The indexes work like this: + + ![Slicing](../images/slicing3.png) + +- Indexing returns one character of a string. Remember that we don't + need a `:` with indexing. The indexes work like this: + + ![Indexing](../images/indexing3.png) + +- 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 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. +- The `in` keyword can be used for checking if a string contains another + string. +- `len(string)` returns string's length. +- We can use `str`, `int` and `float` to convert values to different + types. + +## Exercises + +1. Fix this program. + + ```python + print("Hello!") + word1 = input("Enter something: ") + word2 = input("Enter another thing: ") + word3 = input("Enter a third thing: ") + word4 = input("And yet another thing: ") + print("You entered " + word1 + ", " + word2 + ", " + word3 + " and " + word4 + ".") + ``` + +2. This program is supposed to say something loudly. Fix it. + + ```python + message = input("What do you want me to say? ") + message.upper + print(message, "!!!") + 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, +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](if.md) | [Next](lists-and-tuples.md) | +[List of contents](../README.md#basics) diff --git a/basics/if.md b/basics/if.md new file mode 100644 index 0000000..71655f2 --- /dev/null +++ b/basics/if.md @@ -0,0 +1,330 @@ +# If, else and elif + +## Using if statements + +Now we know what True and False are. + +```python +>>> 1 == 1 +True +>>> 1 == 2 +False +>>> +>>> its_raining = True +>>> its_raining +True +>>> +``` + +But what if we want to execute different code depending on something? +That's when `if` comes in. + +```python +>>> its_raining = True +>>> if its_raining: +... print("It's raining!") +... +It's raining! +>>> its_raining = False +>>> if its_raining: +... print("It's raining!") # nothing happens +... +>>> +``` + +The prompt changed from `>>>` to `...`. It meant that Python was +expecting me to keep typing. When I was done, I just pressed Enter +twice. My code was executed and the prompt went back to `>>>`. + +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. + +But why is that `if its_raining` instead of `if(its_raining)`? + +Earlier we learned that `if` is a **keyword**. + +```python +>>> if = 123 + File "", line 1 + if = 123 + ^ +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. + +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. + +```python +>>> its_raining = True +>>> if its_raining: +... its_raining = False +... print("It's not raining, but this runs anyway.") +... +It's not raining, but this runs anyway. +>>> +``` + +## Using else + +What if we want to print a different message if it's not raining? We +could do something like this: + +```python +its_raining = True # you can change this to False +its_not_raining = not its_raining # False if its_raining, True otherwise + +if its_raining: + print("It's raining!") +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. + +We can also add `not its_raining` directly to the second if statement: + +```python +its_raining = True + +if its_raining: + print("It's raining!") +if not its_raining: + print("It's not raining.") +``` + +But we can make it even better by using `else`. + +```python +its_raining = True + +if its_raining: + print("It's raining!") +else: + print("It's not raining.") +``` + +The else part simply runs when the if statement doesn't run. It doesn't +check the condition again. + +```python +>>> its_raining = True +>>> if its_raining: +... its_raining = False +... else: +... print("It's not raining, but this still doesn't run.") +... +>>> +``` + +By combining `else` with the input function we can make a program that +asks for a password and checks if it's correct. + +```python +print("Hello!") +password = input("Enter your password: ") + +if password == "secret": + print("That's correct, welcome!") +else: + print("Access denied.") +``` + +The program prints different things depending on what we enter: + +``` +Hello! +Enter your password: secret +Welcome! +``` + +``` +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 +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, we could do this: + +```python +print("Hello!") +word = input("Enter something: ") + +if word == "hi": + print("Hi to you too!") +else: + if word == "hello": + print("Hello hello!") + else: + if word == "howdy": + print("Howdyyyy!") + else: + if word == "hey": + print("Hey hey hey!") + else: + if word == "gday m8": + print("Gday 4 u 2!") + 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: + +```python +print("Hello!") +word = input("Enter something: ") + +if word == "hi": + print("Hi to you too!") +elif word == "hello": + print("Hello hello!") +elif word == "howdy": + print("Howdyyyy!") +elif word == "hey": + print("Hey hey hey!") +elif word == "gday m8": + print("Gday 4 u 2!") +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. + If it doesn't, write it to a file and run that file. +- Indentation is important in Python. +- Indented code under an if statement runs if the condition is true. +- We can also add an else statement. Indented code under it will run + if the code under the if statement does not run. +- elif is short for else if. + +## Exercises + +1. This program contains several problems. Copy-paste it to a file, + then try to run it, fix the errors you got, try to run it again and + keep going until it works. + + ```python + print(Hello!) + something == input('Enter something: ) + print('You entered:' something) + ``` + +2. Fix this program the same way: + + ```python + print('Hello!') + something = input("Enter something: ") + if something = 'hello': + print("Hello for you too!") + + elif something = 'hi' + print('Hi there!') + else: + print("I don't know what," something, "means.") + ``` + +3. Write a program into a file that asks the user to write a word and + then prints that word 1000 times. For example, if the user enters + `hi` the program would reply `hihihihihihihihi` ... + +4. Add spaces between the words, so the output is like `hi hi hi hi` ... + +5. Make something that asks the user to enter two words, and prints + 1000 of each with spaces in between. For example, if the user + enters `hello` and `hi` the program would print + `hello hi hello hi hello hi hello hi hello hi` ... + +6. Make a program that asks for a password and prints `Welcome!`, + `Access denied` or `You didn't enter anything` depending on whether + 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, +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](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 new file mode 100644 index 0000000..249cc4b --- /dev/null +++ b/basics/installing-python.md @@ -0,0 +1,91 @@ +# Installing Python + +If you want to learn to program with Python using this tutorial, you +need to try out the code examples. You can use a website like +[repl.it](https://repl.it/languages/python3), but I highly recommend +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 + +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. +3. Run the installer. +4. Make sure that the launcher gets installed and click Install Now. + + ![The py.exe launcher.](../images/py-exe.png) + +### Mac OSX + +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). + +### Linux + +You already have Python 3, **there's no need to install anything**. You +may also have Python 2, but don't try to remove it. +Some of the programs that came with your operating system +are probably written in Python 2, so removing Python 2 would +break them. + +## Running Python + +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. + +### Windows + +1. Open a PowerShell from your start menu or start screen. +2. Type `py` and press Enter. You should see something like this: + + ![Python running in a PowerShell window.](../images/powershell.png) + +### Other operating systems + +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: + + ![Running Python on my terminal.](../images/terminal.png) + + Your terminal probably looks different than mine, it's OK. + +Now you can type `exit()` and press Enter to get out of Python. Or you +can just close the PowerShell or Terminal window. + +## Summary + +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, +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-programming.md) | [Next](getting-started.md) | +[List of contents](../README.md#basics) diff --git a/basics/larger-program.md b/basics/larger-program.md new file mode 100644 index 0000000..4cd0cda --- /dev/null +++ b/basics/larger-program.md @@ -0,0 +1,241 @@ +# Writing a larger program + +Now we know enough about Python for creating a program that is actually +useful. Awesome! + +In this tutorial we'll write a program that reads questions and answers +in a text file and asks them. For example, this file would make the +program ask what "text displaying function" and "text asking function" +are: + +``` +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 +language, but then I realized that I could study pretty much anything +with it. + +But there are many things the program needs to do and writing it seems +really difficult and complicated! How the heck can we do this? + +## Write functions + +Our program will need to do several different things: + +1. Read the questions from a file. +2. Ask the questions. +3. Print statistics about how many questions were answered correctly + and how many wrong. + +Now everything seems much easier. We know how to do each of these steps +one by one, but doing it all at once would be difficult. In situations +like this it's important to [define functions](defining-functions.md). +We are going to write a `read_questions` function, an `ask_questions` +function and a `stats` function. + +Let's start with the function that reads the question file: + +```python +def read_questions(filename): + answers = {} + with open(filename, 'r') as f: + for line in f: + line = line.strip() + if line != '': + question, answer = line.split('=') + answers[question.strip()] = answer.strip() + return answers +``` + +At this point it's best to try out the function to see how it works. You +need to create a `questions.txt` file like the one in the beginning of +this tutorial if you didn't create it already. + +**TODO:** Instructions for using the -i switch. + +```python +>>> read_questions('questions.txt') +{'text displaying function': 'print', 'text asking function': 'input'} +>>> +``` + +If your function doesn't work correctly it doesn't matter, and fixing +the problem is easy because the function is so short. This is one of the +reasons why we write functions. + +Next we'll write the rest of the functions the same way, first writing +and then testing and fixing. Here are my versions of them: + +```python +def ask_questions(answers): + correct = [] + wrong = [] + + for question, answer in answers.items(): + if input(question + ' = ').strip() == answer: + print("Correct!") + correct.append(question) + else: + 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]) +``` + +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') +>>> correct, wrong = ask_questions(answers) +text displaying function = print +Correct! +text asking function = elif +Wrong! The correct answer is input. +>>> correct +['text displaying function'] +>>> wrong +['text asking function'] +>>> stats(correct, wrong, answers) + +**** STATS **** + +You answered 1 questions right and 1 questions wrong. +These would have been the correct answers: + text asking function = input +>>> +``` + +Everything is working! Now we just need something that runs everything +because we don't want to type this out on the `>>>` prompt every time. + +You might have noticed that the stats function printed `1 questions` +instead of `1 question`, and it looks a bit weird. You can modify the +`print_stats` function to fix this if you want to. + +## The main function + +The last function in a program like this is usually called `main` and it +runs the program using other functions. Our main function consists of +mostly the same pieces of code that we just tried out on the `>>>` +prompt. + +```python +def main(): + filename = input("Name of the question file: ") + answers = read_questions(filename) + correct, wrong = ask_questions(answers) + stats(correct, wrong, answers) +``` + +The last thing we need to add is these two lines: + +```python +if __name__ == '__main__': + main() +``` + +The `__name__` variable is set differently depending on how we run the +file, and it's `'__main__'` when we run the file directly instead of +importing. So if we run the file normally it asks us the words, and if +we import it instead we can still run the functions one by one. If you +want to know more about `__name__` just make a file that prints it and +run it in different ways. + +Now the whole program looks like this: + +```python +def read_questions(filename): + answers = {} + with open(filename, 'r') as f: + for line in f: + line = line.strip() + if line != '': + question, answer = line.split('=') + answers[question.strip()] = answer.strip() + return answers + + +def ask_questions(answers): + correct = [] + wrong = [] + + for question, answer in answers.items(): + if input(f'{question} = ').strip() == answer: + print("Correct!") + correct.append(question) + else: + 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) + correct, wrong = ask_questions(answers) + stats(correct, wrong, answers) + +if __name__ == '__main__': + main() +``` + +This is just the beginning. Now [you can](../LICENSE) take your word +asking program and make your own version of it that suits **your** +needs. Then you can share it with your friends so they will find it +useful as well. + +## Summary + +- Make multiple functions when your program needs to do multiple things. + Each function should do one thing. +- Try out the functions on the `>>>` prompt when you want to check if + they work correctly. +- `__name__` is `'__main__'` when the program is supposed to run, and +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, +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](defining-functions.md) | [Next](what-is-true.md) | +[List of contents](../README.md#basics) diff --git a/basics/lists-and-tuples.md b/basics/lists-and-tuples.md new file mode 100644 index 0000000..379d019 --- /dev/null +++ b/basics/lists-and-tuples.md @@ -0,0 +1,388 @@ +# Lists and tuples + +## Why should we use lists? + +Sometimes we may end up doing something like this. + +```python +name1 = 'wub_wub' +name2 = 'theelous3' +name3 = 'RubyPinch' +name4 = 'go|dfish' +name5 = 'Nitori' + +name = input("Enter your name: ") +if name == name1 or name == name2 or name == name3 or name == name4 or name == name5: + print("I know you!") +else: + print("Sorry, I don't know who you are :(") +``` + +This code works just fine, but there's a problem. The name check +is repetitive, and adding a new name requires adding even more +repetitive, boring checks. + +## Our first list + +Instead of adding a new variable for each name it might be +better to store all names in one variable. This means that our +one variable needs to point to multiple values. An easy way to +do this is using a list: + +```python +names = ['wub_wub', 'theelous3', 'Nitori', 'RubyPinch', 'go|dfish'] +``` + +Here the `names` variable points to a list, which then points to +strings, like this: + +![List of names.](../images/people.png) + +## What can we do with lists? + +Let's open the `>>>` prompt and create a name list. + +```python +>>> names = ['wub_wub', 'theelous3', 'RubyPinch', 'go|dfish', 'Nitori'] +>>> names +['wub_wub', 'theelous3', 'RubyPinch', 'go|dfish', 'Nitori'] +>>> +``` + +There's many things [we can do with strings](handy-stuff-strings.md), +and some of these things also work with lists. + +```python +>>> len(names) # len is short for length, we have 5 names +5 +>>> names + ['Akuli'] # create a new list with me in it +['wub_wub', 'theelous3', 'RubyPinch', 'go|dfish', 'Nitori', 'Akuli'] +>>> ['theelous3', 'RubyPinch'] * 2 # repeating +['theelous3', 'RubyPinch', 'theelous3', 'RubyPinch'] +>>> +``` + +With strings indexing and slicing both returned a string, but +with lists we get a new list when we're slicing and an element +from the list if we're indexing. + +```python +>>> names[:2] # first two names +['wub_wub', 'theelous3'] +>>> names[0] # the first name +'wub_wub' +>>> +``` + +If we want to check if the program knows a name all we need to +do is to use the `in` keyword. + +```python +>>> 'lol' in names +False +>>> 'RubyPinch' in names +True +>>> +``` + +We can't use this for checking if a list of names is a part of +our name list. + +```python +>>> ['RubyPinch', 'go|dfish'] in names +False +>>> ['RubyPinch'] in names +False +>>> +``` + +Lists have a few [useful +methods](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists). +Some of the most commonly used ones are append, extend and remove. +`append` adds an item to the end of a list, `extend` adds +multiple items from another list and `remove` removes an item. + +```python +>>> names +['wub_wub', 'theelous3', 'RubyPinch', 'go|dfish', 'Nitori'] +>>> names.remove('theelous3') # sorry theelous3 +>>> names.remove('go|dfish') # and sorry go|dfish +>>> names +['wub_wub', 'RubyPinch', 'Nitori'] +>>> names.append('Akuli') # let's add me here +>>> names +['wub_wub', 'RubyPinch', 'Nitori', 'Akuli'] +>>> names.extend(['go|dfish', 'theelous3']) # wb guys +>>> names +['wub_wub', 'RubyPinch', 'Nitori', 'Akuli', 'go|dfish', 'theelous3'] +>>> +``` + +Note that `remove` removes only the first match it finds. + +```python +>>> names = ['theelous3', 'go|dfish', 'theelous3'] +>>> names.remove('theelous3') +>>> names # the second theelous3 is still there! +['go|dfish', 'theelous3'] +>>> +``` + +If we need to remove all matching items we can use a simple while loop. +We'll talk more about loops [in the next chapter](loops.md). + +```python +>>> names = ['theelous3', 'go|dfish', 'theelous3'] +>>> while 'theelous3' in names: +... names.remove('theelous3') +... +>>> names +['go|dfish'] +>>> +``` + +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 +>>> names = ['theelous3', 'LOL', 'RubyPinch', 'go|dfish', 'Nitori'] +>>> names[1] = 'wub_wub' # replace LOL with wub_wub +>>> names +['theelous3', 'wub_wub', 'RubyPinch', 'go|dfish', 'Nitori'] +>>> +``` + +As you can see, **list can be changed in-place**. In other +words, they are **mutable**. Integers, floats, strings and many +other built-in types can't, so they are **immutable**. + +With [strings](handy-stuff-strings.md) we did something to them +and then set the result back to the same variable, like +`message = message.strip()`. This just doesn't work right with +most mutable things because they're designed to be changed in-place. + +```python +>>> names = names.remove('Akuli') +>>> print(names) # now it's None! +None +>>> +``` + +This is the same thing that happened way back when [we assigned +print's return value to a variable](using-functions.md#return-values). + +## What is what? + +After working with lists a while you'll find out that they +behave like this: + +```python +>>> a = [1, 2, 3] +>>> b = a +>>> b.append(4) +>>> a # this changed also! +[1, 2, 3, 4] +>>> +``` + +This can be confusing at first, but it's actually easy to +explain. The problem with this code example is the `b = a` +line. If we draw a picture of the variables it looks like this: + +![Same list.](../images/samelist.png) + +This is when the `is` keyword comes in. It can be used to +check if two variables point to the **same** thing. + +```python +>>> a is b +True +>>> +``` + +Typing `[]` creates a **new** list every time. + +```python +>>> [] is [] +False +>>> [1, 2, 3] is [1, 2, 3] +False +>>> +``` + +If we need **a new list with similar content** we can use the +`copy` method. + +```python +>>> a = [1, 2, 3] +>>> b = a.copy() +>>> b is a +False +>>> b.append(4) +>>> b +[1, 2, 3, 4] +>>> a +[1, 2, 3] +>>> +``` + +If we draw a picture of our variables in this example it looks +like this: + +![Different lists.](../images/differentlist.png) + +## Tuples + +Tuples are a lot like lists, but they're immutable so they +can't be changed in-place. We create them like lists, but +with `()` instead of `[]`. + +```python +>>> thing = (1, 2, 3) +>>> thing +(1, 2, 3) +>>> thing = () +>>> thing +() +>>> +``` + +If we need to create a tuple that contains only one item we +need to use `(item,)` instead of `(item)` because `(item)` is +used in places like `(1 + 2) * 3`. + +```python +>>> (3) +3 +>>> (3,) +(3,) +>>> (1 + 2) * 3 +9 +>>> (1 + 2,) * 3 +(3, 3, 3) +>>> +``` + +It's also possible to create tuples by just separating things with +commas and adding no parentheses. Personally I don't like this feature, +but some people like to do it this way. + +```python +>>> 1, 2, 3 +(1, 2, 3) +>>> 'hello', +('hello',) +>>> +``` + +Tuples don't have methods like append, extend and remove +because they can't change themselves in-place. + +```python +>>> stuff = (1, 2, 3) +>>> stuff.append(4) +Traceback (most recent call last): + File "", line 1, in +AttributeError: 'tuple' object has no attribute 'append' +>>> +``` + +So, why the heck would we use tuples instead of lists? There are +some cases when we don't want mutability, but there are also +cases when Python programmers just like to use tuples. If you +want to know more about this you can read [Ned Batchelder's blog +post about this](http://nedbatchelder.com/blog/201608/lists_vs_tuples.html). + +## Summary + +- Lists are a way to store multiple values in one variable. +- Lists can be changed in-place and they have methods that change them + in-place, like append, extend and remove. +- Slicing lists returns a **new** list, and indexing them returns an + item from them. +- `thing = another_thing` does not create a copy of `another_thing`. +- Tuples are like lists, but they can't be changed in-place. They're + also used in different places. + +## Examples + +Here's the same program we had in the beginning of this tutorial, but +using a list: + +```python +namelist = ['wub_wub', 'theelous3', 'RubyPinch', 'go|dfish', 'Nitori'] + +name = input("Enter your name: ") +if name in namelist: + print("I know you!") +else: + print("Sorry, I don't know who you are :(") +``` + +## Exercises + +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!") + name = input("Enter your name: "), + print("Your name is " + name + ".") + ``` + +3. Fix this program. + + ```python + namelist = ['wub_wub', 'RubyPinch', 'go|dfish', 'Nitori'] + namelist = namelist.extend('theelous3') + if input("Enter your name: ") in namelist: + print("I know you!") + else: + print("I don't know you.") + ``` + +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, +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](handy-stuff-strings.md) | [Next](loops.md) | +[List of contents](../README.md#basics) diff --git a/basics/loops.md b/basics/loops.md new file mode 100644 index 0000000..7f693e1 --- /dev/null +++ b/basics/loops.md @@ -0,0 +1,506 @@ +# Loops + +In programming, a **loop** means repeating something multiple times. +There are different kinds of loops: + +- [While loops](#while-loops) repeat something while a condition is true. +- [Until loops](#until-loops) repeat something while a condition is false. +- [For loops](#for-loops) repeat something for each element of something. + +We'll talk about all of these in this tutorial. + +## While loops + +Now we know how if statements work. + +```python +its_raining = True +if its_raining: + print("Oh crap, it's raining!") +``` + +While loops are really similar to if statements. + +```python +its_raining = True +while its_raining: + print("Oh crap, it's raining!") + # we'll jump back to the line with the word "while" from here +print("It's not raining anymore.") +``` + +If you're not familiar with while loops, the program's output may be a +bit surprising: + + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + Oh crap, it's raining! + (much more raining) + +Again, this program does not break your computer. It just prints the +same thing multiple times. We can interrupt it by pressing Ctrl+C. + +In this example, `its_raining` was the **condition**. If something in +the while loop would have set `its_raining` to False, the loop would +have ended and the program would have printed `It's not raining anymore`. + +Let's actually create a program that does just that: + +```python +its_raining = True +while its_raining: + print("It's raining!") + answer = input("Or is it? (y=yes, n=no) ") + if answer == 'y': + print("Oh well...") + elif answer == 'n': + its_raining = False # end the while loop + else: + print("Enter y or n next time.") +print("It's not raining anymore.") +``` + +Running the program may look like this: + + It's raining! + Or is it? (y=yes, n=no) i dunno + Enter y or n next time. + It's raining! + Or is it? (y=yes, n=no) y + Oh well... + It's raining! + Or is it? (y=yes, n=no) n + It's not raining anymore. + +The while loop doesn't check the condition all the time, it only checks +it in the beginning. + +```python +>>> its_raining = True +>>> while its_raining: +... its_raining = False +... print("It's not raining, but the while loop doesn't know it yet.") +... +It's not raining, but the while loop doesn't know it yet. +>>> +``` + +We can also interrupt a loop even if the condition is still true using +the `break` keyword. In this case, we'll set condition to True and rely +on nothing but `break` to end the loop. + +```python +while True: + answer = input("Is it raining? (y=yes, n=no) ") + if answer == 'y': + print("It's raining!") + elif answer == 'n': + print("It's not raining anymore.") + break # end the loop + else: + print("Enter y or n.") +``` + +The program works like this: + + Is it raining? (y=yes, n=no) who knows + Enter y or n. + Is it raining? (y=yes, n=no) y + It's raining! + Is it raining? (y=yes, n=no) n + It's not raining anymore. + +Unlike setting the condition to False, breaking the loop ends it +immediately. + +```python +>>> while True: +... break +... print("This is never printed.") +... +>>> +``` + +## Until loops + +Python doesn't have until loops. If we need an until loop, we can use +`while not`: + +```python +raining = False +while not raining: + print("It's not raining.") + if input("Is it raining? (y/n) ") == 'y': + raining = True +print("It's raining!") +``` + +## For loops + +Let's say we have [a list](lists-and-tuples.md) of things we want to +print. To print each item in it, we could just do a bunch of prints: + +```python +stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] + +print(stuff[0]) +print(stuff[1]) +print(stuff[2]) +print(stuff[3]) +print(stuff[4]) +``` + +The output of the program is like this: + + hello + hi + how are you doing + im fine + how about you + +But this is only going to print five items, so if we add something to +stuff, it's not going to be printed. Or if we remove something from +stuff, we'll get an error saying "list index out of range". + +We could also create an index variable, and use a while loop: + +```python +>>> stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] +>>> length_of_stuff = len(stuff) +>>> index = 0 +>>> while index < length_of_stuff: +... print(stuff[index]) +... index += 1 +... +hello +hi +how are you doing +im fine +how about you +>>> +``` + +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. + +This is when for loops come in: + +```python +>>> for thing in stuff: +... # this is repeated for each element of stuff, that is, first +... # for stuff[0], then for stuff[1], etc. +... print(thing) +... +hello +hi +how are you doing +im fine +how about you +>>> +``` + +Without the comments, that's only two simple lines, and one variable. +Much better than anything else we tried before. + +```python +>>> for thing in stuff: +... print(thing) +... +hello +hi +how are you doing +im fine +how about you +>>> +``` + +Note that `for thing in stuff:` is not same as `for (thing in stuff):`. +Here the `in` keyword is just a part of the for loop and it has a +different meaning than it would have if we had `thing in stuff` without +a `for`. Trying to do `for (thing in stuff):` creates an error. + +Right now the while loop version might seem easier to understand for +you, but later you'll realize that for loops are much easier to work +with than while loops and index variables, especially in large projects. +For looping is also a little bit faster than while looping with an index +variable. + +For loops are not actually limited to lists. We can for loop over many +other things also, including strings and +[tuples](lists-and-tuples.md#tuples). For looping over a tuple gives us +its items, and for looping over a string gives us its characters as +strings of length one. + +```python +>>> for short_string in 'abc': +... print(short_string) +... +a +b +c +>>> for item in (1, 2, 3): +... print(item) +... +1 +2 +3 +>>> +``` + +If we can for loop over something, then that something is **iterable**. +Lists, tuples and strings are all iterable. + +There's only one big limitation with for looping over lists. We +shouldn't modify the list in the for loop. If we do, the results can +be surprising: + +```python +>>> stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] +>>> for thing in stuff: +... stuff.remove(thing) +... +>>> stuff +['hi', 'im fine'] +>>> +``` + +Instead, we can create a copy of stuff and loop over it. + +```python +>>> stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] +>>> for thing in stuff.copy(): +... stuff.remove(thing) +... +>>> stuff +[] +>>> +``` + +Or if we just want to clear a list, we can use the `clear` +[list method](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists): + +```python +>>> stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] +>>> stuff.clear() +>>> stuff +[] +>>> +``` + +## Summary + +- A loop means repeating something multiple times. +- While loops repeat something as long as a condition is true, and + they check the condition only in the beginning. +- For loops can be used for repeating something to each item in a list. +- An iterable is something that can be for looped over. +- The `break` keyword can be used to interrupt the innermost loop at + any time. + +## Examples + +Repeat something an endless amount of times. + +```python +message = input("What do you want me to say? ") +while True: + print(message, "!!!") +``` + +Ask the user to enter five things and print them. + +```python +things = [] + +print("Enter 5 things: ") +while len(things) < 5: + thing = input("> ") + things.append(thing) + +print("You entered these things:") +for thing in things: + print(thing) +``` + +Ask the user a bunch of questions. + +```python +questions_and_answers = [ + # [question, answer], ... + ["What is 2+4? ", "6"], + ["What is 2-4? ", "-2"], + ["What is 2*4? ", "8"], + ["What is 2/4? ", "0.5"], + # You could add more questions, but the code that asks them + # wouldn't need to be changed in any way. +] + +for qa in questions_and_answers: + while True: + if input(qa[0]) == qa[1]: + print("Correct!") + break + else: + print("That's not what I was thinking of... Try again.") +``` + +Store a list of names and let the user check if the program knows +the user. + +```python +# You can add names here so the program will know them automatically +# when it starts. +namelist = [] + +print("Options:") +print(" 0 Quit") +print(" 1 Check if I know you") +print(" 2 Introduce yourself to me") +print(" 3 Make me forget you") +print(" 4 Print a list of people I know") +print() # print an empty line + +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: + # >>> 0 == 0 + # True + # >>> '0' == '0' + # True + # >>> 0 == '0' + # False + if option == '0': + print("Bye!") + break + + elif option == '1': + name = input("Enter your name: ") + if name in namelist: + print("I know you! :D") + else: + print("I don't know you :/") + + elif option == '2': + name = input("Enter your name: ") + if name in namelist: + print("I knew you already.") + else: + namelist.append(name) + print("Now I know you!") + + elif option == '3': + name = input("Enter your name: ") + if name in namelist: + namelist.remove(name) + print("Now I don't know you.") + else: + print("I didn't know you to begin with.") + + elif option == '4': + if namelist == []: + print("I don't know anybody yet.") + else: + for name in namelist: + print(f"I know {name}!") + + else: + print("I don't understand :(") + + print() +``` + +## Exercises + +1. This code is supposed to print the numbers 1,2,3,4,5. Fix it. + + ```python + things = str([1, 2, 3, 4, 5]) + for thing in things: + print(thing) + ``` + +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]] + after = [] + for number in before: + after.append(number) + print(after) + ``` + +3. This program is supposed to convert everything in a list to integers + and then calculate their sum. It should print 6 because `1 + 2 + 3` + is 6. Fix the program. + + ```python + input = ['1', '2', '3'] + + for string in input: + numbers = [] + numbers.append(int(string)) + + result = 0 + for n in numbers: + result + n + print("their sum is", result) + ``` + +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, +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](zip-and-enumerate.md) | +[List of contents](../README.md#basics) diff --git a/basics/modules.md b/basics/modules.md new file mode 100644 index 0000000..59159cd --- /dev/null +++ b/basics/modules.md @@ -0,0 +1,501 @@ +# 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: + +```python +>>> import random +>>> random.randint(1, 3) +3 +>>> random.randint(1, 3) +1 +>>> random.randint(1, 3) +3 +>>> random.randint(1, 3) +2 +>>> random.randint(1, 3) +2 +>>> +``` + +That's cool... but how does that work? + +## What are modules? + +The first line in the example, `import random`, was an +**import statement.** But what is that random thing that it +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`. 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 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. + +You'll see a bunch of files and a few directories in the folder +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 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: + +```python +import random + +print("A random number between 1 and 3:", random.randint(1, 3)) +``` + +Now run the program. + +```python +Traceback (most recent call last): + File "random.py", line 1, in + import random + File "/home/akuli/random.py", line 4, in + print("A random number between 1 and 3:", random.randint(1, 3)) +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. + +We should end up with the same error message, and then a `>>>`. +Like this: + +```python +Traceback (most recent call last): + File "random.py", line 1, in + import random + File "/home/akuli/random.py", line 4, in + print("A random number between 1 and 3:", random.randint(1, 3)) +AttributeError: 'module' object has no attribute 'randint' +>>> +``` + +So first of all, what is that `random` variable? + +```python +>>> random + +>>> +``` + +What the heck? It's a module called random... but it's not the +`random.py` we thought it was. **Our** `random.py` has imported +itself! + +So let's go ahead and rename our file from `random.py` to +something like `ourrandom.py` and try again: + +``` +A random number between 1 and 3: 3 +``` + +There we go, now we don't have our own `random.py` so it works. + +So seems like that modules can be imported from the directory that +our Python file is in, and also from the directory that the real +`random.py` is in. But where else can they come from? + +There's a module called **sys** that contains various things built +into Python. Actually the whole module is built-in, so there's no +`sys.py` anywhere. The sys module has a list that contains all +places that modules are searched from: + +```python +>>> import sys +>>> sys + +>>> sys.path +['', + '/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'] +>>> +``` + +So that's where my Python finds its modules. The first thing in my +sys.path is an empty string, and in this case it means the current +working directory. + +## Caching modules + +Let's create a file called `hello.py` that contains a classic greeting: + +```python +print("Hello World!") +``` + +Let's go ahead and import it, and see how it works. + +```python +>>> import hello +Hello World! +>>> +``` + +Works as expected, but what happens if we try to import it again? + +```python +>>> import hello +>>> +``` + +Nothing happened at all. + +The reason why the module wasn't loaded twice is simple. In a +large project with many files it's normal to import the same +module in many files, so it gets imported multiple times. If +Python would reload the module every time it's imported, +dividing code to multiple files would make the code run slower. + +If we need to load the module again we can just exit out of Python and +launch it again. + +## Brief overview of the standard library + +The **standard library** consists of modules that Python comes +with. Here's a very brief overview of what it can do. All of +these modules can also do other things, and you can read more +about that in the official documentation. + +### Random numbers + +The official documentation is +[here](https://docs.python.org/3/library/random.html). + +```python +>>> import random +>>> random.randint(1, 3) # 1, 2 or 3 +3 +>>> colors = ['red', 'blue', 'yellow'] +>>> random.choice(colors) # choose one color +'red' +>>> random.sample(colors, 2) # choose two different colors +['yellow', 'red'] +>>> random.shuffle(colors) # mix the color list in-place +>>> colors +['yellow', 'red', 'blue'] +>>> +``` + +### Things that are built into Python + +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 +>>> print("Hello!", file=sys.stdout) # this is where prints go by default +Hello! +>>> print("Hello!", file=sys.stderr) # use this for error messages +Hello! +>>> line = sys.stdin.readline() # i will type hello and press enter +hello +>>> line +'hello\n' +>>> +>>> # information about Python's version, behaves like a tuple +>>> sys.version_info +sys.version_info(major=3, minor=7, micro=3, releaselevel='final', serial=0) +>>> sys.version_info[:3] # this is Python 3.7.3 +(3, 7, 3) +>>> +>>> sys.exit() # exit out of Python +``` + +**TODO:** why stderr instead of stdout, 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 +error we should print an error message to `sys.stderr` and then call +`sys.exit(1)`. Like this: + +```python +if something_went_wrong: + # of course, we need to make real error messages more + # informative than this example is + print("Oh crap! Something went wrong.", file=sys.stderr) + sys.exit(1) +``` + +### Mathematics + +There's no math.py anywhere, math is a built-in module like +sys. The official documentation is +[here](https://docs.python.org/3/library/math.html). + +```python +>>> import math +>>> math + +>>> math.pi # approximate value of π +3.141592653589793 +>>> math.sqrt(2) # square root of 2 +1.4142135623730951 +>>> math.radians(180) # convert degrees to radians +3.141592653589793 +>>> math.degrees(math.pi/2) # convert radians to degrees +90.0 +>>> math.sin(math.pi/2) # sin of 90 degrees or 1/2 π radians +1.0 +>>> +``` + +### Time-related things + +The official documentation for the time module is +[here](https://docs.python.org/3/library/time.html). + +```python +>>> import time +>>> time.sleep(1) # wait one second +>>> 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 +'07.04.2017 19:08:33' +>>> +``` + +You are probably wondering how `time.time()` can be used and why its +timing starts from the beginning of 1970. `time.time()` is useful for +measuring time differences because we can save its return value to a +variable before doing something, and then afterwards check how much it +changed. There's an example that does this in [the example +section](#examples). + +If you want to know why it starts from 1970 you can read something like +[this](http://stackoverflow.com/questions/1090869/why-is-1-1-1970-the-epoch-time). +See `help(time.strftime)` if you want to know about more format +specifiers like `%d`, `%m` etc. that `time.strftime` can take. + +### Operating system related things + +The module name "os" is short for "operating system", and it contains +handy functions for interacting with the operating system that Python +is running on. The official documentation is +[here](https://docs.python.org/3/library/os.html). + +```python +>>> import os +>>> os.getcwd() # short for "get current working directory" +'/home/akuli' +>>> os.mkdir('stuff') # create a folder, short for "make directory" +>>> +>>> os.path.isfile('hello.txt') # check if it's a file +True +>>> os.path.isfile('stuff') +False +>>> os.path.isdir('hello.txt') # check if it's a directory +False +>>> os.path.isdir('stuff') +True +>>> os.path.exists('hello.txt') # check if it's anything +True +>>> os.path.exists('stuff') +True +>>> +>>> # this joins with '\\' on windows and '/' on most other systems +>>> path = os.path.join('stuff', 'hello-world.txt') +>>> path +'stuff/hello-world.txt' +>>> with open(path, 'w') as f: +... # now this goes to the stuff folder we created +... print("Hello World!", file=f) +... +>>> os.listdir('stuff') # create a list of everything in stuff +['hello-world.txt'] +>>> +``` + +## Examples + +Mix a list of things. + +```python +import random + +print("Enter things to mix, and press Enter without typing", + "anything when you're done.") +things = [] +while True: + thing = input("Next thing: ") + if thing == "": + break + things.append(thing) + +random.shuffle(things) + +print("After mixing:") +for thing in things: + print(thing) +``` + +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://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals). + +```python +import time + +start = time.time() +answer = input("What is 1 + 2? ") +end = time.time() +difference = end - start + +if answer == '3': + print(f"Correct! That took {difference:.2f} seconds.") +else: + print("That's not correct...") +``` + +Wait a given number of seconds. + +```python +import sys +import time + + +answer = input("How long do you want to wait in seconds? ") +waitingtime = float(answer) +if waitingtime < 0: + print("Error: cannot wait a negative time.", file=sys.stderr) + sys.exit(1) + +print("Waiting...") +time.sleep(waitingtime) +print("Done!") +``` + +Check what a path points to. + +```python +import os +import sys + +print(f"You are currently in {os.getcwd()}.") + +while True: + path = input("A path, or nothing at all to quit: ") + if path == '': + # We could just break out of the loop, but I'll show how + # this can be done with sys.exit. The difference is that + # break only breaks the innermost loop it is in, and + # sys.exit ends the whole program. + sys.exit() + if os.path.isfile(path): + print("It's a file!") + elif os.path.isdir(path): + print("It's a folder!") + elif os.path.exists(path): + # i have no idea when this code would actually run + print("Interesting, it exists but it's not a file or a folder.") + else: + print("I can't find it :(", file=sys.stderr) +``` + +## More modules! + +Python's standard library has many awesome modules and I just +can't tell about each and every module I use here. Here's some of +my favorite modules from the standard library. Don't study them +one by one, but look into them when you think you might need them. +When reading the documentation it's usually easiest to find what +you are looking for by pressing Ctrl+F in your web browser, and +then typing in what you want to search for. + +- [argparse](https://docs.python.org/3/howto/argparse.html): + a full-featured command-line argument parser +- [collections](https://docs.python.org/3/library/collections.html), + [functools](https://docs.python.org/3/library/functools.html) and + [itertools](https://docs.python.org/3/library/itertools.html): + handy utilities +- [configparser](https://docs.python.org/3/library/configparser.html): + load and save setting files +- [csv](https://docs.python.org/3/library/csv.html): + store comma-separated lines in files +- [json](https://docs.python.org/3/library/json.html): + yet another way to store data in files and strings +- [textwrap](https://docs.python.org/3/library/textwrap.html): + break long text into multiple lines +- [warnings](https://pymotw.com/3/warnings/): + like [exceptions](exceptions.md), but they don't interrupt the + whole program +- [webbrowser](https://pymotw.com/3/webbrowser/): + open a web browser from Python + +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/) from PyPI. If you're using +Linux, then also read the "Installing into the system Python on Linux" +section at the bottom. + +## Summary + +- Most modules are files on our computers, but some of them are built + in to Python. We can use modules in our projects by importing them, + and after that using `modulename.variable` to get a variable from + the module. +- Some of the most commonly used modules are random, sys, math, time + and os. +- Avoid creating `.py` files that have the same name as a name of a + module you want to use. +- 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, +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](exceptions.md) | +[List of contents](../README.md#basics) diff --git a/the-way-of-the-program.md b/basics/the-way-of-the-program.md similarity index 64% rename from the-way-of-the-program.md rename to basics/the-way-of-the-program.md index 1621ffa..7def31a 100644 --- a/the-way-of-the-program.md +++ b/basics/the-way-of-the-program.md @@ -30,16 +30,22 @@ learned everything. ## More exercises 1. What happens if you use + between two strings, like - `"hello" + "world"`? -1. What happens if you use + between a string and an integer, like - `"hello" + 123`? -2. What happens if you use + between a float and an integer, like - `3.14 + 123`? + `"hello" + "world"`? How about `"hello" * "world"`? +2. What happens if you use + between a string and an integer, like + `"hello" + 3`? How about `"hello" * 3`? +3. What happens if you use + between a float and an integer, like + `0.5 + 3`? How about `0.5 * 3`? *** -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). +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). -[Previous](getting-started.md) | -[Next](variables.md) | -[Back to the list of contents](README.md) +You may use this tutorial freely at your own risk. See +[LICENSE](../LICENSE). + +[Previous](getting-started.md) | [Next](variables.md) | +[List of contents](../README.md#basics) diff --git a/basics/using-functions.md b/basics/using-functions.md new file mode 100644 index 0000000..c8255ff --- /dev/null +++ b/basics/using-functions.md @@ -0,0 +1,241 @@ +# Using functions + +Now we know how to make Python show text. + +```python +>>> 'Hello!' +'Hello!' +>>> +``` + +But that includes `''`. One way to show text to the user without `''` +is with the print function. In Python, printing doesn't have anything +to do with physical printers, it just means showing text on the screen. + +```python +>>> print('Hello!') +Hello! +>>> +``` + +Now we are ready for a classic example, which is also the first program +in many tutorials :) + +```python +>>> print("Hello World!") +Hello World! +>>> +``` + +But what exactly is print? + +## What are functions? + +Let's see what happens if we type `print` without the `('Hello')` part. + +```python +>>> print + +>>> +``` + +We could also type `print(print)`, it would do the same thing. Python +replied to us with some information about print wrapped in `<>`. + +As we can see, print is a function. Functions do something when they are +**called** by typing their name and parentheses. Inside the +parentheses, we can pass some arguments too. In `print("hello")` the +function is `print` and we give it one argument, which is `"hello"`. + +Functions are easy to understand, They simply **do something when they +are called**. Functions run immediately when we call them, so the +text appears on the screen right away when we run `print(something)`. + +Sometimes people think that doing `thingy = print('hello')` means that +Python is going to print hello every time we type `thingy`. But **this +is not correct**! `print('hello')` runs print right away, and if we +type `thingy` later it's not going to run `print('hello')` again. + +## Return values + +Now we know that `thingy = print('hello')` doesn't store the +`print('hello')` call in a variable. But what does it do then? + +```python +>>> thingy = print('hello') +hello +>>> print(thingy) # thingy is now None +None +>>> +``` + +So doing `thingy = print('hello')` set `thingy` to None. + +Here's what happened, explained in more detail: + +- When we do `thingy = print('hello')`, the right side is processed + first. +- `print('hello')` calls the print function with the argument + `'hello'`. +- The function runs. It shows the word hello. +- The print function **returns** None. All functions need to return + something, and print returns None because there's no need to return + anything else. +- Now the right side has been processed. `print('hello')` returned + None, so we can imagine we have None instead of `print('hello')` + there, and the assignment now looks like `thingy = None`. +- `thingy` is now None. + +Now we understand what **a return value** is. When we call the +function, Python "replaces" `function(arguments)` with whatever the +function returns. + +Calling a function without assigning the return value to anything (e.g. +`print('hello')` instead of `thingy = print('hello')`) simply throws away +the return value. The interactive `>>>` prompt doesn't echo the return +value back because it's None. + +Of course, `thingy = print('hello')` is useless compared to `print('hello')` +because the print function always returns None and we can do `thingy = None` +without any printing. + +Not all functions return None. The input function can be used for +getting a string from the user. + +```python +>>> stuff = input("Enter something:") +Enter something:hello +>>> stuff +'hello' +>>> +``` + +`input("Enter something:")` showed the text `Enter something:` on the +screen and waited for me to type something. I typed hello and pressed +Enter. Then input returned the hello I typed as a string and it was +assigned to `stuff`. + +Usually we want to add a space after the `:`, like this: + +```python +>>> stuff = input("Enter something: ") # now there's space between : and where i type +Enter something: hello +>>> +``` + +## Handy things about print + +We can also print an empty line by calling print without any +arguments. + +```python +>>> print() + +>>> +``` + +In Python, `\n` is a newline character. Printing a string that contains +a newline character also prints a newline: + +```python +>>> print('hello\nworld') +hello +world +>>> +``` + +If we want to print a real backslash, we need to **escape** it by typing +two backslashes. + +[comment]: # (For some reason, GitHub's syntax highlighting doesn't) +[comment]: # (work here.) + + >>> print('hello\\nworld') + hello\nworld + >>> + +We can also pass multiple arguments to the print function. We need to +separate them with commas and print will add spaces between them. + +```python +>>> print("Hello", "World!") +Hello World! +>>> +``` + +Unlike with `+`, the arguments don't need to be strings. + +```python +>>> print(42, "is an integer, and the value of pi is", 3.14) +42 is an integer, and the value of pi is 3.14 +>>> +``` + +## Variables names and built-in things + +In [the previous chapter](variables.md) we learned that `if` is not a +valid variable name because it's a keyword. + +```python +>>> if = 123 + File "", line 1 + if = 123 + ^ +SyntaxError: invalid syntax +>>> +``` + +But `print` and `input` are not keywords, so can we use them as +variable names? + +```python +>>> print = "hello" +>>> print +'hello' +>>> +``` + +We can, but there's a problem. Now we can't even do our hello world! + +```python +>>> print("Hello World!") +Traceback (most recent call last): + File "", line 1, in +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](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. + +## Summary + +- `function()` calls a function without any arguments, and + `function(1, 2, 3)` calls a function with 1, 2 and 3 as arguments. +- When a function is called, it does something and returns something. +- `function(arguments)` is "replaced" with the return value in the code + that called it. For example, `stuff = function()` calls a function, + and then does `stuff = the_return_value` and the return value ends + up in stuff. +- Python comes with `print` and `input`. They are built-in functions. +- Avoid variable names that conflict with built-in functions. + +*** + +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](editor-setup.md) | +[List of contents](../README.md#basics) diff --git a/basics/variables.md b/basics/variables.md new file mode 100644 index 0000000..f1a8030 --- /dev/null +++ b/basics/variables.md @@ -0,0 +1,314 @@ +# Variables, Booleans and None + +## Variables + +Variables are easy to understand. They simply **point to values**. + +```python +>>> a = 1 # create a variable called a that points to 1 +>>> b = 2 # create another variable +>>> a # get the value that the variable points to +1 +>>> b +2 +>>> +``` + +Let's draw a diagram of these variables. + +![Variable diagram](../images/variables1.png) + +We can also change the value of a variable after setting it. + +```python +>>> a = 2 # make a point to 2 instead of 1 +>>> a +2 +>>> +``` + +So now our diagram looks like this: + +![Variable diagram](../images/variables2.png) + +Setting a variable to another variable gets the value of the other +variable and sets the first variable to point to that value. + +```python +>>> a = 1 +>>> b = a # this makes b point to 1, not a +>>> a = 5 +>>> b # b didn't change when a changed +1 +>>> +``` + +Trying to access a variable that is not defined creates an error +message. + +```python +>>> thingy +Traceback (most recent call last): + File "", line 1, in +NameError: name 'thingy' is not defined +>>> +``` + +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. +- Multiple variables can point to the same value, but one variable + 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). + +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 are case-sensitive, like many other things in Python. + +```python +>>> thing = 1 +>>> THING = 2 +>>> thIng = 3 +>>> thing +1 +>>> THING +2 +>>> thIng +3 +>>> +``` + +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. + +```python +>>> if = 123 + File "", line 1 + if = 123 + ^ +SyntaxError: invalid syntax +>>> +``` + +When assigning something to a variable using a `=`, the right side of +the `=` is always executed before the left side. This means that we can +do something with a variable on the right side, then assign the result +back to the same variable on the left side. + +```python +>>> a = 1 +>>> a = a + 1 +>>> a +2 +>>> +``` + +To do something to a variable (for example, to add something to it) we +can also use `+=`, `-=`, `*=` and `/=` instead of `+`, `-`, `*` and +`/`. The "advanced" `%=`, `//=` and `**=` also work. + +```python +>>> a += 2 # a = a + 2 +>>> a -= 2 # a = a - 2 +>>> a *= 2 # a = a * 2 +>>> a /= 2 # a = a / 2 +>>> +``` + +This is not limited to integers. + +```python +>>> a = 'hello' +>>> a *= 3 +>>> a += 'world' +>>> a +'hellohellohelloworld' +>>> +``` + +Now we also understand why typing hello to the prompt didn't work in +the beginning of this tutorial. But we can assign something to a +variable called hello and then type hello: + +```python +>>> hello = 'hello there' +>>> hello +'hello there' +>>> +``` + +## 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 +other programming languages, `=` is assigning and `==` is comparing. +`a = 1` sets a to 1, and `a == 1` checks if a equals 1. + +```python +>>> a = 1 +>>> a == 1 +True +>>> a = 2 +>>> a == 1 +False +>>> +``` + +`a == 1` is the same as `(a == 1) == True`, but `a == 1` is more +readable, so most of the time we shouldn't write `== True` anywhere. + +```python +>>> a = 1 +>>> a == 1 +True +>>> (a == 1) == True +True +>>> a = 2 +>>> a == 1 +False +>>> (a == 1) == True +False +>>> +``` + +## None + +None is Python's "nothing" value. It behaves just like any other value, +and it's often used as a default value for different kinds of things. +Right now it might seem useless but we'll find a bunch of ways to use +None later. + +None's behavior on the interactive prompt might be a bit confusing at +first: + +```python +>>> thingy = None +>>> thingy +>>> +``` + +That was weird! We set thingy to None, but typing `thingy` didn't echo +back None. + +This is because the prompt never echoes back None. That is handy, +because many things result in None, and it would be annoying to see +None coming up all the time. + +If we want to see a None on the interactive prompt, we can use print. + +```python +>>> print(thingy) +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. We'll learn more about what attributes +and calling are later. + +```python +>>> None.hello # None has no attribute 'hello' +Traceback (most recent call last): + File "", line 1, in +AttributeError: 'NoneType' object has no attribute 'hello' +>>> None() # None is not callable +Traceback (most recent call last): + File "", line 1, in +TypeError: 'NoneType' object is not callable +>>> +``` + +## Other comparing operators + +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 | +|-----------|-----------------------------------|-----------------------| +| `a == b` | a is equal to b | `1 == 1` | +| `a != b` | a is not equal to b | `1 != 2` | +| `a > b` | a is greater than b | `2 > 1` | +| `a >= b` | a is greater than or equal to b | `2 >= 1`, `1 >= 1` | +| `a < b` | a is less than b | `1 < 2` | +| `a <= b` | a is less than or equal to b | `1 <= 2`, `1 <= 1` | + +We can also combine multiple comparisons. This table assumes that a and +b are Booleans. + +| Usage | Description | True example | +|-----------|-------------------------------------------|-----------------------------------| +| `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` | + +`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, +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](the-way-of-the-program.md) | [Next](using-functions.md) | +[List of contents](../README.md#basics) diff --git a/basics/what-is-programming.md b/basics/what-is-programming.md new file mode 100644 index 0000000..269ce3f --- /dev/null +++ b/basics/what-is-programming.md @@ -0,0 +1,170 @@ +# What is programming? + +**Feel free to [skip this part](#how-to-read-this-tutorial) if you +already know everything it's talking about.** + +As a computer user you know that computers don't have feelings. They +don't work any faster or slower depending on if we're angry at them or +if we're happy. Computers can perform millions of calculations per +second, but they require us to tell them exactly what to do. If they do +something else than we want them to do the problem is usually that they +don't understand our instructions the way we understand them. + +The only big difference between programming and what you're familiar +with already is that instead of clicking buttons to do things we write +the instructions using a **programming language**. Most programming +languages consist of English words, digits and some characters that have +special meanings. + +Unlike people often think, programming is usually not complicated. Large +programs are always made of **small, simple pieces**, and those pieces +are written one by one. Programming languages are made to be used by +humans, so if there's an easy way to do something and a difficult way to +do something, you should use the easier way. + +## What do I need? + +First of all, **you don't need to be good at math**. Some programmers +are good at math, some are not. Programming and math are two separate +things and being good or bad at one doesn't mean you are automatically +good or bad at the other. + +You also don't need a powerful computer. I could do almost all of my +programming on a 12-year-old computer if I needed to. Fast computers are +nice to work with, but you don't need them. + +Programming takes time like all hobbies do. Some people learn it +quickly, and some people don't. I don't expect you to read this tutorial +in a couple hours and then master everything it's talking about. Take +your time with things, and remember that I learned to program slowly. + +## Getting started + +This tutorial uses a programming language called Python because it's +easy to learn and we can do many different things with it. For example, +we can create our own applications that have buttons that people can +click instead of just using applications written by others. + +Before we can get started with Python we need to know how to write some of +Python's special characters with our keyboards. Unfortunately I don't know +which keys you need to press to produce these characters because your keyboard +is probably different than mine. But the keyboard can tell what you +need to press. For example, my Finnish keyboard has a key like this: + +![A key on my keyboard.](../images/key.png) + +Here's what the characters on this key mean: + +- I can type a number 7 by pressing this key without holding down other keys + at the same time. +- I can type a `/` character by holding down the shift key (on the left edge + of the keyboard, between Ctrl and CapsLock) and pressing this key. +- I can type a `{` character by holding down AltGr (on the bottom of the + keyboard, on the right side of the spacebar) and pressing this key. + Holding down Ctrl and Alt instead of AltGr may also work. + +The only key that doesn't have anything written on it is spacebar. It's the +big, wide key that's closest to you. Another key that's used for producing +whitespace is tab, the key above CapsLock. + +In this tutorial we need to know how to type these characters. We'll learn +their meanings later. + +| Character | Names | +|-----------|---------------------------------------| +| `+` | plus | +| `-` | minus, dash | +| `_` | underscore | +| `*` | star, asterisk | +| `/` | forwardslash (it's leaning forward) | +| `\` | backslash (it's leaning back) | +| `=` | equals sign | +| `%` | percent sign | +| `.` | dot | +| `,` | comma | +| `:` | colon | +| `?` | question mark | +| `!` | exclamation mark | +| `<` `>` | less-than and greater-than signs | +| `'` `"` | single quote and double quote | +| `#` | hashtag | +| `()` | parentheses | +| `[]` | square brackets, brackets | +| `{}` | curly braces, braces, curly brackets | + +That may seem like many characters, but you probably know many of them already +so it shouldn't be a problem. + +## How to read this tutorial + +I've done my best to make this tutorial as easy to follow as possible. Other +people have commented on this and helped me improve this a lot also. But what +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 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 + tutorial so other readers won't have the same problem as you have. +5. See [Getting help](../getting-help.md) if you can't contact me for some + reason. + +You are free to combine this tutorial with other learning resources. If this +tutorial isn't exactly what you're looking for you don't need to stick with +nothing but this. You can find another tutorial and mix the tutorials however +you want as long as you **make sure that you understand everything you read**. + +One of the most important things with learning to program is to **not +fear mistakes**. If you make a mistake, your computer will not break in +any way. You'll get an error message that tells you what's wrong and +where. Even professional programmers do mistakes and get error messages +all the time, and there's nothing wrong with it. + +If you want to know what some piece of code in this tutorial does just +**try it and see**. It's practically impossible to break anything +accidentally with the things you will learn by reading this tutorial, +so you are free to try out all the examples however you want and change +them to do whatever you want. + +Even though a good tutorial is an important part about learning to +program, you also need to learn to make your own things. Use what you +have learned, and create something with it. + +## But reading is boring! + +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 + +- Now you should know what programming and programming languages are. +- You don't need to be good at math and you don't need a new computer. +- Complicated programs consist of simple pieces. +- You don't need to remember how to type different characters. Just find the + character on your keyboard and press the key, holding down shift or AltGr + as needed. +- Make sure you understand everything you read. +- Experiment with things freely and don't fear mistakes. +- 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, +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](../README.md) | [Next](installing-python.md) | +[List of contents](../README.md#basics) diff --git a/basics/what-is-true.md b/basics/what-is-true.md new file mode 100644 index 0000000..a344239 --- /dev/null +++ b/basics/what-is-true.md @@ -0,0 +1,226 @@ +# What is true? + +Now we understand how code like this works. + +```python +message = input("Enter something: ") +if message == '': + print("You didn't enter anything!") +else: + print("You entered:", message) +``` + +But most Python programmers would write that code like this +instead: + +```python +message = input("Enter something: ") +if message: + print("You entered:", message) +else: + print("You didn't enter anything!") +``` + +What the heck was that? We did `if message`, but `message` +was a string, not True or False! + +Python converted our message to a Boolean and then checked if +the Boolean it ended up with was True. But when will it be true? + +## Converting to Booleans + +The `if message:` actually did the same thing as `if bool(message)`, +which is same as `if bool(message) == True:`. Usually we just don't +write the `==True` part anywhere because we don't need it. + +We can convert things to Booleans like Python did by doing +`bool(things)`. Let's try that with strings. + +```python +>>> bool('hello') +True +>>> bool('there') +True +>>> bool('True') +True +>>> bool('False') # this isn't special in any way +True +>>> +``` + +As we can see, the Boolean value of most strings is True. The +only string that has a false Boolean value is the empty string, +`''` or `""`: + +```python +>>> bool('') +False +>>> +``` + +Most other things are also treated as False if they're empty and +True if they're not empty. + +```python +>>> bool([1, 2, 3]) +True +>>> bool([]) +False +>>> bool((1, 2, 3)) +True +>>> bool(()) +False +>>> bool({'a': 1, 'b': 2}) +True +>>> bool({}) +False +>>> +``` + +None and zero are also falsy, but positive and negative numbers +are treated as True. + +```python +>>> bool(None) +False +>>> bool(0) +False +>>> bool(0.0) +False +>>> bool(1) +True +>>> bool(-1) +True +>>> +``` + +Most other things are also treated as True. + +```python +>>> bool(OSError) +True +>>> bool(print) +True +>>> +``` + +## When and why should we use Boolean values of things? + +It's recommended to rely on the Boolean value when we're doing +something with things like lists and tuples. This way our code +will work even if it gets a value of a different type than we +expected it to get originally. + +For example, this code doesn't work right if we give it +something else than a list. It thinks that empty tuples, +strings and dictionaries aren't empty just because they aren't +empty lists: + +```python +>>> def is_this_empty(thing): +... if thing == []: +... print("It's empty!") +... else: +... print("It's not empty.") +... +>>> is_this_empty([1, 2, 3]) +It's not empty. +>>> is_this_empty([]) +It's empty! +>>> is_this_empty(()) +It's not empty. +>>> is_this_empty('') +It's not empty. +>>> is_this_empty({}) +It's not empty. +>>> +``` + +We could improve the code by checking against different empty +things. + +```python +>>> def is_this_empty(thing): +... if thing == [] or thing == () or thing == '' or thing == {}: +... print("It's empty!") +... else: +... print("It's not empty.") +... +>>> +``` + +But Python has many other data types that can be empty and we +haven't talked about in this tutorial. Trying to check all of +them would be pointless because functions like this already +work with all of them: + +```python +>>> def is_this_empty(thing): +... if thing: +... print("It's not empty.") +... else: +... print("It's empty!") +... +>>> +``` + +There's also cases when we should not rely on the Boolean value. +When we're doing things with numbers and None it's best to +simply compare to None or zero. Like this: + +```python +if number != 0: + print("number is not zero") + +if value is not None: + print("value is not None") +``` + +Not like this: + +```python +if number: + print("number is not zero") + +if value: + print("value is not None") +``` + +We used `is not` instead of `!=` in the first example because +the official style guide recommends it. The reason is that it's +possible to create a value that isn't really None but seems like +None when we compare it with None using `==` or `!=`, and we want +to make sure that we don't treat values like that as None. + +So here's how we should check if something is None: + +```python +if not value: ... # not good if we want to check if it's None +if value == None: ... # better +if value is None: ... # best +``` + +## Summary + +- `if thing:` does the same thing as `if bool(thing):`. This also + works with while loops and most other things that are usually used + with Booleans. +- `bool()` of most things is True, but `bool()` values of None, + zero and most empty things are False. +- Use `is` and `is not` when comparing to None, `==` and `!=` when + checking if a number is zero and rely on the Boolean value + when checking if something is empty. + +*** + +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](larger-program.md) | [Next](files.md) | +[List of contents](../README.md#basics) 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 2eae318..0306c61 100644 --- a/classes.md +++ b/classes.md @@ -1,421 +1,14 @@ -# Defining and using custom classes in Python - -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 and how to -create your own functions with the `def` keyword. If you don't, I -highly recommend learning that first, and then moving to classes. - -## Why should I use custom classes in my projects? - -Python comes with a lot of classes that you are already familiar with. - -```py ->>> str - ->>> int - ->>> list - ->>> dict - ->>> -``` - -Calling these classes as if they were functions makes a new **instance** -of them. For example, `str()` makes a `str` instance, also known as a -string. - -```py ->>> str() -'' ->>> int() -0 ->>> list() -[] ->>> dict() -{} ->>> -``` - -We can also get an instance's class with `type()`: - -```py ->>> type('') - ->>> type(0) - ->>> type([]) - ->>> 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**. - -## First class - -In Python, `pass` does nothing. - -```py ->>> pass ->>> -``` - -Let's use it to define an empty class. - -```py ->>> class Website: -... pass -... ->>> Website - ->>> -``` - -_**Note:** If you are using Python 2 I highly recommend using -`class Website(object):` instead of `class Website:`. This creates a -new-style class instead of an old-style class. Old-style classes are -different than new-style classes in some ways and not supported in this -tutorial. In Python 3, there are no old-style classes and -`class Website(object):` does the same thing as `class Website:`._ - -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 -names for your classes. - -Now we can make a Website instance by calling the class. - -```py ->>> stackoverflow = Website() ->>> stackoverflow -<__main__.Website object at 0x7f36e4c456d8> ->>> type(stackoverflow) - ->>> -``` - -We can attach more information about stackoverflow to the new Website -instance. - -```py ->>> stackoverflow.url = 'http://stackoverflow.com/' ->>> stackoverflow.founding_year = 2008 ->>> stackoverflow.free_to_use = True ->>> -``` - -We can also access the information easily. - -```py ->>> stackoverflow.url -'http://stackoverflow.com/' ->>> stackoverflow.founding_year -2008 ->>> stackoverflow.free_to_use -True ->>> -``` - -`url`, `founding_year` and `free_to_use` are not variables, they are -**attributes**. More specifically, they are **instance attributes**. -The biggest difference is that variables are accessed by their name, -and attributes are accessed by typing a name of an object (like -stackoverflow), then a dot and then the name of the attribute. - -If we make another Website, does it have the same `url`, `founding_year` -and `free_to_use`? - -```py ->>> effbot = Website() ->>> effbot.url -Traceback (most recent call last): - File "", line 1, in -AttributeError: 'Website' object has no attribute 'url' ->>> -``` - -It doesn't. We'd need to define the attributes for effbot also. - -The attributes are stored in a dictionary called `__dict__`. It's not -recommended to use it for code that needs to be reliable, but it's a -handy way to see which attributes the instance contains. - -```py ->>> stackoverflow.__dict__ -{'free_to_use': True, - 'founding_year': 2008, - 'url': 'http://stackoverflow.com/'} ->>> effbot.__dict__ -{} ->>> -``` - -## Class attributes - -What happens if we set an attribute of the `Website` class to some value -instead of doing that to an instance? - -```py ->>> Website.is_online = True ->>> Website.is_online -True ->>> -``` - -Seems to be working, but what happened to the instances? - -```py ->>> stackoverflow.is_online -True ->>> effbot.is_online -True ->>> -``` - -What was that? Setting `Website.is_online` to a value also set -`stackoverflow.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 -the `Website` class. - -```py ->>> stackoverflow.__dict__ -{'free_to_use': True, - 'founding_year': 2008, - 'url': 'http://stackoverflow.com/'} ->>> effbot.__dict__ -{} ->>> -``` - -`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 -confusing, which is why it's not recommended to use class attributes like -this. Use instance attributes instead, e.g. `stackoverflow.is_online = True`. - -## Functions and methods - -Let's define a function that prints information about a website. - -```py ->>> def website_info(website): -... print("URL:", website.url) -... print("Founding year:", website.founding_year) -... print("Free to use:", website.free_to_use) -... ->>> website_info(stackoverflow) -URL: http://stackoverflow.com/ -Founding year: 2008 -Free to use: True ->>> -``` - -Seems to be working. We should be able to get information about all -websites, so maybe we should attach the `website_info` function to the -Website class? - -```py ->>> Website.info = website_info ->>> Website.info(stackoverflow) -URL: http://stackoverflow.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? - -```py ->>> stackoverflow.info() -URL: http://stackoverflow.com/ -Founding year: 2008 -Free to use: True ->>> -``` - -What the heck happened? We didn't define a `stackoverflow.info`, it just -magically worked! - -`Website.info` is our `website_info` function, so `stackoverflow.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()`! - -But is `stackoverflow.info` the same thing as `Website.info`? - -```py ->>> Website.info - ->>> stackoverflow.info -> ->>> -``` - -It's not. - -Instead, `stackoverflow.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. - -In other words, `Class.method(instance)` does the same thing as -`instance.method()`. This also works with built-in classes, for -example `'hello'.lower()` is same as `str.lower('hello')`. - -## Defining methods when defining the class - -Maybe we could define a method when we make the class instead of adding -it later? - -```py ->>> class Website: -... -... def info(self): # self is a Website instance -... 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/ -Founding year: 2008 -Free to use: True ->>> -``` - -It's working. The `self` argument in `Website.info` was `stackoverflow`. -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. - -We still need to set `url`, `founding_year` and `free_to_use` manually. -Maybe we could add a method to do that? - -```py ->>> class Website: -... -... def initialize(self, url, founding_year, free_to_use): -... self.url = url -... self.founding_year = founding_year -... self.free_to_use = free_to_use -... -... def info(self): -... print("URL:", self.url) -... 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/ -Founding year: 2008 -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`. - -But we still need to call `stackoverflow.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. - -```py ->>> class Website: -... -... def __init__(self, url, founding_year, free_to_use): -... self.url = url -... self.founding_year = founding_year -... self.free_to_use = free_to_use -... -... def info(self): -... print("URL:", self.url) -... 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/ -Founding year: 2008 -Free to use: True ->>> -``` - -Classes have many other magic methods too, but I'm not going to cover -them in this tutorial. - -## When should I use classes? - -Don't do this: - -```py -class MyProgram: - - def __init__(self): - print("Hello!") - word = input("Enter something: ") - print("You entered " + word + ".") - - -program = MyProgram() -``` - -Usually you shouldn't use a class if you're only going to make one -instance of it, and you don't need a class either if you're only going -to have one method. In this example `MyProgram` has only one method and -only one instance. - -Make functions instead, or just write your code without any functions if -it's short enough for that. This program does the same thing and it's -much more readable: - -```py -print("Hello!") -word = input("Enter something: ") -print("You entered " + word + ".") -``` - -## Important things - -Here are some of the most important things covered in this tutorial. -Make sure you understand them. - -- Object-orientated programming is programming with custom data types. - In Python that means using classes and instances. -- Use CapsWords for class names and lowercase_words_with_underscores for - other names. This makes it easy to see which objects are classes and - which objects are instances. -- Calling a class as if it was a function makes a new instance of it. -- `foo.bar = baz` sets `foo`'s attribute `bar` to `baz`. -- Use class attributes for functions and instance attributes for other - things. -- Functions as class attributes can be accessed as instance methods. - They get their instance as the first argument. Call that `self` when - you define the method. -- `__init__` is a special method, and it's ran when a new instance of a - class is created. It does nothing by default. -- Don't use classes if your code is easier to read without them. +This file has been moved [here](basics/classes.md). *** -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). +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). -[Back to the list of contents](README.md) +[List of contents](./README.md#list-of-contents) diff --git a/common.py b/common.py new file mode 100644 index 0000000..7180739 --- /dev/null +++ b/common.py @@ -0,0 +1,156 @@ +# 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 + +"""Things that other scripts import and use. + +The markdown files use / as a path separator. That's why they need the +posixpath module for processing paths, but they use functions in this +file when actually opening files. +""" + +import contextlib +import itertools +import os +import posixpath +import re +import shutil +import string + + +_LINK_REGEX = r'!?\[(.*?)\]\((.*?)\)' + + +def find_links(file): + """Find all markdown links in a file object. + + Yield (lineno, regexmatch) tuples. + """ + # don't yield same link twice + seen = set() + + # we need to loop over the file two lines at a time to support + # multi-line (actually two-line) links, so this is kind of a mess + firsts, seconds = itertools.tee(file) + next(seconds) # first line is never second line + + # we want 1-based indexing instead of 0-based and one-line links get + # caught from linepair[1], so we need to start at two + for lineno, linepair in enumerate(zip(firsts, seconds), start=2): + lines = linepair[0] + linepair[1] + for match in re.finditer(_LINK_REGEX, lines, flags=re.DOTALL): + if match.group(0) not in seen: + seen.add(match.group(0)) + yield match, lineno + + +def get_markdown_files(): + """Yield the names of all markdown files in this tutorial. + + The yielded paths use / as the path separator. + """ + 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): + """Ask a yes/no question and return True or False. + + The default answer is yes if default is True and no if default is + False. + """ + if default: + # yes by default + question += ' [Y/n] ' + else: + # no by default + question += ' [y/N] ' + + while True: + result = input(question).upper().strip() + if result == 'Y': + return True + if result == 'N': + return False + if not result: + return default + print("Please type y, n or nothing at all.") + + +@contextlib.contextmanager +def backup(filename): + """A context manager that backs up a file.""" + shutil.copy(filename, filename + '.backup') + try: + yield + except Exception: + # It failed, we need to restore from the backup. + shutil.copy(filename + '.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 a8075c6..eff6885 100644 --- a/contact-me.md +++ b/contact-me.md @@ -13,11 +13,18 @@ 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. *** -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). +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). -[Back to the list of contents](README.md) +You may use this tutorial freely at your own risk. See +[LICENSE](./LICENSE). + +[List of contents](./README.md#list-of-contents) diff --git a/defining-functions.md b/defining-functions.md deleted file mode 100644 index 497d58c..0000000 --- a/defining-functions.md +++ /dev/null @@ -1,132 +0,0 @@ -# Defining your own functions - -It's probably been a while since you read about using functions. [Read -about it again](using-functions.md) if you need to. - -## Why should I use custom functions? - -Have a look at this code: - -```py -print("************") -print("Hello World!") -print("************") -print() - -print("********************") -print("Enter your password:") -print("********************") -print() - -word = input() - -if word == 'python': - print("********") - print("Welcome!") - print("********") - print() -else: - print("**************") - print("Access denied.") - print("**************") - print() -``` - -Then compare it to this code: - -```py -print_box("Hello World!") -print_box("Enter your password:") -word = input() -if word == 'python': - print_box("Welcome!") -else: - print_box("Access denied.") -``` - -That's nice, but where do we a box function like that? - -## First functions - -The `pass` keyword does nothing. - -```py ->>> pass ->>> -``` - -Let's use it to define a function that does nothing. - -```py ->>> def do_nothing(): -... pass -... ->>> do_nothing - ->>> -``` - -Seems to be working so far, we have a function. Let's see what happens -if we call it. - -```py ->>> do_nothing() ->>> -``` - -There we go. It did nothing at all. - -Maybe we could just do something in the function instead? - -```py ->>> def print_hi(): -... print("Hi!") -... ->>> print_hi() -Hi! ->>> -``` - -It's working. How about printing a variable in the function? - -```py ->>> message = "Hello World!" ->>> print_message() -Hello World! ->>> -``` - -Again, it works. How about setting a variable in the function? - -```py ->>> def set_username(): -... username = 'me' -... ->>> set_username() ->>> username -Traceback (most recent call last): - File "", line 1, in -NameError: name 'username' is not defined ->>> -``` - -That was weird! Why didn't that work? - -## Locals and globals - -So far we have used nothing but **global variables**. They are called -globals because the same variables are available anywhere in our -program, even in functions. - -```py ->>> a = 1 ->>> b = "hi" ->>> c = "hello there" ->>> def print_abc(): -... print(a, b, c) -... ->>> print_abc() -1 hi hello there ->>> -``` - diff --git a/editor-setup.md b/editor-setup.md deleted file mode 100644 index d2bf94e..0000000 --- a/editor-setup.md +++ /dev/null @@ -1,121 +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](installing-python.md) 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 -Write 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. - -### 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: - -```py -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. - -### 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). - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Back to the list of contents](README.md) diff --git a/getting-help.md b/getting-help.md index 7dac8a8..e56e7a2 100644 --- a/getting-help.md +++ b/getting-help.md @@ -3,56 +3,66 @@ 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. + -Do this: +## Discord - i'm trying to check if this variable equals one but i keep - getting an error http://dpaste.com/yourpaste +If you have a discord account, you can click the "Explore Public Servers" button at bottom left. -Don't do this: +![Discord's explore public servers button](images/discord-explore.png) + +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. *** -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). +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). -[Back to the list of contents](README.md) +[List of contents](./README.md#list-of-contents) diff --git a/getting-started.md b/getting-started.md deleted file mode 100644 index e9a19f3..0000000 --- a/getting-started.md +++ /dev/null @@ -1,207 +0,0 @@ -# Getting started with Python - -[Launch Python](installing-python.md). - -The `>>>` means that Python is ready and you can enter a command. The -basic idea is really simple: enter a command, press Enter, enter another -command, press Enter and keep going. - -You probably don't know any Python commands yet. Let's see what happens -if we just write something and press Enter. - -```py ->>> hello -Traceback (most recent call last): - File "", line 1, in -NameError: name 'hello' is not defined ->>> -``` - -Oops! That didn't work. But like I wrote in the -[introduction](introduction.md), **errors don't matter**. - -Maybe we can press Enter without typing anything? - -```py ->>> ->>> ->>> ->>> -``` - -That worked. How about numbers? - -```py ->>> 123 -123 ->>> -123 --123 ->>> 3.14 -3.14 ->>> -12.3 --12.3 ->>> -``` - -There we go, it echoes them back. - -In some countries, decimal numbers are written with a comma, like `3,14` -instead of `3.14`. Maybe Python knows that? - -```py ->>> 3,14 -(3, 14) ->>> -``` - -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. - -What if we type a `#`? - -```py ->>> # ->>> -``` - -Nothing happened at all. Maybe we can type a `#` and then some text -after it? - -```py ->>> # hello there ->>> -``` - -Again, nothing happened. - -In Python, these pieces of text starting with a `#` are known as -**comments**. They don't effect the code in any way, but you can use -them to explain what your code does. - -## Using Python as a calculator - -Maybe we could type mathematical statements? - -```py ->>> 17 + 3 -20 ->>> 17 - 3 -14 ->>> 17 * 3 -51 ->>> 17 / 3 -5.666666666666667 ->>> -``` - -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. - -```py ->>> 14 + 2 + 1 -17 ->>> 14 +2+ 1 -17 ->>> -``` - -The evaluation order is similar to math. The parentheses `(` and `)` -also work the same way. - -```py ->>> 1 + 2 * 3 # 2 * 3 is calculated first -7 ->>> (1 + 2) * 3 # 1 + 2 is calculated first -9 ->>> -``` - -Square brackets `[]` and curly brackets `{}` cannot be used to change -the evaluation order. They do something completely else. - -```py ->>> [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' ->>> -``` - -## More advanced math - -I decided to include this in my tutorial because some people might be -interested in this. Feel free to skip this if you're not interested. - -The `//` operator will divide and round down to an integer. For example, -`17 / 3` is `5.666666666666667`, and so `17 // 3` is `5`. - -```py ->>> 17 // 3 -5 ->>> -``` - -The `%` operator gets the division remainder. - -```py ->>> 17 % 3 -2 ->>> -``` - -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. - -```py ->>> 17 // 3 -5 ->>> 17 % 3 -2 ->>> -``` - -This is also useful for converting time from minutes to seconds. 500 -seconds is 8 minutes and 20 seconds. - -```py ->>> 500 // 60 -8 ->>> 500 % 60 -20 ->>> -``` - -`**` can be used to raise to a power, so 3² in math is `3**2` in Python. -Powers are calculated before `*` and `/`, but after `()`. - -```py ->>> 2 ** 3 -8 ->>> 2 * 3 ** 2 # 3 ** 2 is calculated first -18 ->>> (2 * 3) ** 2 # 2 * 3 is calculated first -36 ->>> -``` - -## Summary - -- Errors don't matter. -- You 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. - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Previous](installing-python.md) | -[Next](the-way-of-the-program.md) | -[Back to the list of contents](README.md) 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/if.md b/if.md deleted file mode 100644 index 8c0ad10..0000000 --- a/if.md +++ /dev/null @@ -1,262 +0,0 @@ -# If, else and elif - -## Using if statements - -Now we know what True and False are. - -```py ->>> 1 == 1 -True ->>> 1 == 2 -False ->>> ->>> its_raining = True ->>> its_raining -True ->>> -``` - -But what if we want to execute different code depending on something? -That's when `if` comes in. - -```py ->>> its_raining = True ->>> if its_raining: -... print("It's raining!") -... -It's raining! ->>> its_raining = False ->>> if its_raining: -... print("It's raining!") # nothing happens -... ->>> -``` - -The prompt changed from `>>>` to `...`. It meant that Python was -expecting me to keep typing. When I was done, I just pressed Enter -twice. My code was execute and the prompt was restored to `>>>`. IDLE -does this a bit differently, so if you use IDLE, running this example -code looks more like this: - -```py ->>> its_raining = True ->>> if its_raining: - print("It's raining!") - - -It's raining! ->>> -``` - -At this point it's easier to put your 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](idle-new.png) - -If you don't use IDLE, please take the time to -[set up your editor correctly](editor-setup.md). - -Create a file called `rain.py`, and type the following content into it: - -```py -its_raining = True -if its_raining: - print("It's raining!") -``` - -The file extension is `.py`, which is short for Python. - -The line with a print is **indented** by four spaces. Indentation is -important in Python. If the indentation is not consistent, we may get -an error or our program may do something else than we wanted it to. - -Now run the rain program. Most editors (including IDLE) will run your -code when you 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 you want to print a different message if it's not raining? You -could do something like this: - -```py -its_raining = True # you can change this to False -its_not_raining = not its_raining # False if its_raining, True otherwise - -if its_raining: - print("It's raining!") -if its_not_raining: - print("It's not raining.") -``` - -Now your program will print a different value depending on what the -value of `its_raining` is. - -You can also add `not its_raining` directly to the second if statement: - -```py -its_raining = True - -if its_raining: - print("It's raining!") -if not its_raining: - print("It's not raining.") -``` - -But you can make it even better by using `else`. - -```py -its_raining = True - -if its_raining: - print("It's raining!") -else: - print("It's not raining.") -``` - -By combining that with the input function we can make a program that -asks for a password and checks if it's correct. - -```py -print("Hello!") -password = input("Enter your password: ") - -if password == "secret": - print("That's correct, welcome!") -else: - print("Access denied.") -``` - -The program prints different things depending on what you enter. - - >>> ================================ RESTART ================================ - >>> - Hello! - Enter your password: secret - Welcome! - >>> ================================ RESTART ================================ - >>> - 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 -a password from the user, but you shouldn't worry about that just yet. - -## Avoiding many levels of indentation with elif - -If you have more than one condition to check, your code will end up -looking a bit messy. - -```py -print("Hello!") -word = input("Enter something: ") - -if word == "hi": - print("Hi to you too!") -else: - if word == "hello": - print("Hello hello!") - else: - if word == "howdy": - print("Howdyyyy!") - else: - if word == "hey": - print("Hey hey hey!") - else: - if word == "gday m8": - print("Gday 4 u 2!") - else: - print("I don't know what", word, "means.") -``` - -Instead of typing `else`, indenting more and typing an `if` you can -simply type `elif`, which is short for `else if`. Like this: - -```py -print("Hello!") -word = input("Enter something: ") - -if word == "hi": - print("Hi to you too!") -elif word == "hello": - print("Hello hello!") -elif word == "howdy": - print("Howdyyyy!") -elif word == "hey": - print("Hey hey hey!") -elif word == "gday m8": - print("Gday 4 u 2!") -else: - print("I don't know what", word, "means.") -``` - -## Summary - -- Indentation is important in Python. -- Indented code under an if statement runs if the condition is true. -- You can also add an else statement. Indented code under it will run - if the code under the if statement does not run. -- elif is short for else if. - -## Exercises - -1. Write a program into a file that asks the user to write a word and - then prints that word 1000 times. For example, if the user enters - `hi` the program would reply `hihihihi...`. - -2. Add spaces between the words, so the output is like `hi hi hi ...`. - -3. Make something that asks the user to enter two words, and prints - 1000 of each with spaces in between. For example, if the user - enters `hello` and `hi` the program would print - `hello hi hello hi hello hi...`. - -4. Make a program that asks for a password and prints `Welcome!`, - `Access denied` or `You didn't enter anything` depending on whether - the user entered the correct password, a wrong password, or nothing - at all by pressing Enter without typing anything. - -5. Make a program that asks for username and password and checks them. - Make users "foo" and "bar" with passwords "biz" and "baz". - -The answers are [here](answers.md). - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Previous](using-functions.md) | -[Next](lists.md) | -[Back to the list of contents](README.md) diff --git a/images/differentlist.png b/images/differentlist.png new file mode 100644 index 0000000..c919d91 Binary files /dev/null and b/images/differentlist.png differ 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/download-me.png b/images/download-me.png new file mode 100644 index 0000000..fb10d5f Binary files /dev/null and b/images/download-me.png differ diff --git a/images/drawings.odg b/images/drawings.odg new file mode 100644 index 0000000..e890e1c Binary files /dev/null and b/images/drawings.odg differ diff --git a/images/freeze-melt.png b/images/freeze-melt.png new file mode 100644 index 0000000..8739c63 Binary files /dev/null and b/images/freeze-melt.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/generators.png b/images/generators.png new file mode 100644 index 0000000..dd397b1 Binary files /dev/null and b/images/generators.png differ diff --git a/idle-new.png b/images/idle-new.png similarity index 100% rename from idle-new.png rename to images/idle-new.png diff --git a/idle.png b/images/idle.png similarity index 100% rename from idle.png rename to images/idle.png diff --git a/images/indexing1.png b/images/indexing1.png new file mode 100644 index 0000000..16a8ff6 Binary files /dev/null and b/images/indexing1.png differ diff --git a/images/indexing2.png b/images/indexing2.png new file mode 100644 index 0000000..5536490 Binary files /dev/null and b/images/indexing2.png differ diff --git a/images/indexing3.png b/images/indexing3.png new file mode 100644 index 0000000..99f35f2 Binary files /dev/null and b/images/indexing3.png differ diff --git a/images/iters.png b/images/iters.png new file mode 100644 index 0000000..f138729 Binary files /dev/null and b/images/iters.png differ diff --git a/images/key.png b/images/key.png new file mode 100644 index 0000000..edeb724 Binary files /dev/null and b/images/key.png differ diff --git a/images/locals-and-globals.png b/images/locals-and-globals.png new file mode 100644 index 0000000..dbe95d8 Binary files /dev/null and b/images/locals-and-globals.png differ diff --git a/images/modules.png b/images/modules.png new file mode 100644 index 0000000..412388e Binary files /dev/null and b/images/modules.png differ diff --git a/images/people.png b/images/people.png new file mode 100644 index 0000000..2dfa416 Binary files /dev/null and b/images/people.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/samelist.png b/images/samelist.png new file mode 100644 index 0000000..d113026 Binary files /dev/null and b/images/samelist.png differ diff --git a/images/slicing1.png b/images/slicing1.png new file mode 100644 index 0000000..dac91a5 Binary files /dev/null and b/images/slicing1.png differ diff --git a/images/slicing2.png b/images/slicing2.png new file mode 100644 index 0000000..30ae7b3 Binary files /dev/null and b/images/slicing2.png differ diff --git a/images/slicing3.png b/images/slicing3.png new file mode 100644 index 0000000..3486d4f Binary files /dev/null and b/images/slicing3.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/images/variables1.png b/images/variables1.png new file mode 100644 index 0000000..fd5595e Binary files /dev/null and b/images/variables1.png differ diff --git a/images/variables2.png b/images/variables2.png new file mode 100644 index 0000000..266b97d Binary files /dev/null and b/images/variables2.png differ diff --git a/installing-python.md b/installing-python.md deleted file mode 100644 index d2a3930..0000000 --- a/installing-python.md +++ /dev/null @@ -1,110 +0,0 @@ -# Installing Python - -If you want to learn to program with Python using this tutorial, you -need to try out the code examples. You can use a website like -[repl.it](https://repl.it/languages/python3), but I highly recommend -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. - -Let's get started! - -## Downloading and installing Python - -### Windows - -Use the official Python installer, it will install Python and IDLE for -you. - -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. -3. Run the installer. -4. Select the "advanced installation" option, and make sure the py.exe - launcher gets installed. - -### Mac OSX - -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). - -### GNU/Linux - -You already have Python, there's no need to download anything. - -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. - -## 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 - -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](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. - -### If you like working with PowerShell, command prompt or terminal - -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: - - 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. - >>> - -On GNU/Linux or Mac OSX, type `python3` instead: - - 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. - >>> - -Now type `exit()` and press Enter to get out of Python. - -## Summary - -Now you should have Python installed, and you should be able run it. - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Previous](introduction.md) | -[Next](getting-started.md) | -[Back to the list of contents](README.md) diff --git a/introduction.md b/introduction.md deleted file mode 100644 index db16923..0000000 --- a/introduction.md +++ /dev/null @@ -1,44 +0,0 @@ -# Quick introduction to this tutorial - -I started writing a full Python tutorial, but I stopped when I realized -that it would be a lot of work. Instead, I started adding links to other -free tutorials. Since much of this is collected from different places -around the Internet, you will end up reading about some things twice. -Just skip them and move on :) - -Everything in this tutorial contains lots of examples, and the tutorial -should be easy to follow. But what should you do if you don't understand -something the first time you read it? - -- Read the example code and the explanation for it again. -- Try the example yourself. -- 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. -- If you still have trouble understanding the tutorial or any other - problems with the tutorial, please [tell me](contact-me.md). I want - to improve the tutorial so other readers won't have the same - problems as you have. - -One of the most important things with learning to program is to **not -fear mistakes**. If you make a mistake, your computer will not break in -any way. You'll get an error message that tells you what's wrong and -where. Even professional programmers do mistakes and get error messages -all the time, and there's nothing wrong with it. - -Even though a good tutorial is an important part about learning to -program, you also need to learn to make your own things. Use what you -have learned, and create something with it. - -## Summary - -- Now you should know what to do if you don't understand something. -- Don't fear mistakes. -- Error messages are your friends. -- Don't be limited to this tutorial, also make your own programs. - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Next](installing-python.md) | -[Back to the list of contents](README.md) diff --git a/linkcheck.py b/linkcheck.py new file mode 100755 index 0000000..2dcd4c5 --- /dev/null +++ b/linkcheck.py @@ -0,0 +1,156 @@ +#!/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 + +"""Check for broken links. + +This finds links like this... + + [click here](some-file.md) + [or here](../some/path/another-file.md) + ![here's an image](../images/some-cool-image.png) + +...but not like this: + + [some website](http://github.com/) + [another website](https://github.com/) + [local link](#some-title) +""" + +import os +import posixpath + +import common + + +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". + """ + if target.startswith(('http://', 'https://')): + # We don't need this currently, but checking these links could + # be added later. + return "ok" + + path = posixpath.join(posixpath.dirname(this_file), target) + path = posixpath.normpath(path) + + if not os.path.exists(path): + return "doesn't exist" + + if target.endswith('/'): + # A directory. + if not os.path.isdir(path): + return "not a directory" + else: + # 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 for titles and links...") + titledict = {} # {filename: [title1, title2, ...]} + linkdict = {} # {filename: [(file, title, lineno), ...]) + for path in common.get_markdown_files(): + 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)) + + +if __name__ == '__main__': + main() diff --git a/lists.md b/lists.md deleted file mode 100644 index e2a0ba9..0000000 --- a/lists.md +++ /dev/null @@ -1,98 +0,0 @@ -# ThinkPython: Lists - -Now we have learned to save values to variables. - -```py -thing = 'Hello World!' -``` - -Then we can do whatever we want with the variable. - -```py -print(thing) -``` - -But what if you have a lot of values? You can just make a lot of -variables... - -```py -thing1 = 'Hello World!' -thing2 = 'hi' -thing3 = 123 -thing4 = 3.14 -thing5 = 42 -thing6 = 'This is yet another thing.' -thing7 = 'Python is fun!' - -print(thing1) -print(thing2) -print(thing3) -print(thing4) -print(thing5) -print(thing6) -print(thing7) -``` - -...or you can use a list and keep everything in one variable. - -```py -things = ['Hello World!', 'hi', 123, 3.14, 42, - 'This is yet another thing.', 'Python is fun!'] -for thing in things: - print(thing) -``` - -[Read more about lists in ThinkPython -here.](http://greenteapress.com/thinkpython2/html/thinkpython2011.html) -Skip the chapter "10.7 Map, filter and reduce", and all exercises. You -would need to know how to define functions using the `def` keyword, but -we haven't talked about that yet. - -## Summary - -- Lists are a way to store multiple values in one variable. We can - create lists by putting whatever we want inside [square brackets], - for example, `our_list = []` creates an empty list. -- Never do `list = ...`. `list` is a built-in class, and it's used for - converting other values to lists, like `list_of_thingy = list(thingy)`. - If we do `list = something`, then `list(thingy)` will probably do - something else than we want it to do. -- When we have created a list, we can **slice** it. For example, - `our_list[2:]` results in a new list with everything in the - original list except the first two elements. Negative indexes start - from the end of the list, for example, `our_list[-2:]` is a list of - the last two elements. -- We can also **index** lists, `our_list[0]` is the first element in the - list. Non-negative indexes start at zero, and negative indexes - start at -1. -- You can assign to indexes and slices like `some_list[0] = 'hi'`, or - delete them like `del some_list[:2]`. -- `a = b` does not create a copy of b. - - ```py - >>> a = [] - >>> b = a # this does not copy anything - >>> b += [1, 2, 3] - >>> a - [1, 2, 3] - >>> - ``` - - If you want a copy, use the `.copy()` list method: - - ```py - >>> a = [] - >>> b = a.copy() - >>> b += [1, 2, 3] - >>> a - [] - >>> - ``` - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Previous](if.md) | -[Next](loops.md) | -[Back to the list of contents](README.md) diff --git a/loops.md b/loops.md deleted file mode 100644 index 16b69d3..0000000 --- a/loops.md +++ /dev/null @@ -1,266 +0,0 @@ -# Loops - -In programming, a **loop** means repeating something multiple times. -There are different kinds of loops: - -- **While loops** repeat something while a condition is true. -- **Until loops** repeat something until a condition is true. -- **For loops** repeat something for each element of a sequence. - -We'll talk about all of these in this tutorial. - -## While loops - -Now we know how if statements work. - -```py -its_raining = True -if its_raining: - print("Oh crap, it's raining!") -``` - -While loops are really similar to if statements. - -```py -its_raining = True -while its_raining: - print("Oh crap, it's raining!") - # we'll jump back to the line with the word "while" from here -print("It's not raining anymore.") -``` - -If you're not familiar with while loops, the program's output may be a -bit surprising: - - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - Oh crap, it's raining! - (much more raining) - -Again, this program does not break your computer. It just prints the -same thing multiple times. We can interrupt it by pressing Ctrl+C. - -In this example, `its_raining` was the **condition**. If something in -the while loop would have set `its_raining` to False, the loop would -have ended and the program would have printed `It's not raining anymore`. - -Let's actually create a program that does just that: - -```py -its_raining = True -while its_raining: - print("It's raining!") - answer = input("Or is it? (y=yes, n=no) ") - if answer == 'y': - print("Oh well...") - elif answer == 'n': - its_raining = False # end the while loop - else: - print("Enter y or n next time.") -print("It's not raining anymore.") -``` - -Running the program may look like this: - - It's raining! - Or is it? (y=yes, n=no) i dunno - Enter y or n next time. - It's raining! - Or is it? (y=yes, n=no) y - Oh well... - It's raining! - Or is it? (y=yes, n=no) n - It's not raining anymore. - -We can also interrupt a loop even if the condition is still true using -the `break` keyword. In this case, we'll set condition to True and rely -on nothing but `break` to end the loop. - -```py -while True: - answer = input("Is it raining? (y=yes, n=no) ") - if answer == 'y': - print("It's raining!") - elif answer == 'n': - print("It's not raining anymore.") - break # end the loop - else: - print("Enter y or n.") -``` - -The program works like this: - - Is it raining? (y=yes, n=no) who knows - Enter y or n. - Is it raining? (y=yes, n=no) y - It's raining! - Is it raining? (y=yes, n=no) n - It's not raining anymore. - -## Until loops - -Python doesn't have until loops. If you need an until loop, use -`while not`: - -```py -raining = False -while not raining: - print("It's not raining.") - if input("Is it raining? (y/n) ") == 'y': - raining = True -print("It's raining!") -``` - -## For loops - -Let's say we have a list of things we want to print. To print each item -in stuff, we could just do a bunch of prints: - -```py -stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] - -print(stuff[0]) -print(stuff[1]) -print(stuff[2]) -print(stuff[3]) -print(stuff[4]) -``` - -The output of the program is like this: - - hello - hi - how are you doing - im fine - how about you - -But this is only going to print five items, so if we add something to -stuff, it's not going to be printed. Or if we remove something from -stuff, we'll get an error saying "list index out of range". - -We could also create an index variable, and use a while loop: - -```py ->>> stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] ->>> length_of_stuff = len(stuff) # len(x) is the length of x, 5 in this case ->>> index = 0 ->>> while index < length_of_stuff: -... print(stuff[index]) -... index += 1 -... -hello -hi -how are you doing -im fine -how about you ->>> -``` - -But there's `len()` and an index variable we need to increment. That's -a lot of stuff to worry about for just printing each item. - -This is when for loops come in: - -```py ->>> for thing in stuff: -... # this is repeated for each element of stuff, that is, first -... # for stuff[0], then for stuff[1], etc. -... print(thing) -... -hello -hi -how are you doing -im fine -how about you ->>> -``` - -Without the comments, that's only two simple lines, and one variable. -Much better than anything else we tried before. - -```py ->>> for thing in stuff: -... print(thing) -... -hello -hi -how are you doing -im fine -how about you ->>> -``` - -There's only one big limitation with for looping over lists. You -shouldn't modify the list in the for loop. If you do, the results can -be surprising: - -```py ->>> stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] ->>> for thing in stuff: -... stuff.remove(thing) -... ->>> stuff -['hi', 'im fine'] ->>> -``` - -Instead, you can create a copy of stuff and loop over it. - -```py ->>> stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] ->>> for thing in stuff.copy(): -... stuff.remove(thing) -... ->>> stuff -[] ->>> -``` - -Or if you want to clear a list, just use the `.clear()` list method: - -```py ->>> stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] ->>> stuff.clear() ->>> stuff -[] ->>> -``` - -## Exercises - -1. Back in "Using if, else and elif" we created a program that asked - for username and password and checks them, and we made users "foo" - and "bar" with passwords "biz" and "baz". Adding a new user would - have required adding more code that checks the username and - password. Add this to the beginning of your program: - - ```py - users = [ - ['foo', 'biz'], - ['bar', 'baz'], - ] - ``` - - Then rewrite the rest of the program using a for loop. - -2. Make the program ask the username and password over and over again -3. Can you limit the number of attempts to 3? - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Previous](lists.md) | -[Back to the list of contents](README.md) diff --git a/make-html.py b/make-html.py new file mode 100755 index 0000000..b40ec3a --- /dev/null +++ b/make-html.py @@ -0,0 +1,285 @@ +#!/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 + +"""Create HTML files of the tutorial.""" + +import argparse +import os +import platform +import posixpath +import shutil +import sys +import textwrap +import webbrowser + +if platform.system() == 'Windows': + python = 'py' +else: + python = 'python3' + +try: + import mistune +except ImportError: + print("mistune isn't installed.", file=sys.stderr) + print("You can install it by running this command on a terminal or ") + print("command prompt:") + print() + 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 = """\ + + + + + {title} + + + + {body} + + +""" + + +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(filename, mode) + + +def fix_filename(filename): + 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'): + filename = filename[:-3] + '.html' + return filename + + +class TutorialRenderer(mistune.HTMLRenderer): + + def __init__(self, pygments_style): + super().__init__() + self.pygments_style = pygments_style + self.title = None # will be set by header() + + 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 = common.header_link(raw) + content = super().header(text, level, raw) + return '
{1}'.format(target, content) + + def link(self, link, title, text): + """Return a link that points to the correct file.""" + # "[text](link)" + if link.startswith('#'): + # it's like "#title", no need to do anything + pass + elif '#' in link: + # it's like "some-file#title", we need to fix some-file + before, after = link.split('#', 1) + link = fix_filename(before) + '#' + after + else: + # it's like "some-file" + link = fix_filename(link) + return super().link(link, title, text) + + def block_code(self, code, lang=None): + """Highlight Python code blocks with Pygments if it's installed.""" + if lang == 'python' and pygments is not None: + # we can highlight it + if code.startswith('>>> '): + lexer = pygments.lexers.PythonConsoleLexer(python3=True) + else: + lexer = pygments.lexers.Python3Lexer() + formatter = pygments.formatters.HtmlFormatter( + style=self.pygments_style, noclasses=True) + return pygments.highlight(code, lexer, formatter) + + 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.""" + result = super().image(src, title, text) + return self.link(src, title, result) + + def table(self, header, body): + """Return a table with a border.""" + result = super().table(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(" %s -m pip install pygments" % python) + print() + print("You can also continue without Pygments, but the code examples") + print("will not be colored.") + if not common.askyesno("Continue without pygments?"): + print("Interrupt.") + return + args.pygments_style = None + + if os.path.exists(args.outdir): + if not common.askyesno("%s exists. Do you want to remove it?" + % args.outdir): + print("Interrupt.") + return + if os.path.isdir(args.outdir): + shutil.rmtree(args.outdir) + else: + os.remove(args.outdir) + + print("Generating HTML files...") + for markdownfile in common.get_markdown_files(): + 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(args.pygments_style) + body = mistune.markdown(markdown, renderer=renderer) + 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(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 %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(args.outdir, 'index.html')) + + +if __name__ == '__main__': + main() diff --git a/todo.md b/todo.md deleted file mode 100644 index e849d18..0000000 --- a/todo.md +++ /dev/null @@ -1,4 +0,0 @@ -- Lists and tuples -- Loops (maybe move while loops here?) -- Defining functions -- More exercises! diff --git a/update-ends.py b/update-ends.py new file mode 100755 index 0000000..9b2d1ef --- /dev/null +++ b/update-ends.py @@ -0,0 +1,139 @@ +#!/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 + +"""Update ends of markdown files.""" + +import posixpath +import re + +import common + + +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, +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 +[LICENSE]({toplevel}/LICENSE). + +{extralinks}[List of contents]({toplevel}/README.md#{readmeheader}) +""" + +CHAPTER_LINK_REGEX = r'^\d+\. \[.*\]\((.*\.md)\)$' + + +def get_filenames(): + """Get chapter files and other files from README. + + Return a two-tuple of chapter file names and other file names as + iterables of strings. + """ + chapters = [] + with open('README.md', 'r') as f: + # move to where the content list starts + while f.readline().strip() != "## List of contents": + pass + + # now let's read the content list + for line in f: + line = line.strip() + if line.startswith('## '): + # end of content list + break + if line: + # not empty line + match = re.search(CHAPTER_LINK_REGEX, line) + if match is not None: + # it's a link to a chapter + chapters.append(match.group(1)) + + others = set(common.get_markdown_files()) - set(chapters) + return chapters, others + + +def update_end(filename, end): + """Add *** and end to a file if it doesn't have them already. + + filename should be relative to the toplevel using / as a path + separator. + """ + end = '\n***\n\n' + end + with open(filename, 'r') as f: + content = f.read() + if content.endswith(end): + # No need to do anything. + print(" Has correct end:", filename) + return + + if '\n***\n' in content: + # We need to remove the old ending first. + print(" Removing old end:", filename) + where = content.index('\n***\n') + with open(filename, 'w') as f: + f.write(content[:where]) + + print(" Adding end:", filename) + with open(filename, 'a') as f: + f.write(end) + + +def main(): + chapter_files, other_files = get_filenames() + + # make previous of first file and next of last file to just bring + # back to README + prevs = ['README.md'] + chapter_files[:-1] + nexts = chapter_files[1:] + ['README.md'] + + print("Chapter files:") + for prevpath, thispath, nextpath in zip(prevs, chapter_files, nexts): + # all paths should be like 'section/file.md' + where = posixpath.dirname(thispath) + prev = posixpath.relpath(prevpath, where) + next_ = posixpath.relpath(nextpath, where) + extralinks = "[Previous](%s) | [Next](%s) |\n" % (prev, next_) + end = END_TEMPLATE.format( + toplevel='..', extralinks=extralinks, readmeheader=where) + update_end(thispath, end) + + print() + + print("Other files:") + for filename in other_files: + where = posixpath.dirname(filename) + end = END_TEMPLATE.format( + toplevel=posixpath.relpath('.', where), + extralinks="", readmeheader='list-of-contents') + update_end(filename, end) + + +if __name__ == '__main__': + main() 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/using-functions.md b/using-functions.md deleted file mode 100644 index afe18b0..0000000 --- a/using-functions.md +++ /dev/null @@ -1,165 +0,0 @@ -# Using functions - -Now we know how to make Python show text. - -```py ->>> 'Hello!' -'Hello!' ->>> -``` - -But that includes `''`. One way to show text to the user without `''` -is with the print function. In Python, printing doesn't have anything -to do with physical printers, it just means showing text on the screen. - -```py ->>> print('Hello!') -Hello! ->>> -``` - -Now we are ready for a classic example, which is also the first program -in many tutorials :) - -```py ->>> print("Hello World!") -Hello World! ->>> -``` - -But what exactly is print? - -```py ->>> print - ->>> -``` - -In Python 3, print is a function. Functions do something when they are -**called** by typing their name and parentheses. Inside the -parentheses, we can pass some arguments too. In `print("hello")` the -function is `print` and we give it one argument, which is `"hello"`. - -Functions are sometimes thoght of as difficult to understand, but they -really are not. They just do something when they are called. But if we -do `x = print('hello')`, what is x? - -```py ->>> x = print('hello') -hello ->>> print(x) # x is now None -None ->>> -``` - -So doing `x = print('hello')` set x to None. Here's what happened, -explained in more detail: - -- In `x = print('hello')`, the right side is processed first. -- `print('hello')` calls the print function with the argument - `'hello'`. -- The function runs **immediately** when it's called. It shows the word - hello. -- The print function **returns** None. All functions need to return - something, and print returns None because there's no need to return - anything else. -- Now the right side has been processed. `print('hello')` returned - None, so we can imagine we have None instead of `print('hello')` - there, and the assignment now looks like `x = None`. -- x is now None. - -Calling a function without assigning the return value to anything (e.g. -`print('hello')` instead of `x = print('hello')`) simply throws away -the return value. The interactive `>>>` prompt also echoes the return -value back if it's not None. - -Of course, `x = print('hello')` is useless compared to `print('hello')` -because the print function always returns None and we can do `x = None` -without any printing. - -You can also print an empty line by calling print without any -arguments: - -```py ->>> print() - ->>> -``` - -In Python, `\n` is a newline character. Printing a string that contains -a newline character also prints a newline: - -```py ->>> print('hello\nworld') -hello -world ->>> -``` - -If you want to print a backslash, you need to **escape** it by typing -two backslashes: - -[comment]: # (For some reason, GitHub's syntax highlighting doesn't) -[comment]: # (work here.) - - >>> print('hello\\nworld') - hello\nworld - >>> - -You can also pass multiple arguments to the print function. Separate -them with commas, and print will add spaces between them. - -```py ->>> print("Hello", "World!") -Hello World! ->>> -``` - -Unlike with `+`, the arguments don't need to be strings. - -```py ->>> print(42, "is an integer, and the value of pi is", 3.14) -42 is an integer, and the value of pi is 3.14 ->>> -``` - -Not all functions return None. The input function can be used for -getting a string from the user. - -```py ->>> x = input("Enter something:") -Enter something:hello ->>> x -'hello' ->>> -``` - -`input("Enter something:")` showed the text `Enter something:` on the -screen and waited for me to type something. I typed hello and pressed -Enter. Then input returned the hello I typed as a string and it was -assigned to x. - -You may want to add a space after the `:`, like this: - -```py ->>> x = input("Enter something: ") # now there's space between : and where i type -Enter something: hello ->>> -``` - -## Summary - -- `function()` calls a function without any arguments, and - `function(1, 2, 3)` calls a function with 1, 2 and 3 as arguments. - `x = function()` calls a function, and assigns the return value of - the call to y. -- When a function is called, it does something and returns something. -- Python comes with `print` and `input`. They are built-in functions. - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Previous](variables.md) | -[Next](if.md) | -[Back to the list of contents](README.md) diff --git a/variables.md b/variables.md deleted file mode 100644 index a0a53fd..0000000 --- a/variables.md +++ /dev/null @@ -1,211 +0,0 @@ -# Variables, Booleans and None - -## Variables - -Variables are easy to understand. They simply **contain data**. Actually -they [point to data](https://www.youtube.com/watch?v=_AEJHKGk9ns), but -think about them as data containers for now. - -```py ->>> a = 1 # create a variable called a and assign 1 to it ->>> a # get the value of a and let Python echo it -1 ->>> -``` - -We can also change the value of a variable after setting it. - -```py ->>> a = 2 ->>> a -2 ->>> -``` - -Trying to access a variable that is not defined is an error. - -```py ->>> thingy -Traceback (most recent call last): - File "", line 1, in -NameError: name 'thingy' is not defined ->>> -``` - -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 you should use simple, lowercase variable names. You can also -use underscores. - -```py ->>> number_one = 1 ->>> number_two = 2 ->>> -``` - -Python also has some words that cannot be used as variable names -because they have a special meaning. They are called **keywords**, and -you can run `help('keywords')` to see the full list if you 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. - -```py ->>> if = 123 - File "", line 1 - if = 123 - ^ -SyntaxError: invalid syntax ->>> -``` - -When assigning something to a variable using a `=`, the right side of -the `=` is always executed before the left side. This means that we can -do something with a variable on the right side, then assign the result -back to the same variable on the left side. - -```py ->>> a = 1 ->>> a = a + 1 ->>> a -2 ->>> -``` - -To do something to a variable (for example, to add something to it) we -can also use `+=`, `-=`, `*=` and `/=` instead of `+`, `-`, `*` and -`/`. The "advanced" `%=`, `//=` and `**=` also work. - -```py ->>> a += 2 # a = a + 2 ->>> a -= 2 # a = a - 2 ->>> a *= 2 # a = a * 2 ->>> a /= 2 # a = a / 2 ->>> -``` - -This is not limited to integers. - -```py ->>> a = 'hello' ->>> a *= 3 ->>> a += 'world' ->>> a -'hellohellohelloworld' ->>> -``` - -Now you also understand why typing hello to the prompt didn't work in -the beginning of this tutorial. But we can assign something to a -variable called hello and then type hello: - -```py ->>> hello = 'hello there' ->>> hello -'hello there' ->>> -``` - -## Booleans - -There are two Boolean values, True and False. In Python, and in many -other programming languages, `=` is assigning and `==` is comparing. -`a = 1` sets a to 1, and `a == 1` checks if a equals 1. - -```py ->>> a = 1 ->>> a == 1 -True ->>> a = 2 ->>> a == 1 -False ->>> -``` - -`a == 1` is the same as `(a == 1) == True`, but `a == 1` is more -readable, so most of the time you shouldn't write `== True` anywhere. - -```py ->>> a = 1 ->>> a == 1 -True ->>> (a == 1) == True -True ->>> a = 2 ->>> a == 1 -False ->>> (a == 1) == True -False ->>> -``` - -## None - -None is Python's "nothing" value. It behaves just like any other value, -and it's often used as a default value for different kinds of things. -We'll find a bunch of ways to use None later. - -None's behavior on the interactive prompt might be a bit confusing at -first: - -```py ->>> thingy = None ->>> thingy ->>> -``` - -That was weird! We set thingy to None, but typing `thingy` didn't echo -back None. - -This is because the prompt never echoes back None. That is handy, -because many things result in None, and it would be annoying to see -None coming up all the time. - -If you want to see a None on the interactive prompt, use print: - -```py ->>> print(thingy) -None ->>> -``` - -## 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. - -| Usage | Description | True examples | -|-----------|-----------------------------------|-----------------------| -| `a == b` | a is equal to b | `1 == 1` | -| `a != b` | a is not equal to b | `1 == 2` | -| `a > b` | a is greater than b | `2 > 1` | -| `a >= b` | a is greater than or equal to b | `2 >= 1`, `1 >= 1` | -| `a < b` | a is less than b | `1 < 2` | -| `a <= b` | a is less than or equal to b | `1 <= 2`, `1 <= 1` | - -We can also combine multiple comparisons. These are not always correct, -because a and b don't need to be Booleans. We'll learn more about that -later. - -| Usage | Description | True example | -|-----------|-------------------------------------------|-----------------------------------| -| `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`. - -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. - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Previous](the-way-of-the-program.md) | -[Next](using-functions.md) | -[Back to the list of contents](README.md) diff --git a/what-next.md b/what-next.md new file mode 100644 index 0000000..8437d95 --- /dev/null +++ b/what-next.md @@ -0,0 +1,43 @@ +# What should I do now? + +You are now at the end of this tutorial. This means that you're almost +ready to do something fun with Python. This page contains a bunch of +links to other useful and fun Python resources. + +## What the heck is this? + +Sometimes you'll come across things that I haven't shown in this +tutorial. Here's a table of things you'll probably come across: + +### A dictionary without a `:` + +These are actually [sets](https://docs.python.org/3/tutorial/datastructures.html#sets), +not dictionaries. However, `{}` is a dictionary. + +### yield stuff + +The [yield keyword](http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do) +is a way to create generators + +| What you see | What it is | +|---------------------------|---------------------------------------------------------------------------------------------------| +| a dictionary without `:` | [a set] | +| yield stuff | | +| `import stuff as thing` | imports stuff and sets the module to a thing variable | +| `from stuff import thing` | about the same as `import stuff` and after that +| `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)