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 0df1b8f..93baa59 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ The instructions and text in this tutorial (the "software") are licensed under the zlib License. - (C) 2016 Akuli + (C) 2016-2021 Akuli This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/README.md b/README.md index 9e62e24..f8e350d 100644 --- a/README.md +++ b/README.md @@ -1,62 +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 was written in Python 3, and you need Python 3 or newer to -be able to run the example code yourself. 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. - -I have tested most of the code in this tutorial on Python 3.4, but -everything should also work on Python 3.2 and all newer Pythons. +# 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 -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. [If, else and elif](if.md) -8. [Handy stuff with strings](handy-stuff-strings.md) -9. [Lists and tuples](lists-and-tuples.md) -10. [Loops](loops.md) -11. [Trey Hunner: zip and enumerate](trey-hunner-zip-and-enumerate.md) -12. [Dictionaries](dicts.md) -13. [Defining functions](defining-functions.md) -14. [What is true?](what-is-true.md) -15. [Files](files.md) -16. [Exceptions](exceptions.md) -17. [Modules](modules.md) -18. [Classes](classes.md) - -Other things this tutorial comes with: +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) -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. +## 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". + + ![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 index 672106e..ff1ecde 100644 --- a/TODO.md +++ b/TODO.md @@ -2,18 +2,29 @@ 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 -- Better lists chapter, also introduce tuples in it - **More exercises and examples everywhere!** -- explain bool(x) and why it matters + - 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` (maybe reading PEP-8 is enough to explain this?) + - non-public `_variables` - "singular" inheritance, inheritance of built-in classes - using super -- no classes part 3 with multiple inheritance, it's not something people need -- iterables and iterators: something most Python programmers need to be - aware of + - 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) @@ -23,4 +34,19 @@ This tutorial is not complete. It still needs: - David Beazley's metaprogramming and other talks - "What the heck is this?" section for stuff i haven't talked about - regexes - - yield + +- 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 b16375c..0000000 --- a/answers.md +++ /dev/null @@ -1,293 +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. 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. - -## 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.") - ``` - -## 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.") - ``` - -## Trey Hunner: zip and enumerate - -1. Read some lines with `input` into a list and then enumerate it. - - ```py - 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: - - ```py - >>> 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: - - ```py - 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: - - ```py - 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. - - ```py - 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. - - ```py - uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' - lowercase = 'abcdefghijklmnopqrstuvwxyz' - - for upper, indexlowerpair in zip(uppercase, enumerate(lowercase, start=1)): - index, lower = indexlowerpair - print(index, upper, lower) - ``` - -## Defining functions - -1. Use `-value` (it works just like `-1`) to get the negative in - the abs function, and for loops in the other two functions. - - ```py - def my_abs(value): - if value < 0: - return -value - # actually, this else is useless because returning ends the - # function anyway - else: - return value - - def my_any(a_list): # don't call this "list", see summary in the Lists chapter - for item in a_list: - if item: - return True # ends the function - return False - - def my_all(a_list): - for item in a_list: - if not item: - return False - return True - ``` - -2. Like this: - - ```py - def print_box(message, character='*'): - number_of_characters = len(message) + 4 - print(character * number_of_characters) - print(character, message, character) - print(character * number_of_characters) - ``` - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Back to the list of contents](README.md#list-of-contents) 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/defining-functions.md b/basics/defining-functions.md similarity index 64% rename from defining-functions.md rename to basics/defining-functions.md index d76e873..9b78b94 100644 --- a/defining-functions.md +++ b/basics/defining-functions.md @@ -7,7 +7,7 @@ It's probably been a while since you read about using functions. Have a look at this code: -```py +```python print("************") print("Hello World!") print("************") @@ -30,7 +30,7 @@ else: Then compare it to this code: -```py +```python print_box("Hello World!") print_box("Enter a word:") word = input() @@ -44,77 +44,83 @@ In this tutorial we'll learn to define a `print_box` function that prints text in a box. We can write the code for printing the box once, and then use it multiple times anywhere in the program. -Dividing a long program into simple functions also makes the code -easier to work with. If there's a problem with the code we can -test the functions one by one and find the problem easily. +[Dividing a long program into simple functions](larger-program.md) also +makes the code easier to work with. If there's a problem with the code +we can test the functions one by one and find the problem easily. ## First functions The `pass` keyword does nothing. -```py +```python >>> pass ->>> +>>> ``` Let's use it to define a function that does nothing. -```py +```python >>> def do_nothing(): ... pass -... +... >>> do_nothing ->>> +>>> ``` -Seems to be working so far, we have a function. Actually it's just -a value that is assigned to a variable called `do_nothing`. Let's see -what happens if we call it. +Seems to be working so far, we have a function. It's just a value that +is assigned to a variable called `do_nothing`. You can ignore the +`0xblablabla` stuff for now. + +The `pass` is needed here because without it, Python doesn't know when +the function ends and it gives us a syntax error. We don't need the +`pass` when our functions contain something else. + +Let's see what happens if we call our function. -```py +```python >>> do_nothing() ->>> +>>> ``` There we go. It did nothing at all. Maybe we could just do something in the function instead? -```py +```python >>> def print_hi(): ... print("Hi!") -... +... >>> print_hi() Hi! ->>> +>>> ``` It's working. How about printing a variable in the function? -```py +```python >>> def print_message(): ... print(message) -... +... >>> message = "Hello World!" >>> print_message() Hello World! ->>> +>>> ``` Again, it works. How about setting a variable in the function? -```py +```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? @@ -125,71 +131,70 @@ 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 +```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. -```py +```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) +![Locals and globals.](../images/locals-and-globals.png) However, modifying a global variable in-place from a function is easy. -```py +```python >>> stuff = ['global stuff'] >>> def add_stuff(): ... stuff.append('local stuff') -... +... >>> add_stuff() >>> stuff ['global stuff', 'local stuff'] ->>> +>>> ``` -This doesn't work if the value is of an immutable type, like string or -integer because immutable values cannot be modified in-place. -Fortunately, Python will tell us if something's wrong. +This only works for changing in-place, we cannot assign a new value to +the variable. -```py ->>> foo = 1 ->>> def bar(): -... foo += 1 -... ->>> bar() -Traceback (most recent call last): - File "", line 1, in - File "", line 2, in bar -UnboundLocalError: local variable 'foo' referenced before assignment ->>> +```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. @@ -197,12 +202,12 @@ 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? -```py +```python >>> def print_twice(message): ... print(message) ... print(message) -... ->>> +... +>>> ``` Here `message` is an argument. When we call the function we'll get a @@ -216,11 +221,11 @@ This function can be called in two ways: This is the recommended way for functions that take only one or two arguments. I would do this in my code. - ```py + ```python >>> print_twice("hi") hi hi - >>> + >>> ``` When the function was running it had a local `message` variable @@ -232,11 +237,11 @@ This function can be called in two ways: - Using a **keyword argument**: - ```py + ```python >>> print_twice(message="hi") hi hi - >>> + >>> ``` The name "keyword argument" is a little bit confusing because @@ -248,18 +253,14 @@ This function can be called in two ways: 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. That's - because we are not assigning to a variable, we are giving the - function a keyword argument and it can do whatever it wants with - it. - -Personally, I would use this function with a positional argument. It -only takes one argument, so I don't need to worry about which argument -is which. + 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: -```py +```python def print_box(message): print('*' * len(message)) print(message) @@ -273,17 +274,17 @@ printing stars? We could change our `print_box` function to take two arguments: -```py +```python def print_box(message, character): print(character * len(message)) print(message) print(character * len(message)) ``` -Then we could change our existing code to always call `print_box` with -a star as the second argument: +Then we could change our code to always call `print_box` with a star as +the second argument: -```py +```python print_box("Hello World", "*") ... ``` @@ -291,7 +292,7 @@ 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. -```py +```python def print_box(message, character='*'): print(character * len(message)) print(message) @@ -303,13 +304,13 @@ different character in two ways: - Using a positional argument. - ```py + ```python print_box("Hello World!") ``` - Using a keyword argument. - ```py + ```python print_box(message="Hello World!") ``` @@ -318,13 +319,13 @@ need to: - Using two positional arguments. - ```py + ```python print_box("Enter a word:", "?") ``` - Using two keyword arguments. - ```py + ```python print_box(message="Enter a word:", character="?") print_box(character="?", message="Enter a word:") ``` @@ -334,13 +335,13 @@ need to: I would probably do this. If an argument has a default value, I like to use a keyword argument to change it if needed. - ```py + ```python print_box("Enter a word:", character="?") ``` However, this doesn't work: - ```py + ```python print_box(character="?", "Enter a word:") ``` @@ -351,53 +352,65 @@ need to: ## Output -The built-in input function returns a value. Can our function return a -value also? +The built-in input function [returns a value](using-functions.md#return-values). +Can our function return a value too? -```py ->>> def times_two(x): -... return x * 2 -... +```python +>>> def times_two(thing): +... return thing * 2 +... >>> times_two(3) 6 ->>> times_two("hello") -'hellohello' ->>> +>>> times_two(5) +10 +>>> ``` -Yes, it can. +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**. -```py +```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. -```py +```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's two ways to output information from functions. They can print +There are two ways to output information from functions. They can print something or they can return something. So, should we print or return? Most of the time **returning makes functions much easier to use**. Think @@ -411,20 +424,61 @@ assign it to a variable. If our function returns a value we can always print it, like this: -```py +```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. -```py +```python def ask_yes_no(prompt): while True: answer = input(prompt + ' (y or n) ') @@ -442,13 +496,13 @@ else: Ask questions with multiple answers. -```py +```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 # returning ends the function + return answer print(error_message) @@ -456,7 +510,7 @@ colors = ['red', 'yellow', 'blue', 'green', 'orange', 'pink', 'black', 'gray', 'white', 'brown'] choice = ask_until_correct("What's your favorite color?", colors, error_message="I don't know that color.") -print("Your favorite color is %s!" % choice) +print(f"Your favorite color is {choice}!") ``` ## Summary @@ -474,76 +528,58 @@ print("Your favorite color is %s!" % choice) function does. Returning also ends the function immediately. - Return a value instead of printing it if you need to do something with it after calling the function. +- Remember that `thing`, `thing()`, `print(thing)` and `print(thing())` + do different things. ## Exercises -**TODO:** more exercises. - -**There is a lot to learn about functions, and I don't expect you to -learn everything at once.** However, there's also lots of free Python -exercises about defining functions you can do. Do many of them and -spend time with them until you're familiar with defining functions. - -1. The box printing function doesn't really print a box, it prints a - message between two lines. - - ************ - Hello World! - ************ +**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. - Modify it to print an actual box: +1. What's wrong with this code? - **************** - * Hello World! * - **************** + ```python + def ask_name(): + name = input("Enter your name: ") -2. Python comes with many built-in functions. Some of the simplest ones - are abs, all and any. They can be used like this: - - - abs returns the absolute value of its only argument. + ask_name() + print("Your name is", name) + ``` - ```py - >>> abs(1) - 1 - >>> abs(-1) - 1 - >>> - ``` +2. How about this code? - - any returns True if one or more of the elements of a list is true, - and False otherwise. + ```python + def get_greeting(): + return "Hello World!" - ```py - >>> any([True, False, True]) - True - >>> any([False, False, False]) - False - >>> - ``` + print(get_greeting) + ``` - - all returns True if all elements of a list are true, and False - otherwise. +3. Why does this print None after greeting the world? - ```py - >>> all([True, True, True]) - True - >>> all([True, False, True]) - False - >>> - ``` + ```python + def greet(target): + print("Hello", target) - Define functions `my_abs`, `my_all` and `my_any` that work the same - way without using the built-in functions. Then run the program with - IDLE, or with `py -i file.py` on Windows or `python3 -i file.py` on - other operating systems. Try the above examples with your - functions. + print(greet("World")) + ``` -2. Find more exercises about defining functions online. +4. Find more exercises about defining functions online. -Answers for the first and second exercise are [here](answers.md). +Answers for the first, second and third exercise are +[here](answers.md#defining-functions). *** -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) +[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/exceptions.md b/basics/exceptions.md similarity index 82% rename from exceptions.md rename to basics/exceptions.md index 07da845..785fb18 100644 --- a/exceptions.md +++ b/basics/exceptions.md @@ -3,7 +3,7 @@ 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. -```py +```python text = input("Enter something: ") number = int(text) print("Your number doubled:", number*2) @@ -13,12 +13,12 @@ That works. ``` Enter a number: 3 -Your number twice: 6 +Your number doubled: 6 ``` But that doesn't work if the user does not enter a number. -```py +```python Enter a number: lol Traceback (most recent call last): File "/some/place/file.py", line 2, in @@ -36,30 +36,30 @@ 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. -```py +```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). -```py +```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. -```py +```python >>> the_problem = ValueError('oh no') >>> the_problem ValueError('oh no',) ->>> +>>> ``` ## Catching exceptions @@ -68,26 +68,26 @@ 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. -```py +```python >>> try: ... print(int('lol')) ... except ValueError: ... print("Oops!") -... +... Oops! ->>> +>>> ``` The except part doesn't run if the try part succeeds. -```py +```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 @@ -95,68 +95,54 @@ 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. -```py +```python >>> 123 + 'hello' Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for +: 'int' and 'str' ->>> -``` - -Catching ValueError doesn't catch TypeErrors. - -```py ->>> try: -... 123 + 'hello' -... except ValueError: -... print("wrong value") -... -Traceback (most recent call last): - File "", line 2, 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. -```py +```python >>> try: ... 123 + 'hello' ... print("This doesn't get printed.") ... except TypeError: ... print("Oops!") -... +... Oops! ->>> +>>> ``` Does an `except ValueError` also catch TypeErrors? -```py +```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? -```py +```python >>> try: ... int('lol') ... except ValueError: ... print('wrong value') ... except TypeError: ... print('wrong type') -... +... wrong value >>> try: ... 123 + 'hello' @@ -164,72 +150,73 @@ wrong value ... print('wrong value') ... except TypeError: ... print('wrong type') -... +... wrong type ->>> +>>> ``` Seems to be working. -We can catch multiple exceptions by catching a tuple of exceptions: +We can also also catch multiple exceptions by catching +[a tuple](lists-and-tuples.md#tuples) of exceptions: -```py +```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. -```py +```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`. -```py +```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? -Never do things like this: +Do **not** do things like this: -```py +```python try: # many lines of code except Exception: @@ -238,14 +225,14 @@ except Exception: 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 problems a lot harder. 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 +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: -```py +```python try: with open('some file', 'r') as f: content = f.read() @@ -255,16 +242,16 @@ except OSError: # we can't read the file but we can work without it Usually catching errors that the user has caused is also a good idea: -```py +```python import sys text = input("Enter a number: ") try: number = int(text) except ValueError: - print("'%s' is not a number." % text, file=sys.stderr) + print(f"'{text}' is not a number.", file=sys.stderr) sys.exit(1) -print("Your number doubled is %d." % (number * 2)) +print(f"Your number doubled is {(number * 2)}.") ``` ## Raising exceptions @@ -276,43 +263,43 @@ 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: -```py +```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. -```py +```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. -```py +```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 @@ -324,16 +311,16 @@ 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 a module -that other people will import we should probably use exceptions. For -other errors (for example, if the **user** of the program has done -something wrong) it's usually better to use `sys.stderr` and `sys.exit`. +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 diagram with -[this program](https://github.com/Akuli/classtree/) on Python 3.4. You -may have more or less exceptions than I have if your Python is newer or +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 @@ -346,6 +333,7 @@ older than mine, but they should be mostly similar. ├── BufferError ├── EOFError ├── ImportError + │ └── ModuleNotFoundError ├── LookupError │ ├── IndexError │ └── KeyError @@ -370,7 +358,9 @@ older than mine, but they should be mostly similar. │ └── TimeoutError ├── ReferenceError ├── RuntimeError - │ └── NotImplementedError + │ ├── NotImplementedError + │ └── RecursionError + ├── StopAsyncIteration ├── StopIteration ├── SyntaxError │ └── IndentationError @@ -400,9 +390,9 @@ 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's 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. +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 @@ -420,7 +410,7 @@ Catching Exception doesn't catch them either. Keep asking a number from the user until it's entered correctly. -```py +```python while True: try: number = int(input("Enter a number: ")) @@ -435,9 +425,9 @@ 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). +chapter](defining-functions.md) and [the module chapter](modules.md). -```py +```python # These are here so you can change them to customize the program # easily. default_greeting = "Hello World!" @@ -462,17 +452,23 @@ def greet(): try: greet() except OSError: - print("Cannot read '%s'!" % filename, file=sys.stderr) + print(f"Cannot read '{filename}'!", file=sys.stderr) if askyesno("Would you like to create a default greeting file?"): - try: - with open(filename, 'w') as f: - print(default_greeting, file=f) - except OSError: - print("Cannot create '%s' :(" % filename, file=sys.stderr) - sys.exit(1) # time to give up + with open(filename, 'w') as f: + print(default_greeting, file=f) greet() ``` *** -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). + +[Previous](modules.md) | [Next](classes.md) | +[List of contents](../README.md#basics) diff --git a/files.md b/basics/files.md similarity index 82% rename from files.md rename to basics/files.md index 9908101..5a8f7dd 100644 --- a/files.md +++ b/basics/files.md @@ -59,36 +59,40 @@ is same as `/home/me/hello.py`. Let's create a file and write a hello world to it. -```py +```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. +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`. -```py +```python >>> f <_io.TextIOWrapper name='hello.txt' mode='w' encoding='UTF-8'> ->>> +>>> ``` -So the first argument we passed to `open` was the path we wanted to write. +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`... but where the heck does that come from? -`w` is short for write, and that just means that we'll create a new file. -There's some other modes we can use also: +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 | |-------|-----------|-----------------------------------------------------------------------| @@ -96,17 +100,14 @@ There's some other modes we can use also: | `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. | -The `w` and `a` modes create a new file if it exists already, but trying -to read from a non-existent file is an error. - 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. -```py +```python >>> f.closed True ->>> +>>> ``` When we had opened the file we could just print to it. The print is just @@ -119,29 +120,29 @@ 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. -```py +```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` created the file for us, but -doing that with `r` gives us an error instead. We'll learn more about -errors [later](exceptions.md). +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). -```py +```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 @@ -150,26 +151,26 @@ 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 good practise +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. -So how does that work if we have more than one line in the file? +Let's see how that works if we have more than one line in the file. -```py +```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 @@ -180,25 +181,25 @@ 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: -```py +```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. -```py +```python >>> with open('hello.txt', 'r') as f: ... first_line = f.readline() ... second_line = f.readline() -... +... >>> first_line 'Hello one!\n' >>> second_line @@ -206,9 +207,10 @@ that reads the next line, and returns `''` if we're at the end of the file. ``` There's only one confusing thing about reading files. If we try -to read it twice we'll find out that it only gets read once: +to read the same file object twice we'll find out that it only gets read +once: -```py +```python >>> first = [] >>> second = [] >>> with open('hello.txt', 'r') as f: @@ -216,64 +218,50 @@ to read it twice we'll find out that it only gets read once: ... 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, it's in the beginning -again and everything works. +to read. But if we open the file again, we get a new file object that +is in the beginning and everything works. -```py +```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. -As we can see, files behave a lot like lists. [The join string -method](https://docs.python.org/3/library/stdtypes.html#str.join) joins -together strings from a list, but we can also use it to join together -lines of a file: - -```py ->>> with open('hello.txt', 'r') as f: -... full_content = ''.join(f) -... ->>> full_content -'Hello one!\nHello two!\nHello three!\n' ->>> -``` - -But if we need all of the content as a string, we can just use [the read +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). -```py +```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')`. @@ -289,34 +277,39 @@ 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". -```py ->>> r'C:\some\name' == 'C:\\some\\name' -True ->>> +```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. -```py +```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: -```py +```python while True: filename = input("Filename or path, or nothing at all to exit: ") if filename == '': @@ -333,7 +326,7 @@ 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. -```py +```python # Ask repeatedly until the user answers 'y' or 'n'. while True: answer = input("Have you been here before? (y/n) ") @@ -372,6 +365,14 @@ else: *** -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) +[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/handy-stuff-strings.md b/basics/handy-stuff-strings.md similarity index 64% rename from handy-stuff-strings.md rename to basics/handy-stuff-strings.md index 02c932d..98d5cc4 100644 --- a/handy-stuff-strings.md +++ b/basics/handy-stuff-strings.md @@ -2,27 +2,27 @@ Python strings are just pieces of text. -```py +```python >>> our_string = "Hello World!" >>> our_string 'Hello World!' ->>> +>>> ``` So far we know how to add them together. -```py +```python >>> "I said: " + our_string 'I said: Hello World!' ->>> +>>> ``` We also know how to repeat them multiple times. -```py +```python >>> our_string * 3 'Hello World!Hello World!Hello World!' ->>> +>>> ``` Python strings are [immutable](https://docs.python.org/3/glossary.html#term-immutable). @@ -40,51 +40,51 @@ 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: -```py +```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) +![Slicing with non-negative values](../images/slicing1.png) But what happens if we slice with negative values? -```py +```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) +![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: -```py +```python >>> our_string[1:] 'ello World!' >>> our_string[:-1] 'Hello World' ->>> +>>> ``` Remember that strings can't be changed in-place. -```py +```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 @@ -94,10 +94,10 @@ going to talk about it now. So now we know how slicing works. But what happens if we forget the `:`? -```py +```python >>> our_string[1] 'e' ->>> +>>> ``` That's interesting. We got a string that is only one character long. But @@ -108,7 +108,7 @@ 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. -```py +```python >>> our_string[0] 'H' >>> our_string[1] @@ -119,19 +119,19 @@ first character is `our_string[0]`, the second character is 'l' >>> our_string[4] 'o' ->>> +>>> ``` So string indexes work like this: -![Indexing with non-negative values](images/indexing1.png) +![Indexing with non-negative values](../images/indexing1.png) How about negative values? -```py +```python >>> our_string[-1] '!' ->>> +>>> ``` We got the last character. @@ -144,23 +144,30 @@ thing as indexing with 0. Indexing with negative values works like this: -![Indexing with negative values](images/indexing2.png) +![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. +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: -```py +```python >>> our_string.upper() 'HELLO WORLD!' >>> our_string.lower() @@ -171,6 +178,10 @@ True 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 @@ -209,109 +220,44 @@ them [later](lists-and-tuples.md). To add a string in the middle of another string, we can do something like this: -```py +```python >>> name = 'Akuli' >>> 'My name is ' + name + '.' 'My name is Akuli.' ->>> +>>> ``` But that gets complicated if we have many things to add. -```py +```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 are just different. Here's a few ways to solve -our problem: - -- `%s`-formatting, also known as printf-formatting and old-style - formatting. This has less features than `.format()`-formatting, but - `'Hello %s.' % name` is shorter and faster to type than - `'Hello {}.'.format(name)`. I like to use printf-formatting for simple - things and `.format` when I need more powerful features. - - ```py - >>> "Hello %s." % name - 'Hello Akuli.' - >>> "My name is %s and I'm on the %s channel on %s." % (name, channel, network) - "My name is Akuli and I'm on the ##learnpython channel on freenode." - >>> - ``` - - In the second example we had `(name, channel, network)` on the right - side of the `%` sign. It was a tuple, and we'll talk more about them - [later](lists-and-tuples.md#tuples). - - If we have a variable that may be a tuple we need to wrap it in another - tuple when formatting: - - ```py - >>> thestuff = (1, 2, 3) - >>> "we have %s" % thestuff - Traceback (most recent call last): - File "", line 1, in - TypeError: not all arguments converted during string formatting - >>> "we have %s and %s" % ("hello", thestuff) - 'we have hello and (1, 2, 3)' - >>> "we have %s" % (thestuff,) - 'we have (1, 2, 3)' - >>> - ``` - - Here `(thestuff,)` was a tuple that contained nothing but `thestuff`. - -- `.format()`-formatting, also known as new-style formatting. This - formatting style has a lot of features, but it's a little bit more - typing than `%s`-formatting. - - ```py - >>> "Hello {}.".format(name) - 'Hello Akuli.' - >>> "My name is {} and I'm on the {} channel on {}.".format(name, channel, network) - "My name is Akuli and I'm on the ##learnpython channel on freenode." - >>> - ``` +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-strings are even less typing, but new in Python 3.6. **Use this only if - you know that nobody will need to run your code on Python versions older - than 3.6.** Here the f is short for "format", and the content of the - string is same as it would be with `.format()` but we can use variables - directly. +`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. - ```py - >>> f"My name is {name} and I'm on the {channel} channel on {network}." - "My name is Akuli and I'm on the ##learnpython channel on freenode." - >>> - ``` - -All of these formatting styles have many other features also: - -```py ->>> 'Three zeros and number one: {:04d}'.format(1) -'Three zeros and number one: 0001' ->>> 'Three zeros and number one: %04d' % 1 -'Three zeros and number one: 0001' ->>> +```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." +>>> ``` -If you need to know more about formatting I recommend reading -[this](https://pyformat.info/). - ## Other things We can use `in` and `not in` to check if a string contains another string. -```py +```python >>> our_string = "Hello World!" >>> "Hello" in our_string True @@ -319,20 +265,20 @@ True 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". -```py +```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 @@ -340,7 +286,7 @@ We can convert strings, integers and floats with each other with behave a lot like functions. We'll learn more about what they really are [later](classes.md). -```py +```python >>> str(3.14) '3.14' >>> float('3.14') @@ -349,13 +295,13 @@ are [later](classes.md). '123' >>> int('123') 123 ->>> +>>> ``` Giving an invalid string to `int` or `float` produces an error message. -```py +```python >>> int('lol') Traceback (most recent call last): File "", line 1, in @@ -364,7 +310,7 @@ ValueError: invalid literal for int() with base 10: 'lol' Traceback (most recent call last): File "", line 1, in ValueError: could not convert string to float: 'hello' ->>> +>>> ``` ## Summary @@ -372,16 +318,16 @@ ValueError: could not convert string to float: 'hello' - Slicing returns a copy of a string with indexes from one index to another index. The indexes work like this: - ![Slicing](images/slicing3.png) + ![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) + ![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 rememeber something about them. +- 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. @@ -389,10 +335,45 @@ ValueError: could not convert string to float: 'hello' string. - `len(string)` returns string's length. - We can use `str`, `int` and `float` to convert values to different - types. + 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). *** -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) +[Previous](if.md) | [Next](lists-and-tuples.md) | +[List of contents](../README.md#basics) diff --git a/if.md b/basics/if.md similarity index 53% rename from if.md rename to basics/if.md index 5fa7bf8..71655f2 100644 --- a/if.md +++ b/basics/if.md @@ -4,7 +4,7 @@ Now we know what True and False are. -```py +```python >>> 1 == 1 True >>> 1 == 2 @@ -19,17 +19,17 @@ True But what if we want to execute different code depending on something? That's when `if` comes in. -```py +```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 @@ -40,43 +40,30 @@ An important thing to notice is that the line with a print is **indented**. You can press the tab key, or if it doesn't work just press space a few times. -IDLE does this a bit differently, so if you use IDLE, running the -example code looks more like this: - -```py ->>> its_raining = True ->>> if its_raining: - print("It's raining!") - - -It's raining! ->>> -``` - But why is that `if its_raining` instead of `if(its_raining)`? Earlier we learned that `if` is a **keyword**. -```py +```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. +**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. -```py +```python >>> its_raining = True >>> if its_raining: ... its_raining = False @@ -86,68 +73,12 @@ It's not raining, but this runs anyway. >>> ``` -## Storing code in files - -At this point it's easier to put our code into a file and use it -there. If you use IDLE, go to File at top left and select New File, or -just press Ctrl+N. - -![New File in IDLE](images/idle-new.png) - -If you don't use IDLE, please take the time to -[set up your editor correctly](editor-setup.md). When you're done your -editor should give you four spaces every time you press tab. - -Create a file called `rain.py`, and type the following content into it: - -```py -its_raining = True -if its_raining: - print("It's raining!") -``` - -You can save the file anywhere you want, for example on your desktop. -Give it a name that ends with `.py`, for example `rain.py`. The `.py` -is short for Python. - -Now we can run the rain program. Most editors (including IDLE) will -run our code when we press F5. If your editor doesn't, run it from -PowerShell, command prompt or terminal. You probably need to first go -to wherever you saved your file with `cd`. For example, if the file is -on your desktop, type `cd Desktop` before running the file. - -Running from IDLE looks like this: - - >>> - ========================= RESTART: /some/place/rain.py ========================= - It's raining! - >>> - -And running from the Windows PowerShell or command prompt looks like -this: - - C:\Users\You> cd Desktop - C:\Users\You\Desktop> py rain.py - It's raining! - C:\Users\You\Desktop> - -Running from a terminal looks like this: - - you@YourComputer:~$ cd Desktop - you@YourComputer:~/Desktop$ python3 rain.py - It's raining! - you@YourComputer:~/Desktop$ - -From now on, **if a code example starts with `>>>` run it on the -interactive prompt, and if it doesn't, write it to a file and run the -file**. - ## Using else What if we want to print a different message if it's not raining? We could do something like this: -```py +```python its_raining = True # you can change this to False its_not_raining = not its_raining # False if its_raining, True otherwise @@ -157,12 +88,15 @@ 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: -```py +```python its_raining = True if its_raining: @@ -173,7 +107,7 @@ if not its_raining: But we can make it even better by using `else`. -```py +```python its_raining = True if its_raining: @@ -185,7 +119,7 @@ else: The else part simply runs when the if statement doesn't run. It doesn't check the condition again. -```py +```python >>> its_raining = True >>> if its_raining: ... its_raining = False @@ -198,7 +132,7 @@ check the condition again. By combining `else` with the input function we can make a program that asks for a password and checks if it's correct. -```py +```python print("Hello!") password = input("Enter your password: ") @@ -208,19 +142,19 @@ else: print("Access denied.") ``` -The program prints different things depending on what we enter. +The program prints different things depending on what we enter: + +``` +Hello! +Enter your password: secret +Welcome! +``` - >>> ================================ RESTART ================================ - >>> - Hello! - Enter your password: secret - Welcome! - >>> ================================ RESTART ================================ - >>> - Hello! - Enter your password: lol - Access denied. - >>> +``` +Hello! +Enter your password: lol +Access denied. +``` Using the input function for passwords doesn't work very well because we can't hide the password with asterisks. There are better ways to get @@ -228,10 +162,9 @@ a password from the user, but you shouldn't worry about that just yet. ## Avoiding many levels of indentation with elif -If we have more than one condition to check, our code will end up -looking a bit messy. +If we have more than one condition to check, we could do this: -```py +```python print("Hello!") word = input("Enter something: ") @@ -253,10 +186,15 @@ 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: -```py +```python print("Hello!") word = input("Enter something: ") @@ -274,6 +212,54 @@ else: print("I don't know what", word, "means.") ``` +Now the program is shorter and much easier to read. + +Note that the `elif` parts only run if nothing before them matches, and +the `else` runs only when none of the `elifs` match. If we would have +used `if` instead, all possible values would be always checked and the +`else` part would run always except when word is `"gday m8"`. This is +why we use `elif` instead of `if`. + +For example, this program prints only `hello`... + +```python +if 1 == 1: + print("hello") +elif 1 == 2: + print("this is weird") +else: + print("world") +``` + +...but this prints `hello` *and* `world`: + +```python +if 1 == 1: + print("hello") +if 1 == 2: + print("this is weird") +else: + print("world") +``` + +Now the `else` belongs to the `if 1 == 2` part and **it has nothing to +do with the `if 1 == 1` part**. On the other hand, the elif version +**grouped the multiple ifs together** and the `else` belonged to all of +them. Adding a blank line makes this obvious: + +```python +if 1 == 1: + print("hello") + +if 1 == 2: + print("this is weird") +else: + print("world") +``` + +In general, adding blank lines to appropriate places is a good idea. If +you are asked to "fix code", feel free to add missing blank lines. + ## Summary - If a code example starts with `>>>` run it on the interactive prompt. @@ -286,55 +272,59 @@ else: ## Exercises -1. Write a program into a file that asks the user to write a word and +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 `hihihihi...`. + `hi` the program would reply `hihihihihihihihi` ... -2. Add spaces between the words, so the output is like `hi hi hi ...`. +4. Add spaces between the words, so the output is like `hi hi hi hi` ... -3. Make something that asks the user to enter two words, and prints +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 hello hi hello hi hello hi` ... -4. Make a program that asks for a password and prints `Welcome!`, +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. -5. Make a program that asks for username and password and checks them. - Make users "foo" and "bar" with passwords "biz" and "baz". - - 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! - >>> - ``` - -The answers are [here](answers.md). +The answers are [here](answers.md#if-else-and-elif). *** -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) +[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/lists-and-tuples.md b/basics/lists-and-tuples.md similarity index 62% rename from lists-and-tuples.md rename to basics/lists-and-tuples.md index 3350094..379d019 100644 --- a/lists-and-tuples.md +++ b/basics/lists-and-tuples.md @@ -4,7 +4,7 @@ Sometimes we may end up doing something like this. -```py +```python name1 = 'wub_wub' name2 = 'theelous3' name3 = 'RubyPinch' @@ -29,80 +29,80 @@ 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: -```py +```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) +![List of names.](../images/people.png) ## What can we do with lists? Let's open the `>>>` prompt and create a name list. -```py +```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. -```py ->>> len(names) # we have 5 names +```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. -```py +```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. -```py +```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. -```py +```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. +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. -```py +```python >>> names ['wub_wub', 'theelous3', 'RubyPinch', 'go|dfish', 'Nitori'] >>> names.remove('theelous3') # sorry theelous3 @@ -115,17 +115,63 @@ multiple items from another list and remove removes an item. >>> 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: -```py +```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 @@ -137,77 +183,75 @@ 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. -```py +```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). +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: -```py +```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 diagram of this example it looks like this: +line. If we draw a picture of the variables it looks like this: -![Same list.](images/samelist.png) +![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. -```py +```python >>> a is b True ->>> +>>> ``` Typing `[]` creates a **new** list every time. -```py +```python >>> [] is [] False >>> [1, 2, 3] is [1, 2, 3] False ->>> +>>> ``` -If we need a new list with the same content we can use the +If we need **a new list with similar content** we can use the `copy` method. -```py +```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) - -If you're using Python 3.2 or older you can do `a[:]` instead -of `a.copy()`. `a[:]` is a slice of the whole list, just like -`a[0:]`. +![Different lists.](../images/differentlist.png) ## Tuples @@ -215,21 +259,21 @@ 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 `[]`. -```py +```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`. -```py +```python >>> (3) 3 >>> (3,) @@ -238,30 +282,42 @@ used in places like `(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 theirselves in-place. +because they can't change themselves in-place. -```py +```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's -a few cases when we don't want mutability, but there's also +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 point to multiple values with just one variable. +- 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 @@ -275,7 +331,7 @@ post about this](http://nedbatchelder.com/blog/201608/lists_vs_tuples.html). Here's the same program we had in the beginning of this tutorial, but using a list: -```py +```python namelist = ['wub_wub', 'theelous3', 'RubyPinch', 'go|dfish', 'Nitori'] name = input("Enter your name: ") @@ -285,8 +341,48 @@ 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). + *** -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) +[Previous](handy-stuff-strings.md) | [Next](loops.md) | +[List of contents](../README.md#basics) diff --git a/loops.md b/basics/loops.md similarity index 53% rename from loops.md rename to basics/loops.md index 51d450f..7f693e1 100644 --- a/loops.md +++ b/basics/loops.md @@ -5,7 +5,7 @@ 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 a list. +- [For loops](#for-loops) repeat something for each element of something. We'll talk about all of these in this tutorial. @@ -13,15 +13,15 @@ We'll talk about all of these in this tutorial. Now we know how if statements work. -```py +```python its_raining = True if its_raining: print("Oh crap, it's raining!") ``` -While loops are really similar to if statements. +While loops are really similar to if statements. -```py +```python its_raining = True while its_raining: print("Oh crap, it's raining!") @@ -58,7 +58,7 @@ have ended and the program would have printed `It's not raining anymore`. Let's actually create a program that does just that: -```py +```python its_raining = True while its_raining: print("It's raining!") @@ -87,7 +87,7 @@ Running the program may look like this: The while loop doesn't check the condition all the time, it only checks it in the beginning. -```py +```python >>> its_raining = True >>> while its_raining: ... its_raining = False @@ -101,7 +101,7 @@ 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 +```python while True: answer = input("Is it raining? (y=yes, n=no) ") if answer == 'y': @@ -115,7 +115,7 @@ while True: The program works like this: - Is it raining? (y=yes, n=no) who knows + Is it raining? (y=yes, n=no) who knows Enter y or n. Is it raining? (y=yes, n=no) y It's raining! @@ -125,7 +125,7 @@ The program works like this: Unlike setting the condition to False, breaking the loop ends it immediately. -```py +```python >>> while True: ... break ... print("This is never printed.") @@ -138,7 +138,7 @@ immediately. Python doesn't have until loops. If we need an until loop, we can use `while not`: -```py +```python raining = False while not raining: print("It's not raining.") @@ -149,10 +149,10 @@ 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: +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: -```py +```python stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] print(stuff[0]) @@ -176,70 +176,61 @@ 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 +```python >>> stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] ->>> length_of_stuff = len(stuff) # len(stuff) is 5 +>>> 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 there's `len()` and an index variable we need to increment and a +But we have `len()` and an index variable we need to increment and a while loop and many other things to worry about. That's a lot of work just for printing each item. This is when for loops come in: -```py +```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. -```py +```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: - -```py ->>> for (thing in stuff): - File "", line 1 - for (thing in stuff): - ^ -SyntaxError: invalid syntax ->>> -``` +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 @@ -247,80 +238,269 @@ 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: -```py +```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. -```py +```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): -```py +```python >>> stuff = ['hello', 'hi', 'how are you doing', 'im fine', 'how about you'] >>> stuff.clear() >>> stuff [] ->>> +>>> ``` -If you're using Python 3.2 or older you need to use `stuff[:]` instead -of `stuff.copy()` and `stuff[:] = []` instead of `stuff.clear()`. -`stuff[:]` is a slice of the whole list, just like `stuff[0:]`. - ## Summary - A loop means repeating something multiple times. - 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. 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 the program: - - ```py - users = [ - ['foo', 'biz'], - ['bar', 'baz'], - ] +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) ``` - Then rewrite the rest of the program using a for loop. +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. -2. Make the program ask the username and password over and over again - until the user enters them correctly. + ```python + input = ['1', '2', '3'] -3. Can you limit the number of attempts to 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). *** -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) +[Previous](lists-and-tuples.md) | [Next](zip-and-enumerate.md) | +[List of contents](../README.md#basics) diff --git a/modules.md b/basics/modules.md similarity index 79% rename from modules.md rename to basics/modules.md index c233310..59159cd 100644 --- a/modules.md +++ b/basics/modules.md @@ -1,9 +1,9 @@ # Modules -Let's say we want to generate a random number between 1 and -3. The random module is a really easy way to do this: +Let's say we want to generate a random number between 1 and 3. +The random module is a really easy way to do this: -```py +```python >>> import random >>> random.randint(1, 3) 3 @@ -15,7 +15,7 @@ Let's say we want to generate a random number between 1 and 2 >>> random.randint(1, 3) 2 ->>> +>>> ``` That's cool... but how does that work? @@ -26,19 +26,19 @@ The first line in the example, `import random`, was an **import statement.** But what is that random thing that it gave us? -```py +```python >>> random - ->>> + +>>> ``` So it's a module, and it comes from a path... but what does all that mean? -Now open the folder that contains your `random.py` is. On my -system it's `/usr/lib/python3.4`, but yours will probably be +Now open the folder that contains your `random.py`. On my +system it's `/usr/lib/python3.7`, but yours will probably be different. To open a folder in your file manager you can press -Windows-R on Windows or Alt+F2 on most GNU/Linux distributions, +Windows-R on Windows or Alt+F2 on most Linux distributions, and just type your path there. I don't have an up-to-date copy of OSX so unfortunately I have no idea what you need to do on OSX. @@ -46,18 +46,23 @@ OSX. You'll see a bunch of files and a few directories in the folder that opens: -![My Python's modules.](images/modules.png) +![My Python's modules.](../images/modules.png) All of these `.py` files can be imported like we just imported -`random.py`. In random.py, there's a like like `randint = something`, +`random.py`. In random.py, there's a line like `randint = something`, so we can use its randint variable with `random.randint` after importing it. +You're probably wondering how a computer can generate random numbers. +The random module does different things on different operating systems, +but on most systems it reads random noise that several programs on the +computer produce and creates random numbers based on that. + ## Where do modules come from? Create a `random.py` file with the following content: -```py +```python import random print("A random number between 1 and 3:", random.randint(1, 3)) @@ -65,7 +70,7 @@ print("A random number between 1 and 3:", random.randint(1, 3)) Now run the program. -```py +```python Traceback (most recent call last): File "random.py", line 1, in import random @@ -76,31 +81,33 @@ AttributeError: 'module' object has no attribute 'randint' But what was that? Why didn't it work? +**TODO:** update the `-i` instructions. + Let's go ahead and check what's wrong. If you don't use IDLE, you'll need to pass the `-i` option to Python, so if you would normally run -`python3 random.py` you should now do `python3 -i random.py`. This -will run the file and then give you a `>>>` prompt that we can use -to check what's wrong. If you use IDLE, just run the file normally. +`python3 random.py` you should now do `python3 -i random.py`. This will +run the file and then give you a `>>>` prompt that we can use to check +what's wrong. If you use IDLE, just run the file normally. We should end up with the same error message, and then a `>>>`. Like this: -```py +```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? +So first of all, what is that `random` variable? -```py +```python >>> random ->>> +>>> ``` What the heck? It's a module called random... but it's not the @@ -125,19 +132,19 @@ 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: -```py +```python >>> import sys >>> sys >>> sys.path ['', - '/usr/lib/python3.4', - '/usr/lib/python3.4/plat-i386-linux-gnu', - '/usr/lib/python3.4/lib-dynload', - '/home/akuli/.local/lib/python3.4/site-packages', - '/usr/local/lib/python3.4/dist-packages', + '/usr/lib/python37.zip', + '/usr/lib/python3.7', + '/usr/lib/python3.7/lib-dynload', + '/home/akuli/.local/lib/python3.7/site-packages', + '/usr/local/lib/python3.7/dist-packages', '/usr/lib/python3/dist-packages'] ->>> +>>> ``` So that's where my Python finds its modules. The first thing in my @@ -148,30 +155,30 @@ working directory. Let's create a file called `hello.py` that contains a classic greeting: -```py +```python print("Hello World!") ``` Let's go ahead and import it, and see how it works. -```py +```python >>> import hello Hello World! ->>> +>>> ``` Works as expected, but what happens if we try to import it again? -```py +```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 +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. @@ -190,7 +197,7 @@ about that in the official documentation. The official documentation is [here](https://docs.python.org/3/library/random.html). -```py +```python >>> import random >>> random.randint(1, 3) # 1, 2 or 3 3 @@ -202,7 +209,7 @@ The official documentation is >>> random.shuffle(colors) # mix the color list in-place >>> colors ['yellow', 'red', 'blue'] ->>> +>>> ``` ### Things that are built into Python @@ -211,12 +218,11 @@ The module name "sys" is short for "system", and it contains things that are built into Python. The official documentation is [here](https://docs.python.org/3/library/sys.html). -```py +`sys.stdin`, `sys.stdout` and `sys.stderr` are [file objects](files.md), +just like the file objects that `open()` gives us. + +```python >>> import sys ->>> # special files that the print and input functions use ->>> # stdin is short for standard input ->>> # stdout is short for standard output ->>> # stderr is short for standard errors >>> print("Hello!", file=sys.stdout) # this is where prints go by default Hello! >>> print("Hello!", file=sys.stderr) # use this for error messages @@ -225,25 +231,24 @@ Hello! hello >>> line 'hello\n' ->>> +>>> >>> # information about Python's version, behaves like a tuple >>> sys.version_info -sys.version_info(major=3, minor=4, micro=2, releaselevel='final', serial=0) ->>> sys.version_info[:3] # this is Python 3.4.2 -(3, 4, 2) ->>> +sys.version_info(major=3, minor=7, micro=3, releaselevel='final', serial=0) +>>> sys.version_info[:3] # this is Python 3.7.3 +(3, 7, 3) +>>> >>> sys.exit() # exit out of Python ``` -If you use IDLE you'll notice that printing to `sys.stderr` makes -the message show up in red instead of the normal blue. +**TODO:** why stderr instead of stdout, when to use `sys.stdin.readline()` instead of `input()` `sys.exit()` does the same thing as `sys.exit(0)`. The zero means that the program succeeded, and everything's fine. If our program has an error we should print an error message to `sys.stderr` and then call `sys.exit(1)`. Like this: -```py +```python if something_went_wrong: # of course, we need to make real error messages more # informative than this example is @@ -251,16 +256,13 @@ if something_went_wrong: sys.exit(1) ``` -`sys.exit` doesn't work for getting out of IDLE's `>>>` prompt. You can -just close the window to get out of IDLE. - ### Mathematics There's no math.py anywhere, math is a built-in module like sys. The official documentation is [here](https://docs.python.org/3/library/math.html). -```py +```python >>> import math >>> math @@ -274,7 +276,7 @@ sys. The official documentation is 90.0 >>> math.sin(math.pi/2) # sin of 90 degrees or 1/2 π radians 1.0 ->>> +>>> ``` ### Time-related things @@ -282,14 +284,14 @@ sys. The official documentation is The official documentation for the time module is [here](https://docs.python.org/3/library/time.html). -```py +```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 -'26.09.2016 16:33:58' ->>> +'07.04.2017 19:08:33' +>>> ``` You are probably wondering how `time.time()` can be used and why its @@ -311,12 +313,12 @@ 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). -```py +```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') @@ -329,7 +331,7 @@ True 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 @@ -337,17 +339,17 @@ True >>> 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. -```py +```python import random print("Enter things to mix, and press Enter without typing", @@ -367,10 +369,10 @@ for thing in things: ``` Measure how long it takes for the user to answer a question. -The `%.2f` rounds to 2 decimals, and you can find more formatting -tricks [here](https://pyformat.info/). +The `{:.2f}` rounds to 2 decimals, and you can find more formatting +tricks [here](https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals). -```py +```python import time start = time.time() @@ -379,14 +381,14 @@ end = time.time() difference = end - start if answer == '3': - print("Correct! That took %.2f seconds." % difference) + print(f"Correct! That took {difference:.2f} seconds.") else: print("That's not correct...") ``` Wait a given number of seconds. -```py +```python import sys import time @@ -404,11 +406,11 @@ print("Done!") Check what a path points to. -```py +```python import os import sys -print("You are currently in %s." % os.getcwd()) +print(f"You are currently in {os.getcwd()}.") while True: path = input("A path, or nothing at all to quit: ") @@ -432,7 +434,7 @@ while True: ## 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 +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 @@ -454,21 +456,19 @@ then typing in what you want to search for. - [textwrap](https://docs.python.org/3/library/textwrap.html): break long text into multiple lines - [warnings](https://pymotw.com/3/warnings/): - like exceptions, but they don't interrupt the whole program + like [exceptions](exceptions.md), but they don't interrupt the + whole program - [webbrowser](https://pymotw.com/3/webbrowser/): open a web browser from Python -I also use these modules, but they don't come with Python so you'll -need to install them yourself if you want to use them: - -- [appdirs](https://github.com/activestate/appdirs): - an easy way to find out where to put setting files -- [requests](http://docs.python-requests.org/en/master/user/quickstart/): - an awesome networking library +There are also lots of awesome modules that don't come with Python. +You can search for those on the [Python package index](https://pypi.org/), +or PyPI for short. It's often better to find a library that does something +difficult than to spend a lot of time trying to do it yourself. I recommend reading [the official documentation about installing -modules](https://docs.python.org/3/installing/). If you're using -GNU/Linux also read the "Installing into the system Python on Linux" +modules](https://docs.python.org/3/installing/) from PyPI. If you're using +Linux, then also read the "Installing into the system Python on Linux" section at the bottom. ## Summary @@ -484,8 +484,18 @@ section at the bottom. - Python comes with many modules, and we can install even more modules if we want to. +**TODO:** exercises + *** -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) +[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 77% rename from the-way-of-the-program.md rename to basics/the-way-of-the-program.md index 9de882a..7def31a 100644 --- a/the-way-of-the-program.md +++ b/basics/the-way-of-the-program.md @@ -38,6 +38,14 @@ learned everything. *** -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#list-of-contents) +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/variables.md b/basics/variables.md similarity index 67% rename from variables.md rename to basics/variables.md index 233bb22..f1a8030 100644 --- a/variables.md +++ b/basics/variables.md @@ -4,7 +4,7 @@ Variables are easy to understand. They simply **point to values**. -```py +```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 @@ -16,74 +16,63 @@ Variables are easy to understand. They simply **point to values**. Let's draw a diagram of these variables. -![Variable diagram](images/variables1.png) +![Variable diagram](../images/variables1.png) We can also change the value of a variable after setting it. -```py +```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) +![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. -```py +```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. -```py +```python >>> thingy Traceback (most recent call last): File "", line 1, in NameError: name 'thingy' is not defined ->>> +>>> ``` -Variables are simple to understand, but there's a few details that we +Variables are simple to understand, but there are a few details that we need to keep in mind: - Variables always point to a value, **they never point to other - variables**. That's why the arrows in our diagrams always go left - to right. + variables**. That's why the arrows in our diagrams always go left + to right. - Multiple variables can point to the same value, but one variable - cannot point to multiple values. + cannot point to multiple values. - The values that variables point to can point to other values also. - We'll learn more about that when we'll talk about - [lists](lists-and-tuples.md). + We'll learn more about that when we'll talk about + [lists](lists-and-tuples.md). Variables are an important part of most programming languages, and they allow programmers to write much larger programs than they could write without variables. -Variable names can be multiple characters long. They can contain -uppercase characters, numbers and some other characters, but most of the -time we should use simple, lowercase variable names. You can also use -underscores. - -```py ->>> magic_number = 123 ->>> greeting = "Hello World!" ->>> -``` - Variable names are case-sensitive, like many other things in Python. -```py +```python >>> thing = 1 >>> THING = 2 >>> thIng = 3 @@ -96,19 +85,20 @@ Variable names are case-sensitive, like many other things in Python. >>> ``` -Python also has some words that cannot be used as variable names -because they have a special meaning. They are called **keywords**, and -we can run `help('keywords')` to see the full list if we want to. +There are also words that cannot be used as variable names +because they are reserved by Python itself and have a special meaning. +They are called **keywords**, and we can run `help('keywords')` +to see the full list if we want to. We'll learn to use most of them later in this tutorial. Trying to use a keyword as a variable name causes a syntax error. -```py +```python >>> if = 123 File "", line 1 if = 123 ^ SyntaxError: invalid syntax ->>> +>>> ``` When assigning something to a variable using a `=`, the right side of @@ -116,68 +106,98 @@ 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 +```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. -```py +```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. -```py +```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: -```py +```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. -```py +```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. -```py +```python >>> a = 1 >>> a == 1 True @@ -188,7 +208,7 @@ True False >>> (a == 1) == True False ->>> +>>> ``` ## None @@ -201,10 +221,10 @@ None later. None's behavior on the interactive prompt might be a bit confusing at first: -```py +```python >>> thingy = None >>> thingy ->>> +>>> ``` That was weird! We set thingy to None, but typing `thingy` didn't echo @@ -216,17 +236,18 @@ None coming up all the time. If we want to see a None on the interactive prompt, we can use print. -```py +```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. +are talking about is always None. We'll learn more about what attributes +and calling are later. -```py +```python >>> None.hello # None has no attribute 'hello' Traceback (most recent call last): File "", line 1, in @@ -235,14 +256,13 @@ AttributeError: 'NoneType' object has no attribute 'hello' 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. At this -point, this list probably looks awfully long, but it's actually pretty -easy to learn. +So far we've used `==`, but there are other operators also. This list +probably looks awfully long, but it's actually quite easy to learn. | Usage | Description | True examples | |-----------|-----------------------------------|-----------------------| @@ -261,17 +281,34 @@ b are Booleans. | `a and b` | a is True and b is True | `1 == 1 and 2 == 2` | | `a or b` | a is True, b is True or they're both True | `False or 1 == 1`, `True or True` | -Another way to combine operations is chaining. For example, `a < b < c` -does the same thing as `a < b and b < c`. - `not` can be used for negations. If `value` is True, `not value` is False, and if `value` is False, `not value` is True. There's also `is`, but don't use it instead of `==` unless you know what you are doing. We'll learn more about it later. +## Summary + +- Variables have a name and a value. We can create or change variables + with `name = value`. +- `thing += stuff` does the same thing as `thing = thing + stuff`. +- Use lowercase variable names and remember that programmers hate + figuring out what x is. +- `=` means assigning and `==` means comparing. +- True and False are Booleans. Comparing values results in a Boolean. +- None is a value that we'll find useful later. When error messages say + `NoneType object` they mean None. + *** -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) +[Previous](the-way-of-the-program.md) | [Next](using-functions.md) | +[List of contents](../README.md#basics) diff --git a/what-is-programming.md b/basics/what-is-programming.md similarity index 53% rename from what-is-programming.md rename to basics/what-is-programming.md index 858b53f..269ce3f 100644 --- a/what-is-programming.md +++ b/basics/what-is-programming.md @@ -1,23 +1,49 @@ # 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. 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. +**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 @@ -25,7 +51,7 @@ 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) +![A key on my keyboard.](../images/key.png) Here's what the characters on this key mean: @@ -47,7 +73,7 @@ their meanings later. | Character | Names | |-----------|---------------------------------------| | `+` | plus | -| `-` | minus, dash, hyphen | +| `-` | minus, dash | | `_` | underscore | | `*` | star, asterisk | | `/` | forwardslash (it's leaning forward) | @@ -78,12 +104,12 @@ should you do if you have a problem with the tutorial? 1. Try the example code yourself. 2. Read the code and the explanation for it again. 3. If there's something you haven't seen before in the tutorial and it's - not explained, try to find it from the previous chapters. -4. If 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 + 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 @@ -97,23 +123,48 @@ 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. -- Don't fear mistakes. -- Error messages are your friends. +- 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)! *** -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). + +[Previous](../README.md) | [Next](installing-python.md) | +[List of contents](../README.md#basics) diff --git a/what-is-true.md b/basics/what-is-true.md similarity index 82% rename from what-is-true.md rename to basics/what-is-true.md index 46a75f3..a344239 100644 --- a/what-is-true.md +++ b/basics/what-is-true.md @@ -2,7 +2,7 @@ Now we understand how code like this works. -```py +```python message = input("Enter something: ") if message == '': print("You didn't enter anything!") @@ -13,7 +13,7 @@ else: But most Python programmers would write that code like this instead: -```py +```python message = input("Enter something: ") if message: print("You entered:", message) @@ -36,7 +36,7 @@ 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. -```py +```python >>> bool('hello') True >>> bool('there') @@ -45,23 +45,23 @@ 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 `""`: -```py +```python >>> bool('') False ->>> +>>> ``` Most other things are also treated as False if they're empty and True if they're not empty. -```py +```python >>> bool([1, 2, 3]) True >>> bool([]) @@ -74,13 +74,13 @@ False True >>> bool({}) False ->>> +>>> ``` None and zero are also falsy, but positive and negative numbers are treated as True. -```py +```python >>> bool(None) False >>> bool(0) @@ -91,17 +91,17 @@ False True >>> bool(-1) True ->>> +>>> ``` Most other things are also treated as True. -```py +```python >>> bool(OSError) True >>> bool(print) True ->>> +>>> ``` ## When and why should we use Boolean values of things? @@ -116,13 +116,13 @@ something else than a list. It thinks that empty tuples, strings and dictionaries aren't empty just because they aren't empty lists: -```py +```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([]) @@ -133,20 +133,20 @@ It's not empty. It's not empty. >>> is_this_empty({}) It's not empty. ->>> +>>> ``` We could improve the code by checking against different empty things. -```py +```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 @@ -154,21 +154,21 @@ 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: -```py +```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: -```py +```python if number != 0: print("number is not zero") @@ -178,7 +178,7 @@ if value is not None: Not like this: -```py +```python if number: print("number is not zero") @@ -194,7 +194,7 @@ to make sure that we don't treat values like that as None. So here's how we should check if something is None: -```py +```python if not value: ... # not good if we want to check if it's None if value == None: ... # better if value is None: ... # best @@ -203,10 +203,24 @@ 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. + 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. + 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. + 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 d71b65f..0306c61 100644 --- a/classes.md +++ b/classes.md @@ -1,423 +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](using-functions.md) -and [how to create your own functions](defining-functions.md). If you -don't I highly recommend learning that first, and then moving to classes. - -## 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 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 ->>> -``` - -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`? - -```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. 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: - -```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() -``` - -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: - -```py -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. +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) +[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 42085cd..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#list-of-contents) +You may use this tutorial freely at your own risk. See +[LICENSE](./LICENSE). + +[List of contents](./README.md#list-of-contents) diff --git a/dicts.md b/dicts.md deleted file mode 100644 index 8ca59c8..0000000 --- a/dicts.md +++ /dev/null @@ -1,193 +0,0 @@ -# Dictionaries - -**TODO:** write the lists-and-tuples.md this tutorial links to. - -Now we know how [lists and tuples](lists-and-tuples.md) work and how -to [for loop](loops.md#for-loops) over them. We also did an exercise -with code like this: - -```py -userlist = [ - ('me', 'my password'), - ('you', 'your password'), -] -``` - -Then to check if a username and password were correct we did -`(username, password) in userlist`. Adding new users was also easy as -appending to that list. - -What if we need to check if a username is in the users, but we don't -need to know the password? `username in userlist` is always False -because the user list consists of (username,password) pairs, so we need -to for loop over the whole list: - -```py -username_exists = False -for user in userlist: - if user[0] == username: - username_exists = True - break -if username_exists: - # do something -``` - -Getting a user's password also requires a similar loop: - -```py -password = None -for user in userlist: - if user[0] == username: - password = user[1] - break -# make sure password isn't still None and do something with it -``` - -This works just fine because our user list only contains two users, but -it would be slow if the userlist was bigger. - -## What are dictionaries? - -A better way to store user information might be a dictionary. - -```py -passwords = { - 'me': 'my password', - 'you': 'your password', -} -``` - -Here `'me'` and `'you'` are **keys** in the dictionary, and -`'my password'` and `'your password'` are their **values**. Dictionaries -are often named by their values. This dictionary has passwords as its -values so I named the variable `passwords`. - -There are a few big differences between dictionaries and lists of pairs: - -- Dictionaries are not ordered. There's **no guarantees** about which - order the username:password pairs appear in when we do something - with the dictionary. -- Checking if a key is in the dictionary is simple and fast. We don't - need to for loop through the whole dictionary. -- Getting the value of a key is also simple and fast. -- We can't have the same key in the dictionary multiple times, but - multiple different keys can have the same value. This means that - **multiple users can't have the same name, but they can have the - same passwords**. - -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. - -```py ->>> globals() -{'userlist': [('me', 'my password'), ('you', 'your password')], - 'passwords': {'me': 'my password', 'you': 'your password'}, - ...many other things we don't need to care about... -} ->>> -``` - -So if you have trouble remembering how dictionaries work just compare -them to variables. - -## What can we do with dictionaries? - -Dictionaries have some similarities with lists. For example, both -lists and dictionaries have a length. - -```py ->>> len(userlist) # contains two elements -2 ->>> len(passwords) # contains two key:value pairs -2 ->>> -``` - -We can get a value of a key with `the_dict[key]`. Trying to get the -value of a non-existing key gives us an error. We can also add new -key:value pairs by doing `the_dict[key] = value`. - -```py ->>> passwords['me'] -'my password' ->>> passwords['you'] -'your password' ->>> passwords['lol'] -Traceback (most recent call last): - File "", line 1, in -KeyError: 'lol' ->>> passwords["lol"] = "lol's password" ->>> passwords -{'lol': "lol's password", 'you': 'your password', 'me': 'my password'} ->>> -``` - -For looping over a dictionary gets its keys, and checking if something's -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. - -```py ->>> 'me' in passwords -True ->>> 'my password' in passwords -False ->>> for name in passwords: -... print(name) -... -lol -you -me ->>> -``` - -Dictionaries have a values method that we can use if we want to do -something with the values: - -```py ->>> passwords.values() -dict_values(["lol's password", 'your password', 'my password']) ->>> -``` - -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. - -```py ->>> for password in passwords.values(): -... print(password) -... -lol's password -your password -my password ->>> -``` - -We can do things like `list(passwords.values())` if we need a real list -for some reason, but doing that can slow down our program if the -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. - -```py ->>> passwords.items() -dict_items([('lol', "lol's password"), - ('you', 'your password'), - ('me', 'my password')]) ->>> for name, password in passwords.items(): -... print(name + ": " + password) -... -lol: lol's password -you: your password -me: my password ->>> -``` - -**TODO:** lists as keys vs tuples as keys. diff --git a/editor-setup.md b/editor-setup.md deleted file mode 100644 index 4d5a0b3..0000000 --- a/editor-setup.md +++ /dev/null @@ -1,132 +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#if-you-like-working-with-powershell-command-prompt-or-terminal) -for trying out things. You also need an editor for writing code that will -be stored in files. - -If you use IDLE as your editor, **it comes with everything set up for -you, and you don't need to worry about setting up anything**. If you -don't, you probably need to change some settings to make your editor -suitable for Python use. - -Do **not** use word processors like Microsoft Word and LibreOffice -Writer for programming. They create their own files, but you need plain -text files for programming. - -Start by creating an empty file called `hello.py` and opening it with -your editor. Or just open your editor and save a file as `hello.py`. - -## Automatic tab expanding - -This is important. Never use tabs in Python. Nobody else is using tabs, -and the official style guide tells you to never use tabs. - -However, **you don't need to press the spacebar four times every time -you want to indent**. Your editor should give you four spaces when you -hit the tab key. Some editors also remove four spaces when you hit -backspace and there are four spaces before the cursor. - -### Geany - -1. Go to *Edit* at the top and select Preferences. -2. Go to *Editor* at left. -2. Go to *Indenting* at top. -4. Select *Spaces* instead of *Tabs*. - -### gedit and pluma - -1. Go to Edit at the top and select Preferences. -2. Go to Editor at top. -3. Change the indent width to 4 and select *Add spaces instead of tabs*. - -### GNU Emacs - -Emacs uses spaces with Python files by default. - -### 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. Or if you - are using Windows, run `python` on a terminal and enter these - commands: - - ```py - >>> import sys - >>> print(sys.executable) - ``` - - You'll get a path to your python.exe. Replace `python` in the build - commands with this path. Most importantly, your *Execute* command - should be `"C:\your\path" "%f"`. - -### gedit, pluma and Mousepad - -These editors don't support running programs with F5. - -### GNU Emacs - -Usually I write something in Emacs, then I press Ctrl+Z to suspend -Emacs, run the program myself and then I run `fg` to get back to Emacs. -If you know how to run Python programs in Emacs and you'd like to write -about it here, [tell me](contact-me.md). - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Back to the list of contents](README.md#list-of-contents) diff --git a/getting-help.md b/getting-help.md index fbb592c..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) +[List of contents](./README.md#list-of-contents) diff --git a/getting-started.md b/getting-started.md deleted file mode 100644 index 9ec319c..0000000 --- a/getting-started.md +++ /dev/null @@ -1,218 +0,0 @@ -# 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. - -```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](what-is-programming.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. Later we'll learn what `(3, 14)` is. - -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. - -If you're not using IDLE, the prompt will change from `>>>` to -`...`. Just press Enter again to get it back to `>>>`. - -```py ->>> # hello again -... ->>> -``` - -In Python, these pieces of text starting with a `#` are known as -**comments**. They don't change how the code works in any way, but -we can use them to explain what our code does. - -## Using Python as a calculator - -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. We'll learn more about what they do later. - -```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](#summary) if you're not -interested. - -The `//` operator will divide and then throw away the dot and everything -after it. For example, `17 / 3` is `5.666666666666667`, and so `17 // 3` -is `5` because we throw away the `.666666666666667` part. - -```py ->>> 17 / 3 -5.666666666666667 ->>> 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. -- We can enter any Python commands to the interactive `>>>` prompt, and - it will echo back the result. -- Pieces of text starting with a `#` are comments. -- `+`, `-`, `*` and `/` work in Python just like in math. - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Back to the list of contents](README.md#list-of-contents) diff --git a/html-style.css b/html-style.css new file mode 100644 index 0000000..b9524a9 --- /dev/null +++ b/html-style.css @@ -0,0 +1,10 @@ +/* This file is used by the HTML files that make-html.py creates. + Customize this if you want to create HTML files with different + colors. See also make-html.py's --pygments-style option. */ +body { + color: white; + background-color: #222222; +} +a { + color: orange; +} diff --git a/images/discord-explore.png b/images/discord-explore.png new file mode 100644 index 0000000..1162495 Binary files /dev/null and b/images/discord-explore.png differ diff --git a/images/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 index 12799b3..e890e1c 100644 Binary files a/images/drawings.odg 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/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/powershell.png b/images/powershell.png new file mode 100644 index 0000000..72aaf6a Binary files /dev/null and b/images/powershell.png differ diff --git a/images/py-exe.png b/images/py-exe.png new file mode 100644 index 0000000..bce75f2 Binary files /dev/null and b/images/py-exe.png differ diff --git a/images/terminal.png b/images/terminal.png new file mode 100644 index 0000000..da293f1 Binary files /dev/null and b/images/terminal.png differ diff --git a/installing-python.md b/installing-python.md deleted file mode 100644 index 0004384..0000000 --- a/installing-python.md +++ /dev/null @@ -1,114 +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. Install Python like any other program. 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](images/idle.png) - -From now on, I'll instead show everything like this, so I don't have to -take more screenshots: - - Python 3.4.3 (default, Oct 14 2015, 20:28:29) - [GCC 4.8.4] on linux - Type "copyright", "credits" or "license()" for more information. - >>> - -The exact content of your Python's welcome message is probably different -than mine, it's ok. - -### 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, you should have a terminal application installed -already. Run it and type `python3`: - - 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 you can type `exit()` and press Enter to get out of Python. - -You may also have an older version of Python installed, but don't remove -it. Your system may need it, so if you replace it with your own Python -some things might stop working. See -[this](https://docs.python.org/3/faq/installed.html) for more info. - -## 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). - -[Back to the list of contents](README.md#list-of-contents) 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/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/dicts-and-tuples.md b/todo/dicts-and-tuples.md deleted file mode 100644 index ff2fed0..0000000 --- a/todo/dicts-and-tuples.md +++ /dev/null @@ -1,3 +0,0 @@ -# Dictionaries and tuples - -So far we know how to store multiple diff --git a/todo/genexample.py b/todo/genexample.py deleted file mode 100644 index 0016a05..0000000 --- a/todo/genexample.py +++ /dev/null @@ -1,18 +0,0 @@ -def thing(): - print("starting") - print("blah blah blah") - print("yielding 1") - yield 1 -# -------------------- CUT HERE -------------------- - print("running") - print("blah blah blah") - print("yielding 2") - yield 2 -# -------------------- CUT HERE -------------------- - print("done") - - -t = Thing() # run nothing at all -print("got from next(t):", next(t)) # run the first piece -print("got from next(t):", next(t)) # run the second piece -print("got from next(t):", next(t)) # run the last piece and raise StopIteration diff --git a/todo/iterators.md b/todo/iterators.md deleted file mode 100644 index e72b503..0000000 --- a/todo/iterators.md +++ /dev/null @@ -1,40 +0,0 @@ -# Iterables and iterators - -We have used for loops in many exercises and other things so far. One of -the simplest things we can for loop over are lists. - -```py ->>> for item in ['a', 'b', 'c']: -... print(item) -... -a -b -c ->>> -``` - -But what exactly is happening behind the scenes? - -## Iterables - -An **iterable** is anything we can put after a `for item in`. For -example, strings, lists, tuples and dictionaries are all iterable. -Iterating over an iterable is simple: - -```py -string = 'hello' -for character in string: - print(character) -``` - -You might think that under the covers Python does something like this: - -```py -string = 'hello' -index = 0 -while index < len(string): - print(string[index]) - index += 1 -``` - -But actually, th diff --git a/todo/yield-example.py b/todo/yield-example.py deleted file mode 100644 index 627b8a7..0000000 --- a/todo/yield-example.py +++ /dev/null @@ -1,18 +0,0 @@ -def thing(): - print("starting") - print("blah blah blah") - print("yielding 1") - yield 1 -# -------------------- CUT HERE -------------------- - print("running") - print("blah blah blah") - print("yielding 2") - yield 2 -# -------------------- CUT HERE -------------------- - print("done") - - -t = Thing() # run nothing at all -print("got", next(t)) # run the first piece -print("got", next(t)) # run the second piece -print("got", next(t)) # run the last piece and raise StopIteration diff --git a/trey-hunner-zip-and-enumerate.md b/trey-hunner-zip-and-enumerate.md deleted file mode 100644 index 7f4596d..0000000 --- a/trey-hunner-zip-and-enumerate.md +++ /dev/null @@ -1,140 +0,0 @@ -# Trey Hunner: zip and enumerate - -Now we know how [for loops](loops.md#for-loops) work in Python. But -for loops aren't limited to printing each item in a list, they can -do a lot more. - -To be able to understand for loop tricks we need to first know -assigning values to multiple variables at once. It works like this: - -```py ->>> 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. - -```py ->>> [a, b] = (1, 2) ->>> a -1 ->>> b -2 ->>> -``` - -We can also have `[]` or `()` on one side but not on the other -side. - -```py ->>> (a, b) = 1, 2 ->>> a -1 ->>> b -2 ->>> -``` - -Python created a tuple automatically. - -```py ->>> 1, 2 -(1, 2) ->>> -``` - -If we're for looping over a list with pairs of values in it we -could do this: - -```py ->>> 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. - -```py ->>> for a, b in items: -... print(a, b) -... -a 1 -b 2 -c 3 ->>> -``` - -Now you're ready to read [this awesome looping -tutorial](http://treyhunner.com/2016/04/how-to-loop-with-indexes-in-python/). -Read it now, then come back here and do the exercises. - -## Exercises - -1. Create a program that works like this. Here I entered everything - after the `>` prompt that the program displayed. - - ``` - Enter something, and press Enter without typing anything when you're done. - >hello there - >this is a test - >it seems to work - > - Line 1 is: hello there - Line 2 is: this is a test - Line 3 is: it seems to work - ``` - -2. Create a program that prints all letters from A to Z and a to z - next to each other: - - ``` - A a - B b - C c - ... - X x - Y y - Z z - ``` - - Start your program like this: - - ```py - 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). - -*** - -You may use this tutorial freely at your own risk. See [LICENSE](LICENSE). - -[Back to the list of contents](README.md#list-of-contents) 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 0138698..0000000 --- a/using-functions.md +++ /dev/null @@ -1,163 +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. - -We 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 we want to print a 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. - -```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. - -Usually we 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 x. -- 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). - -[Back to the list of contents](README.md#list-of-contents) diff --git a/what-next.md b/what-next.md index ffe1a86..8437d95 100644 --- a/what-next.md +++ b/what-next.md @@ -28,3 +28,16 @@ is a way to create generators | `from stuff import *` | imports everything ## Fun modules + +*** + +If you have trouble with this tutorial, please +[tell me about it](./contact-me.md) and I'll make this tutorial better, +or [ask for help online](./getting-help.md). +If you like this tutorial, please [give it a +star](./README.md#how-can-i-thank-you-for-writing-and-sharing-this-tutorial). + +You may use this tutorial freely at your own risk. See +[LICENSE](./LICENSE). + +[List of contents](./README.md#list-of-contents)