python_introduction/learning_python3.md

3457 lines
106 KiB
Markdown
Raw Normal View History

2021-10-25 09:24:01 +02:00
# What we'll learn
You'll learn **three** things at the same time so don't get discouraged if it feels a bit much at the start.
Everybody's issues will be in these three different domains and at the beginning it can be difficult to differentiate between them.
Keep this in mind, everybody has to go through this stage and the *click* comes at different times for different people but everybody clicks at some point!
The three new things you'll learn:
1. the **concepts** of programming, most notably Object Orientated Programming (OOP)
2021-11-12 17:32:47 +01:00
2. the **syntax** of one particular language, in our case python3
2021-10-25 09:24:01 +02:00
3. the **tools** needed to start programming in our language of choice
2021-11-12 17:32:47 +01:00
Within each of these topics there are *subtopics* but these are not bottomless!
Below is a small overview of how *I* would subdivide them.
2021-10-25 09:24:01 +02:00
## Concepts
The subtopics behind the concept of programming can be sliced (in no particular order) as follows:
* objects or **OOP** (Object Orientated Programming)
* variables (which are **not** *boxes* in python3)
2021-10-25 09:24:01 +02:00
* conditional logic
* functions
* loops
2021-11-12 17:32:47 +01:00
The concepts behind these topics are the same in most modern languages, it's just *how* you write them that is different.
2021-10-25 09:24:01 +02:00
This *how* is part of the **syntax** of the language.
## Syntax
> In computer science, the syntax of a computer language is the set of rules that defines the combinations of symbols that are considered to be correctly structured statements or expressions in that language.
> This applies both to programming languages, where the document represents source code, and to markup languages, where the document represents data.
2021-11-12 17:32:47 +01:00
> The syntax of a language defines its surface form.[1]
2021-10-25 09:24:01 +02:00
The quote above is taken shamelessly from [wikipedia](https://en.wikipedia.org/wiki/Syntax_(programming_languages)).
## Tools
### Writing code
2021-11-12 17:32:47 +01:00
Scripts are text files, plain and simple. So in order to **write** a python3 script all we need is a text editor.
2021-10-25 09:24:01 +02:00
Nano, vim, notepad++ all do a good job of editing plain text files but some make it *easier* than others.
One of the many features of an [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) is *syntax highlighting*.
It colors things such as [keywords](https://realpython.com/python-keywords/) which makes our life so much nicer when writing code.
We'll come back to these features in a bit.
### Running code
2021-11-12 17:32:47 +01:00
In order to **run** python3 code you need the python3 interpreter.
2021-10-25 09:24:01 +02:00
This is because when you **execute** your script, the interpreter will **read and execute** each line of the text file **line by line**.
2021-11-12 17:32:47 +01:00
Most people who want to write and run python3 code, or any language for that matter, will install an Integrated Development Environment to do so.
There are no *rules* as to what has to be included for a program to qualify as an IDE but in my opinion it should include:
2021-10-25 09:24:01 +02:00
* syntax highlighting
* autocomplete
* *goto* commands such as goto definition, goto declaration, goto references
* automatic *pair* opening and closing
* builtin help navigation
There is a plethora of IDE's available and you can't really make a *wrong* choice here, but to make the overall learning curve a bit less steep we'll start out with a user friendly IDE, [pycharm](https://www.jetbrains.com/help/pycharm/installation-guide.html).
# The python3 shell
TODO animated overview of the shell and the world of OOP
# Installing pycharm
TODO
# Your first project
In almost any language you'll find a *helloworld* program.
It serves to illustrate a *very* basic working script or program to showcase the syntax.
In python a helloworld is done as such.
```python3
print("Hello World!")
```
Just for reference below are a few helloworld programs in different languages.
2021-11-02 10:33:03 +01:00
First `c#` then `c` then `c++` and last but not least `javascript`.
### c#
```c#
Console.WriteLine("Hello World!");
```
2021-11-02 10:33:03 +01:00
### c
```c
#include <stdio.h>
int main() {
printf("Hello World!");
return 0;
}
```
2021-11-02 10:33:03 +01:00
### c++
2021-11-09 18:46:56 +01:00
```cpp
2021-11-02 10:33:03 +01:00
// Your First C++ Program
#include <iostream>
int main() {
std::cout << "Hello World!";
return 0;
}
```
### javascript
2021-11-09 18:46:56 +01:00
```js
alert( 'Hello, world!' );
```
2021-10-25 09:24:01 +02:00
2021-10-26 14:31:10 +02:00
## How to execute
* within pycharm
* from the command line
2021-10-25 09:24:01 +02:00
## Simple printing
The most basic printing can be done by calling the `print` function.
In python a call is symbolized by the `()`.
In practice this becomes as follows.
```python
print("hello world")
print("my name is Wouter")
print("I'm", 35, "years old")
```
`print` is a **built-in** function.
We can *prove* this in a shell, plus we can read it's [docstring](https://docs.python.org/3/glossary.html#term-docstring).
```python
>>> print
<built-in function print>
>>> print(print.__doc__)
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:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
>>>
```
2021-10-27 14:37:39 +02:00
🏃 Try it
---
Try printing different lines and with combinations of different object types such as `int`, `float` and `str`.
What happens if you *add* (`+`) values to one another?
2021-11-12 17:32:47 +01:00
What about subtracting?
And multiplication and division?
We can also print the objects referenced by variables.
A simple example:
```python3
name = "Wouter"
age = "35"
print("Hello, my name is", name, "and I'm", age, "years old.")
```
While it works perfectly fine it's not super *readable*.
We can improve the readability by using either string replacement or string formatting.
My personal preference is string formatting.
2021-10-27 14:37:39 +02:00
🏃 Try it
---
Have a look at both ways illustrated below and try them out.
2021-10-25 09:24:01 +02:00
## String replacement
```python3
name = "Wouter"
age = "35"
2021-10-27 14:37:39 +02:00
print(f"Hello, my name is {name} and I'm {age} years old.")
```
## String formatting
```python3
name = "Wouter"
age = "35"
print("Hello, my name is {} and I'm {} years old.".format(name, age))
```
## Some links to read up
* [realpython string formatting](https://realpython.com/python-string-formatting/)
2021-10-28 11:39:16 +02:00
* [realpython data types](https://realpython.com/python-data-types/)
2021-10-25 09:24:01 +02:00
# Taking input
2021-11-12 17:32:47 +01:00
The first **built-in function** we saw is `print` which can be used to signal messages to the user.
But how can we **get** some information from the user?
This is done with the built-in `input` function.
If we open up a python shell we can observe it's behaviour.
```python3
>>> input()
hello world
'hello world'
>>>
```
It seems to echo back what we type on the empty line.
If we take this idea and add it to a script the behaviour changes slightly.
The [prompt](https://en.wikipedia.org/wiki/Command-line_interface#Command_prompt) appears but when we hit `enter` the text is not printed.
This is one of the slight nuances between running scripts and using the shell.
The shell is more *verbose* and will explicitly tell you what a function returns, unless it doesn't return anything.
2021-10-25 09:24:01 +02:00
`input` is, like `print`, a **built-in** function.
We can *prove* this in a shell, plus we can read it's [docstring](https://docs.python.org/3/glossary.html#term-docstring) just as with `print`.
```python
>>> input
<built-in function input>
>>> print(input.__doc__)
Read a string from standard input. The trailing newline is stripped.
The prompt string, if given, is printed to standard output without a
trailing newline before reading input.
If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
On *nix systems, readline is used if available.
>>>
```
2021-10-27 16:39:11 +02:00
## Some functions are blocking
When we call `print` the function is executed immediately and the python interpreter continues with the next line.
The `input` function is slightly different.
It is called a **blocking** function.
When a blocking function is called the program is *waiting* for some action.
Once the condition is met, the program continues.
It's important to be aware of this but don't overthink it.
We'll get back to this behaviour later.
2021-10-25 09:24:01 +02:00
## Functions can return something
So, functions can **return** something but how can we *use* the returned objects?
This is where **variables** come in handy.
2021-10-27 14:37:39 +02:00
The `input` function will **always** return an object of type `str`.
If we want to use this object later in our code we need to add a *post-it* to it so we can reference it later.
Remember that the object is created by the function call, and we add the reference after the object's creation.
```python3
print("What is your name? ")
answer = input()
print("Well hello", answer, "!")
```
2021-10-27 14:37:39 +02:00
**When looking at the code block above did you notice the *empty space* I added after my question?
Can you tell me why I did that?**
🏃 Try it
---
Try playing around with the `input` function and incorporate the different ways to print with it.
2021-11-12 17:32:47 +01:00
Ask multiple questions and combine the answers to print a sensible message on one line.
Can you create a simple calculator?
If not, can you explain me *why*?
2021-10-27 14:37:39 +02:00
## Functions can take arguments
2021-10-27 14:37:39 +02:00
Some, if not most, functions will take one or more arguments when calling them.
This might sound complicated but you've already done this!
2021-11-12 17:32:47 +01:00
The `print` function takes *a-message-to-print* as an argument, or even multiple ones as you probably noticed when playing around with `+` and `,`.
2021-10-27 14:37:39 +02:00
The `input` function *can* take arguments but as we've seen does not *require* an argument.
When looking at the documentation we can discover **what** the function does, how to **call** the function and what it **returns**.
**CTRL-q opens the documentation in pycharm**
```
Help on built-in function input in module builtins:
input(prompt=None, /)
Read a string from standard input. The trailing newline is stripped.
The prompt string, if given, is printed to standard output without a
trailing newline before reading input.
If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
On *nix systems, readline is used if available.
```
We can add one **argument** inside the `input` call which serves as a prompt.
Now which `type` should the object we pass to `input` be?
The most logical type would be a `str` that represents the *question* to ask the user no?
Let's try it out.
```python3
2021-10-27 16:39:11 +02:00
answer = input("What is your name?")
print("Well hello {}!".format(answer))
2021-10-27 14:37:39 +02:00
```
2021-10-27 16:39:11 +02:00
🏃 Try it
---
Modify the questions you asked before so they have a proper prompt via the `input` function.
2021-11-12 17:32:47 +01:00
Ask multiple questions and combine the answers to print a message on one line.
Try it with different `print` formatting.
2021-10-27 16:39:11 +02:00
2021-10-25 09:24:01 +02:00
# Taking input and evaluation
2021-10-27 16:39:11 +02:00
We can do a lot more with the input from users than just print it back out.
It can be used to make logical choices based on the return **value**.
This is a lot easier than you think.
Imagine you ask me my age.
When I respond with *35* you'll think to yourself *"that's old!"*.
If I would be younger then *30* you would think I'm young.
2021-11-12 17:32:47 +01:00
We can implement this logic in python with an easy to read syntax.
2021-10-27 16:39:11 +02:00
First we'll take a python **shell** to experiment a bit.
```python3
>>> 35 > 30
True
>>> 25 > 30
False
>>> 30 > 30
False
>>> 30 >= 30
True
>>>
```
2021-10-25 09:24:01 +02:00
2021-10-27 16:39:11 +02:00
The `True` and `False` you see are also objects but of the type `bool`.
If you want to read up a bit more on boolean logic I can advise you [this](https://en.wikipedia.org/wiki/Boolean_algebra) page and also [this](https://realpython.com/lessons/boolean-logic/).
This boolean logic open the door towards **conditional logic**.
2021-10-26 14:31:10 +02:00
2021-10-25 09:24:01 +02:00
## Conditional logic
2021-10-27 16:39:11 +02:00
Let's convert the quote below to logical statements.
> If you are younger than 27 you are still young so if you're older than 27 you're considered old, but if you are 27 on the dot your life might be at [risk](https://en.wikipedia.org/wiki/27_Club)!
```python3
age = 35
if age < 27:
print("you are still very young, enjoy it!")
elif age > 27:
print("watch out for those hips oldie...")
else:
print("you are at a dangerous crossroad in life!")
```
2021-10-26 14:31:10 +02:00
2021-10-29 12:08:01 +02:00
**Do not fight the automatic indentation in your IDE! Pycharm is intelligent enough to know when to indent so if it does not indent by itself, you probably made a syntax error.**
2021-10-25 09:24:01 +02:00
## Class string methods
2021-10-27 16:39:11 +02:00
Let's take the logic above and implement it in a real program.
I would like the program to ask the user for his/her name and age.
After both questions are asked I would like the program to show a personalized message to the user.
The code below is functional but requires quite a bit of explaining!
```python3
name = input("What is you name?")
age = input("What is you age?")
if age.isdigit():
age = int(age)
else:
print("{} is not a valid age".format(age))
exit(1)
if age < 27:
print("My god {}, you are still very young, enjoy it!".format(name))
elif age > 27:
print("Wow {}! Watch out for those hips oldie...".format(name))
else:
print("{}, you are at a dangerous crossroad in life!".format(name.capitalize()))
```
An object of the type `str` has multiple **methods** that can be applied on itself.
It might not have been obvious but the *string formatting* via `"hello {}".format("world")` we did above is exactly this.
We **call** the `.format` **method** on a `str` object.
Strings have a handful of methods we can call.
2021-11-12 17:32:47 +01:00
Pycharm lists these methods when you type `.` after a string object.
2021-10-27 16:39:11 +02:00
In the **shell** we can visualize this as well.
For those of you not familiar with shells have a look at [tab completion](https://en.wikipedia.org/wiki/Command-line_completion).
```python3
>>> name = "wouter"
>>> name.
name.capitalize( name.format( name.isidentifier( name.ljust( name.rfind( name.startswith(
name.casefold( name.format_map( name.islower( name.lower( name.rindex( name.strip(
name.center( name.index( name.isnumeric( name.lstrip( name.rjust( name.swapcase(
name.count( name.isalnum( name.isprintable( name.maketrans( name.rpartition( name.title(
name.encode( name.isalpha( name.isspace( name.partition( name.rsplit( name.translate(
name.endswith( name.isascii( name.istitle( name.removeprefix( name.rstrip( name.upper(
name.expandtabs( name.isdecimal( name.isupper( name.removesuffix( name.split( name.zfill(
name.find( name.isdigit( name.join( name.replace( name.splitlines(
>>> name.capitalize()
'Wouter'
>>> name.isd
name.isdecimal( name.isdigit(
>>> name.isdigit()
False
>>> age = "35"
>>> age.isdigit()
True
>>>
```
**Remember CTRL-q opens the documentation in Pycharm and don't forget to actually use it!**
🏃 Try it
---
Can you code ma little calculator *now*?
What about one that takes floating point numbers?
2021-10-28 11:39:16 +02:00
## Some links to read up on
* [realpython](https://realpython.com/python-conditional-statements/) conditional logic
2021-10-25 09:24:01 +02:00
# Coding challenge - Celsius to Fahrenheit converter
2021-10-27 16:39:11 +02:00
Your first challenge!
2021-10-28 11:39:16 +02:00
I would like you to write a program that converts Celsius to Fahrenheit.
2021-11-02 10:33:03 +01:00
You should do this in a **new** python file.
I suggest you call it `c_to_f.py` or something that makes sense to you.
2021-10-27 16:39:11 +02:00
The result of this program *could* be as follows.
```bash
2021-10-28 11:39:16 +02:00
➜ ~ git:(master) ✗ python3 ex_celcius_to_fahrenheit.py
2021-10-27 16:39:11 +02:00
What's the temperature?30
30°C equals 86.0°F
Go turn off the heating!
2021-10-28 11:39:16 +02:00
➜ ~ git:(master) ✗ python3 ex_celcius_to_fahrenheit.py
2021-10-27 16:39:11 +02:00
What's the temperature?4
4°C equals 39.2°F
Brrrr, that's cold!
2021-10-28 11:39:16 +02:00
➜ ~ git:(master) ✗ python3 ex_celcius_to_fahrenheit.py
2021-10-27 16:39:11 +02:00
What's the temperature?blabla
I can't understand you...
➜ ~ git:(master) ✗
```
2021-10-28 11:39:16 +02:00
If you want to make the program a bit more complex, try adding the reverse as in Fahrenheit to Celsius.
Your first question to the user could then be *in which direction do you want to convert?*.
If you want to add an other feature, I suggest you try to make the converter work for `int` and for `float` objects.
2021-10-28 11:39:16 +02:00
2021-10-27 16:39:11 +02:00
<details>
<summary>Spoiler warning</summary>
```python3
result = input("What's the temperature?")
if result.isdigit():
celsius = int(result)
else:
print("I can't understand you...")
exit(1)
farenheit = celsius * (9/5) + 32
print("{}°C equals {}°F".format(celsius, farenheit))
if celsius < 15:
print("Brrrr, that's cold!")
else:
print("Go turn off the heating!")
```
</details>
2021-11-12 12:38:15 +01:00
# Coding challenge - Currency converter
I would like you to write a program that converts EUR to DOLLAR.
You should do this in a **new** python file.
I suggest you call it `euro_to_dollar.py` or something that makes sense to you.
The result of this program *could* be as follows.
```bash
➜ python_course_doc git:(master) ✗ python3 test.py
How much EUR would you like to convert into DOLLAR? 140
140 EUR is 159.6 DOLLAR
➜ python_course_doc git:(master) ✗ python3 test.py
How much EUR would you like to convert into DOLLAR? blablabla
That's not a number I understand...
➜ python_course_doc git:(master) ✗
```
2021-11-12 17:32:47 +01:00
2021-11-12 12:38:15 +01:00
<details>
<summary>Spoiler warning</summary>
2021-11-12 12:40:08 +01:00
```python
2021-11-12 12:38:15 +01:00
result = input("How much EUR would you like to convert into DOLLAR? ")
rate = 1.14
if result.isdigit():
eur = int(result)
dollar = eur * rate
print("{} EUR is {} DOLLAR".format(eur, dollar))
else:
print("That's not a number I understand...")
2021-11-12 12:40:08 +01:00
```
2021-11-12 12:38:15 +01:00
</details>
## Some links to read up on
2022-04-14 16:42:25 +02:00
* how to convert string to float, Look at the response here: [str.isfloat()](https://stackoverflow.com/questions/736043/checking-if-a-string-can-be-converted-to-float-in-python)?
2021-10-26 21:52:08 +02:00
# A text based adventure game
2021-10-28 11:35:48 +02:00
We can use conditional logic to create quite elaborate decision processes.
Let's build a mini text based adventure game.
2021-11-12 17:32:47 +01:00
Granted it's not a *triple A* game but it will train your `if` and `else` skills plus it will highlight some issues we'll overcome in the next section.
2021-10-28 11:35:48 +02:00
2021-10-28 11:16:13 +02:00
![adventure game](./assets/text_based_adventure_game.png)
2021-10-26 21:52:08 +02:00
2021-10-29 12:08:01 +02:00
Consider the diagram above we can imagine a program that functions nicely with the code below.
2021-11-12 17:32:47 +01:00
It is however not very *readable* nor *scalable*.
2021-10-29 12:08:01 +02:00
2021-10-28 11:35:48 +02:00
```python3
answer = input("You're at a cross section. Do you go left or right?")
if answer.startswith("l"):
answer = input("Down this hall you encounter a bear. Do you fight it?")
if answer.startswith("y"):
print("The bear counter attack! He kills you")
print("game over!")
exit(0)
elif answer.startswith("n"):
print("It's a friendly bear! He transforms into a wizard!")
answer = input("The wizard asks you if you know the meaning of life?")
if answer == "42":
print("He knods approuvingly and upgrades you to wizard status!")
print("You win!")
exit(0)
else:
print("He shakes his head in disbelief. You fool!")
print("game over!")
else:
print("that's not a valid choice...")
print("game over!")
exit(0)
elif answer.startswith("r"):
answer = input("Down this hall you find some mushrooms. Do you eat them?")
if answer.startswith("n"):
print("You starve to dead...")
print("game over!")
exit(0)
elif answer.startswith("y"):
print("A wizard apprears out of thin air!")
answer = input("The wizard asks you if you know the meaning of life?")
if answer == "42":
print("He knods approuvingly and upgrades you to wizard status!")
print("You win!")
exit(0)
else:
print("He shakes his head in disbelief. You fool!")
print("game over!")
else:
print("that's not a valid choice...")
print("game over!")
exit(0)
pass
else:
print("game over!")
exit(0)
```
2021-10-26 21:52:08 +02:00
2021-10-29 12:08:01 +02:00
I urge you to read up on some [best practices](https://www.tutorialdocs.com/article/python-conditional-statements-tips.html) for `if` statements.
We will not improve on this particular example but I do advise you to create a similar style game during one of the workshops once we have learned some new tricks.
If you need some inspiration you could have a look [here](https://github.com/vamshi-krishna-prime/python-text-game).
2021-10-29 12:08:01 +02:00
2021-10-25 09:24:01 +02:00
# Creating your own functions
2021-10-29 12:08:01 +02:00
One of the *issues* we have in the text based game example is duplicate code.
At two spots we execute almost identical code and this is something that should be avoided at all costs!
Why write the same thing over and over?
2021-11-12 17:32:47 +01:00
Each time you write it again you introduce a new window for errors!
2021-10-29 12:08:01 +02:00
You're better off writing it **once** and use it **lots**.
This is where **functions** come into play.
Python ships with a lot of built-in functions but we can create our own very easily.
2021-11-12 17:32:47 +01:00
The [keyword](https://docs.python.org/3/reference/compound_stmts.html#function-definitions) to define a function in python is `def`.
2021-10-29 12:08:01 +02:00
All the code that is *indented* will be executed when we **call** the function.
2021-11-12 17:32:47 +01:00
Here is a basic abstraction with a correct syntax.
2021-10-29 12:08:01 +02:00
```python3
def first_function():
print("I'm a function")
print("Hear me roar!")
```
**Do not fight the indentation, it's part of the syntax of python!**
2021-10-29 12:08:01 +02:00
If you type *only* the code above in a new script, and run it, you won't see much.
This is because you only **created** the function.
To use it you need to **call** it.
This is done as follows.
```python3
def first_function():
print("I'm a function")
print("Hear me roar!")
first_function()
```
Learning how to create functions is a big step in your programming journey.
It can seem confusing at first because the code execution *appears* to jump around.
This is however **not** the case.
Your script is still read and executed **line by line** so you can not call a function **before** you defined it!
For now you should not overthink the structure of you scripts.
As long as they work you should be happy.
We'll dive into the proper *anatomy* of a program real soon.
**Remember you can't use functions before defining them! Learn to read the error messages. Also, if it does not autocomplete, it *probably* won't work...**
2021-10-26 14:31:10 +02:00
## Functions that *do* something
2021-10-29 12:08:01 +02:00
The first function I showed you above performs a series of actions each time it is called.
We can use it to *bake cakes* for example.
Below we create **one** function to bake the cake, and call it **three** times to bake three cakes.
```python3
def bake_chocolate_cake():
print("mix the base ingredients")
print("add the chocolate flavour")
print("put in the oven")
print("enjoy!")
bake_chocolate_cake()
bake_chocolate_cake()
bake_chocolate_cake()
```
Now, you might like a *vanilla* cake from time to time.
Easy, we'll just write a second function for that purpose.
```python3
def bake_chocolate_cake():
print("mix the base ingredients")
print("add the chocolate flavour")
print("put in the oven")
print("enjoy!")
def bake_vanilla_cake():
print("mix the base ingredients")
print("add the vanilla flavour")
print("put in the oven")
print("enjoy!")
bake_chocolate_cake()
bake_chocolate_cake()
bake_vanilla_cake()
bake_chocolate_cake()
bake_vanilla_cake()
```
Voila, we can now make as many chocolate and vanilla cakes as we want!
But what about *bananas*?
Following our logic we can create a *third* function to bake a banana cake but you're probably seeing a *pattern* here.
2021-11-12 17:32:47 +01:00
Each `bake_FLAVOR_cake` function is almost identical, just the flavor changes.
We can create one generic `bake_cake` function and add the custom `flavor` each time we actually *bake* a cake.
This can be done with arguments.
2021-10-29 12:08:01 +02:00
```python3
def bake_cake(flavor):
print("mix the base ingredients")
print("add the {} flavor".format(flavor))
print("put in the oven")
print("enjoy!")
bake_cake("chocolate")
bake_cake("vanilla")
bake_cake("banana")
```
2021-10-25 09:24:01 +02:00
2021-10-26 14:31:10 +02:00
## Variable scope
2021-10-25 09:24:01 +02:00
2021-10-29 14:01:34 +02:00
Variable scope might sounds complicated but with some examples you'll understand it in no time.
Remember variables are like unique *post-its*?
Well, variable scope is like having multiple colors for your post-its.
A yellow post-it with `name` on it is not the same as a red one with `name` on it so they can both reference **different** objects.
Scope is **where** the colors change, and this is done automatically for you in python.
It's an inherent feature of the language but the concept of scope is not unique to python, you'll find it in most modern languages.
Now, some examples.
```python3
total = 9000
def function_scope(argument_one, argument_two):
print("inside the function", argument_one, argument_two)
total = argument_one + argument_two
print("inside the function", total)
function_scope(300, 400)
print("outside the function", total)
```
2022-04-14 16:42:25 +02:00
Here, there is a variable `total` outside the function references.But, it is a different object from the `total` inside the function.
2021-10-29 14:01:34 +02:00
Python is very *nice* and will try to fix some common mistakes or oversights by itself.
For example.
```python3
name = "Wouter"
def function_scope():
print("inside the function", name)
function_scope()
print("outside the function", name)
```
But we can not **modify** the referenced object from **inside** the function.
This will give an `UnboundLocalError: local variable 'name' referenced before assignment` error
```python3
name = "Wouter"
def function_scope():
2021-10-29 14:37:52 +02:00
print("inside the function", name)
2021-10-29 14:01:34 +02:00
name = "Alice"
function_scope()
print("outside the function", name)
```
There is however a handy **keyword** we can use to explicitly reference variables from the outermost scope.
This is done as follows with the `global` keyword.
```python3
name = "Wouter"
def function_scope():
global name
print("inside the function", name)
name = "Alice"
function_scope()
print("outside the function", name)
```
2021-10-25 09:24:01 +02:00
## Functions that *return* something
2021-10-29 14:37:52 +02:00
While the `global` keyword can be useful, it's rarely used.
This is because function can not only *do* thing, they can **return** objects.
The `input` function we've been using is a prime example of this as it always give you a `str` object back with the content of the user input.
We can copy this behaviour as follows.
```python3
def square_surface(length, width):
surface = length * width
return surface
square = square_surface(20, 40)
print(square)
```
🏃 Try it
---
Functions that return an object are essential to any modern programming language.
Think of some calculations such as the Celsius to Fahrenheit converter and create the corresponding functions.
2021-10-26 14:31:10 +02:00
2021-10-29 12:08:01 +02:00
## Some links to read up on
* some [best practices](https://able.bio/rhett/python-functions-and-best-practices--78aclaa) for functions
* more details on [variable scope](https://pythongeeks.org/python-variable-scope/)
2021-10-29 14:37:52 +02:00
* in depth [article](https://realpython.com/python-return-statement/) on return values in python
2021-10-29 12:08:01 +02:00
# Coding challenge - Pretty Print
Can you write me a function that decorates a name or message with a *pretty* character.
2021-11-02 10:33:03 +01:00
You should do this in a **new** python file.
I suggest you call it `pretty_frame.py` or something that makes sense to you.
2021-10-29 12:08:01 +02:00
Kind of like the two examples below.
```
##########
# Wouter #
##########
#################
# Python rules! #
#################
```
2021-10-29 14:37:52 +02:00
As an extra challenge you could return a [multi line string](https://www.askpython.com/python/string/python-multiline-strings) and print it outside of the function!
2021-10-29 12:08:01 +02:00
<details>
<summary>Spoiler warning</summary>
```python3
def pretty_print(msg, decorator="#"):
line_len = len(msg) + (len(decorator) * 2) + 2
print(decorator * line_len)
print("{} {} {}".format(decorator, msg, decorator))
print(decorator * line_len)
pretty_print("Wouter")
pretty_print("Python rules!")
pretty_print("Alice", "-")
```
</details>
2021-10-26 14:31:10 +02:00
# Using the standard library
2021-10-29 15:34:58 +02:00
There is no need to reinvent the wheel each time you build a bike.
The same goes for programming.
Most, if not all, programming languages come with a **standard library** which is a collection of additional objects and functions to facilitate common problems.
We'll look at some essential ones together but I urge you to [read](https://docs.python.org/3/library/index.html) up a bit when you have some free time.
Over the course of you programming journey you'll discover that efficient programming is often finding the right libraries and chaining them together to suit your use case.
Imagine we want to include a dice in our text based adventure game.
How on earth do we program that?
2021-11-12 17:32:47 +01:00
We need some form of *randomness* in our code.
2021-10-29 15:34:58 +02:00
A [quick google](https://stackoverflow.com/questions/28705965/python-random-function-without-using-random-module) demonstrates this is quite difficult without the use of libraries.
As randomness is both [extensively](https://www.random.org/randomness/) used in programming **and** it's quite [difficult](https://medium.com/swlh/random-functions-a4f36b1dfd8f) to do properly, you'll find a `random` library in most languages.
The new **keyword** you'll learn here is `import`.
2021-11-12 17:32:47 +01:00
It allows you to *include* extra functionality to your program.
(Go look back at the `c` and `c++` helloworld code, what do you think the `#include` does?)
2021-10-29 15:34:58 +02:00
Once *imported* we can **call** functions that exist from the library in question.
So, our dice becomes as follows.
```python3
import random
throw = random.randint(1, 6)
print(throw)
```
**Autocomplete is your friend. You can use it to browse around a library and discover interesting functions or classes you can make use of.**
A second widely used library is `datetime`.
It facilitates handling dates, hours, calendars, time differences, etc.
A simple program we can write to illustrate it's purpose is a *will-it-be-Sunday* program.
You give date and the program tells you if it's a Sunday or not.
```python3
import datetime
sunday = 6
date = input("which date should I look up? (example: 2022 3 23) ")
year, month, day = date.split()
date = datetime.date(int(year), int(month), int(day))
if date.weekday() == sunday:
print("yes! you can sleep in...")
else:
print("better set your alarm")
```
The program above incorporates a lot of different concepts.
Read it very slowly and think about what each step is doing.
Also think about how you can *break* this program!
**Why on earth is Sunday six? Read the [doc](https://docs.python.org/3/library/datetime.html#datetime.date.weekday)!**
2021-10-26 14:31:10 +02:00
# Coding challenge - Memento Mori calculator
2021-10-29 15:54:16 +02:00
[Memento Mori](https://en.wikipedia.org/wiki/Memento_mori) is a bit of a grim concept and if it freaks you out, maybe adjust the exercise to calculate the days until your next birthday.
That being said, we all die and we all live in a country with a life expectancy.
The [Belgian](https://www.healthybelgium.be/en/health-status/life-expectancy-and-quality-of-life/life-expectancy) life expectancy for a man is about 80 years so if you know your birthday, which you should, you can calculate your theoretical death day.
Plus, you can calculate the percentage of your life that remains.
2021-11-02 10:33:03 +01:00
You should do this in a **new** python file.
I suggest you call it `memento_mori.py` or something that makes sense to you.
2021-10-29 15:54:16 +02:00
<details>
<summary>Spoiler warning</summary>
```python3
import datetime
life_expectancy_years = 80.8
life_expectancy_days = life_expectancy_years * 365
delta = datetime.timedelta(days=life_expectancy_days)
date = input("what is your birthday? (example: 1986 10 7) ")
year, month, day = date.split()
birthday = datetime.date(int(year), int(month), int(day))
memento_mori = birthday + delta
days_to_live = memento_mori - datetime.datetime.now().date()
percentage = (days_to_live.days / life_expectancy_days) * 100
print("your theoretical death day is:", memento_mori)
2021-11-02 10:33:03 +01:00
print("that's", days_to_live.days, "days left to live")
2021-10-29 15:54:16 +02:00
print("which means you have {:.2f}% left to live...".format(percentage))
```
</details>
There are quite a few quirky tricks in the code above.
Take your time to ask me about them, or use online documentation and keep some notes.
2021-10-25 09:24:01 +02:00
# Writing your first library
2021-11-02 10:33:03 +01:00
Up until now have coded our own functions and usage of these functions.
We've seen there is no need to always write everything from scratch as we can *reuse* existing code by importing libraries and calling it's functions.
But what are those libraries actually?
2021-10-25 09:24:01 +02:00
2021-11-02 10:33:03 +01:00
Consider the following mini script.
It imports `datetime` and calls the `now` function to print the date and time.
A pretty basic clock.
```python3
import datetime
timestamp = datetime.datetime.now()
print("It is: {}".format(timestamp))
```
When you put your cursor on the `now` part of the function call, you can press **CTRL-b** to go to the **declaration** of said function.
You can also right click on the `now` part and see all possible *actions* you can perform on this function.
This opens up a **second tab** in your editor window with a `datetime.py` file in it.
In this file your cursor should have jumped to the following code.
```python3
@classmethod
def now(cls, tz=None):
"Construct a datetime from time.time() and optional time zone info."
t = _time.time()
return cls.fromtimestamp(t, tz)
```
What does this look like?
It looks like python code no?
Well, it is!
Libraries are often *just* other python files we load into our program.
So if they are just python files we *should* be able to write our own libraries no?
🏃 Try it
---
Import some libraries and go peak at some function declarations.
A big part of code writing is *navigating* a codebase written by other people.
Properly understanding how to navigate is essential!
2021-10-25 09:24:01 +02:00
## How do we write libraries?
2021-11-02 10:33:03 +01:00
Let's go back to our `pretty_print` function.
I have code along these lines.
```python3
def pretty_print(msg, decorator="#"):
line_len = len(msg) + (len(decorator) * 2) + 2
print(decorator * line_len)
print("{} {} {}".format(decorator, msg, decorator))
print(decorator * line_len)
2021-11-10 14:57:09 +01:00
2021-11-02 10:33:03 +01:00
pretty_print("Wouter")
pretty_print("Python rules!")
pretty_print("Alice", "-")
```
I'll **create a new python file** and name it `helper_functions.py`.
In this file I rewrite (don't copy paste, you need the practice) my function.
As I'm rewriting my function I'll also need some test calls to my function.
Done!
Now in a **second new python file** I'll name `my_program.py` I'll `import` the `helper_fucntions.py` file.
Note the syntax drops the `.py` extension!
```python3
import helper_functions
print("I'm a program")
helper_functions.pretty_print("hello world!")
```
If we now **run** the `my_program.py` file we get the following output.
```
##########
# Wouter #
##########
#################
# Python rules! #
#################
---------
- Alice -
---------
I'm a program
################
# hello world! #
################
```
OK, it *kind* of works, the function get's called from within the `my_program.py` file but the test calls from the `helper_functions.py` file are messing up my execution.
Luckily there is a way to tell python to only run certain code when a file should be seen as program and not a library.
Sounds complicated but it's logically very simple.
2021-10-25 09:24:01 +02:00
## What is `__name__ == "__main__"`?
2021-11-02 10:33:03 +01:00
Remember on the first day when I showed you how python is self documenting via `print.__doc__`?
Well, there are [more](https://stackoverflow.com/questions/20340815/built-in-magic-variable-names-attributes) *magical attributes* than just the `__doc__` one!
There is one called `__name__` which is used for the exact purpose we're trying to achieve.
Go back to the `helper_functions.py` file and comment out the test calls and add the line that print's the `__name__` variable.
```python3
def pretty_print(msg, decorator="#"):
line_len = len(msg) + (len(decorator) * 2) + 2
print(decorator * line_len)
print("{} {} {}".format(decorator, msg, decorator))
print(decorator * line_len)
print("I'm a library")
print("my name is: {}".format(__name__))
# pretty_print("Wouter")
# pretty_print("Python rules!")
# pretty_print("Alice", "-")
```
Do the same in the `my_program.py` file so it look similar to the code below.
```python3
import helper_functions
print("I'm a program")
print("my name is: {}".format(__name__))
helper_functions.pretty_print("hello world!")
```
If you now run the `helper_functions.py` file you should get output similar to this.
```
I'm a library
my name is: __main__
```
But when you run the `my_program.py` you should get something like this.
```
I'm a library
my name is: helper_functions
I'm a program
my name is: __main__
################
# hello world! #
################
```
This shows that the `__name__` variable changes according to how a script is called and this behavior can be used to our advantage!
We can *evaluate* the value behind `__name__` and change execution accordingly.
Evaluating and changing execution screams **conditional logic** no?
So in the library we add the following.
```python3
def pretty_print(msg, decorator="#"):
line_len = len(msg) + (len(decorator) * 2) + 2
print(decorator * line_len)
print("{} {} {}".format(decorator, msg, decorator))
print(decorator * line_len)
if __name__ == "__main__":
pretty_print("Wouter")
pretty_print("Python rules!")
pretty_print("Alice", "-")
```
Only when the library is executed as a program, for example when we're testing out out functions, the condition is met to allow execution of our calls.
Problem sorted!
Executing our main program now gives the expected output.
```
I'm a program
my name is: __main__
################
# hello world! #
################
```
2021-10-26 14:31:10 +02:00
## Anatomy of a program
2021-11-02 10:33:03 +01:00
While it's still very early in your coding career I *really* want to insist on good practices from the start as it will help you make sense of it all.
A well written script or program is divided into **three** sections.
1. We collect external tools from libraries, either from the standard library or of our own making.
2. We write the specific tools we need for our program to run.
3. We call a combination of **external** and **specific** tools which constitutes our program logic.
A mock-up program that follows these rules could look like this.
Notice how it's easy to read?
2021-11-12 17:32:47 +01:00
The `pass` is a new [keyword](https://docs.python.org/3/reference/simple_stmts.html#the-pass-statement) that does nothing but is often used to declare functions a placeholders.
2021-11-02 10:33:03 +01:00
```python3
import random
import datetime
def first_function():
pass
def second_function():
pass
def third_function():
pass
def fourth_function():
pass
if __name__ == "__main__":
print("today it's: {}".format(datetime.datetime.now()))
choice = random.randint(0, 100)
if choice < 50:
first_function()
elif choice > 90:
second_function()
elif choice == 55:
third_function()
else:
fourth_function()
```
🏃 Try it
---
Go online and look for some scripts and programs that interest you.
See if you can assess whether the code follows the pattern I outlined.
There are a couple of things you should definitely read up on.
* The [zen](https://www.python.org/dev/peps/pep-0020/) of python.
* What is [pythonic](https://stackoverflow.com/questions/25011078/what-does-pythonic-mean)?
* Coding [style](https://docs.python-guide.org/writing/style/)
2021-10-26 14:31:10 +02:00
# While loop
2021-11-03 11:16:24 +01:00
We started our python journey with fully linear code.
Next we saw functions which are first **defined** and called afterwards.
Now we'll have a look at **loops**.
In python there are **two** types of loops, a **while** and a **for** loop.
We'll start with the while loop which I see as a loop in *time*.
The for loop is a loop in *space* but we'll get to that one later.
The concept of a while loop is pretty simple.
Code **within** the loop will be executed as long as a **condition** is met.
Consider the code below.
```python3
import time
counter = 0
print("before the loop, counter: {}".format(counter))
while counter <= 10:
print("inside the loop, counter: {}".format(counter))
counter += 1
time.sleep(1)
print("after the loop, counter: {}".format(counter))
```
Two *extra* things might look new to you here.
First the `import time` and `time.sleep(1)`, can you tell me what it does?
Next the `counter += 1` which is called [incrementing](https://stackoverflow.com/questions/1485841/behaviour-of-increment-and-decrement-operators-in-python).
You'll find this feature in most languages.
You can think of it's syntax as *counter equals itself plus 1*.
The *1* can be any number you want though!
When learning the `while` [keyword](https://docs.python.org/3/reference/compound_stmts.html#the-while-statement) there is a *second* keyword you should learn.
It comes in very handy when constructing [infinite loops](https://en.wikipedia.org/wiki/Infinite_loop).
Consider the following code.
```python3
import time
counter = 0
print("before the loop, counter: {}".format(counter))
while True:
print("inside the loop, counter: {}".format(counter))
counter += 1
time.sleep(1)
print("after the loop, counter: {}".format(counter))
```
The `while True` condition is *always* `True` so the loop will **never** exit!
This is what we call an **infinite loop**.
2021-11-03 11:16:24 +01:00
The `break` keyword was added to the language so we can *break out* of a loop.
The logic is as follows.
```python3
import time
counter = 0
print("before the loop, counter: {}".format(counter))
while True:
print("inside the loop, counter: {}".format(counter))
counter += 1
if counter >= 10:
print("I'll break now!")
break
time.sleep(1)
print("after the loop, counter: {}".format(counter))
```
Infinite loops are a cornerstone of modern programming.
While they might look scary, don't overthink it, you'll get used to them very quickly.
**When testing out an infinite loop it's sometimes handy to insert a `time.sleep` in it to slow down the execution a bit so you can wrap your head around what's happening.**
🏃 Try it
---
2021-11-12 17:32:47 +01:00
Go back to the Celsius to Farenheit converter and add a while loop to ensure the user puts in only numbers.
2021-11-03 11:16:24 +01:00
# Coding challenge - Guess the number
Now that you know how to repeat code execution we can create our first game!
Everybody knows the *guess the number* game.
The computer chooses a *random* number and the user has to *guess* which number it is.
At each try the computer will till you if the user's number is bigger or smaller *than* the one the computer has in mind.
The flow of the game could be as follows.
```
I have a number in mind...
What's your guess? 50
my number is bigger
What's your guess? 80
my number is smaller
What's your guess? blabla
that's not a number! try again...
What's your guess? 76
yes, that's right! you win!
bye bye...
```
<details>
<summary>Spoiler warning</summary>
```python3
import random
def ask_for_number():
result = input("What's your guess? ")
if result.isdigit():
number = int(result)
return number
else:
return None
if __name__ == "__main__":
number_to_guess = random.randint(0, 100)
print("I have a number in mind...")
while True:
user_number = ask_for_number()
if user_number is None:
print("that's not a number! try again...")
continue
elif number_to_guess == user_number:
print("yes, that's right! you win!")
break
elif number_to_guess > user_number:
print("my number is bigger")
elif number_to_guess < user_number:
print("my number is smaller")
print("bye bye...")
```
</details>
🏃 Try it
---
My *solution* is very basic.
Think of some ways to improve on it.
Can you limit the number of tries?
Can you add a feature to let the user play a *second* game after he/she wins or loses?
Coming up with challenges is on of the most *challenging* aspect op learning how to program.
2021-11-03 15:43:18 +01:00
Your thought process will send you of into unknown territory and will force you to expand you knowledge.
We'll get back to this thought process later, but if you feel like an extra challenge go for it!
2021-10-26 14:31:10 +02:00
2022-04-07 11:42:14 +02:00
Below you can see the same game but broken down into functions.
<details>
<summary>Spoiler warning</summary>
```python3
import random
def ask_for_number():
while True:
human_number = input("what is your guess? ")
if human_number.isdigit():
human_number = int(human_number)
break
else:
print("that is not a number! please try again...")
return human_number
def are_the_numbers_equal(computer_number, human_number):
if human_number < computer_number:
print("my number is bigger")
return False
elif human_number > computer_number:
print("my number is smaller")
return False
elif human_number == computer_number:
print("yes! {} is the number I had in mind".format(computer_number))
return True
def play_a_game():
computer_number = random.randint(0, 100)
print("cheat mode: {}".format(computer_number))
print("I have a number in mind...")
while True:
human_number = ask_for_number()
status = are_the_numbers_equal(computer_number, human_number)
if status == True:
break
def main():
while True:
play_a_game()
play_again = input("do you want to play a new game? (Y/N)")
if play_again.startswith("N"):
print("bye bye!")
break
if __name__ == "__main__":
main()
```
</details>
2022-04-07 11:43:19 +02:00
2022-04-14 16:42:25 +02:00
#Logical Operators
There is three types of logical operators. All operators returns boolean.
| Operator | Result |
|-----------------|----------------------------------------------|
| **And** | It send 'True' if all conditions are true |
| **Or** | It send 'True' if one of conditions are true |
| **Not** | It reverse the boolean result |
Let's start example with 'And' operator!
```python3
CustomerName = "Jean"
CustomerAgreement = True
DealerName = "Paul"
DealerAgreement = True
if CustomerAgreement and DealerAgreement :
print(f"Youpi !!! {CustomerName} and {DealerName} are agreed ")
else:
print(f"Oh no {CustomerName} and {DealerName} are disagreed ;( ")
```
As you can guess, Jean and Paul are agreeing to the deal. If I had put 'False' in DealerAgreement, the result will be inverse.
2021-10-26 14:31:10 +02:00
# Lists
2021-11-03 15:43:18 +01:00
The different built-in objects we've seen until now, such as `str` and `int` are simple [text](https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str) and [numeric](https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex) types.
There are other classes of objects that server different purposes.
One of these *groups* is called [sequence types](https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range).
A list in python is pretty much exactly what you think it is.
It is an *object* that groups together *other* objects.
Sounds complicated?
Have a look at the following.
```python3
my_numbers = [1, 2, 44, 60, 70]
print(my_numbers)
```
Easy right?
Compared to [other languages](https://www.cplusplus.com/reference/list/list/) lists in python are *very* flexible.
They can contain objects of different types, and their length can be changed at any time.
Programmers coming from other languages often find this flexibility of python a bug but you should see it as a feature.
```python3
favorite_number = 7
name = "wouter"
date = [1986, 10, 7]
values = [1, date, favorite_number, "hello world", name]
print(values)
```
The code above is just an illustration of the flexibility of lists.
2021-10-26 14:31:10 +02:00
## Creating lists
2021-11-03 15:43:18 +01:00
Creating lists can be done in two ways, either by using the **square brackets** `[]` or by calling `list`.
When calling `list` it takes **one argument**, which python will iterate over.
For example:
```python3
>>> first_list = ["hello", "world", "!"]
>>> first_list
['hello', 'world', '!']
>>> second_list = list("hello world !")
>>> second_list
['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', ' ', '!']
>>>
```
## List methods
As a `list` is a different type of object, it has different methods you can invoke on it.
When using tab complete in the python shell we get the following.
```python3
>>> second_list.
second_list.append( second_list.count( second_list.insert( second_list.reverse(
second_list.clear( second_list.extend( second_list.pop( second_list.sort(
second_list.copy( second_list.index( second_list.remove(
>>> second_list.
```
One of the most used methods is `append`.
It is used to add an element to the end of the list.
The second most used method is the `pop` one.
Read the shell code below and you'll understand immediately what they do.
```python3
>>> first_list
['hello', 'world', '!']
>>> first_list.append("coucou")
>>> first_list
['hello', 'world', '!', 'coucou']
>>> first_list.pop()
'coucou'
>>> first_list
['hello', 'world', '!']
>>>
```
🏃 Try it
---
Look at all the methods you can invoke on a list and try them out.
Remember to read the documentation!
## Picking elements and slicing lists
We can pick elements from the list of slice the list as we please.
A code block speaks more than words.
```python3
>>> long_list
['I', 'am', 'a', 'very', 'long', 'list', 'of', 'words', 'that', 'make', 'little', 'actual', 'sense']
>>> long_list[7]
'words'
>>> long_list[7:9]
['words', 'that']
>>> long_list[7:]
['words', 'that', 'make', 'little', 'actual', 'sense']
>>> long_list[:7]
['I', 'am', 'a', 'very', 'long', 'list', 'of']
>>> long_list[-1]
'sense'
>>> long_list[0]
'I'
>>>
```
**In programming we start counting at 0 because 0 and nothing are not the same thing.**
🏃 Try it
---
2021-10-26 14:31:10 +02:00
2021-11-03 15:43:18 +01:00
Slice and dice away!
A handy method of the `str` class is `split` which will cut up a string into separate elements.
The result will be a `list` on which you can use `list` methods.
2021-10-26 14:31:10 +02:00
# For loop
2021-11-03 15:43:18 +01:00
I mentioned the for loop, which is a loop in space, when we saw the `while` loop.
The [keyword](https://docs.python.org/3/reference/compound_stmts.html#the-for-statement) in question is, surprise surprise, `for`!
I see it as a loop in space because it will run *for each element in a sequence* which in my mind is something of substance.
Your logical mileage may vary but as long as you understand the following code block we're good.
```python3
import time
friends = ["max", "mike", "alice", "steve", "rosa", "hans"]
print("The door opens and in walk my {} friends!".format(len(friends)))
for friend in friends:
print("Hello {}!".format(friend.capitalize()))
time.sleep(1)
print("{} closes the door behind him...".format(friend.capitalize()))
```
2021-10-26 14:31:10 +02:00
2021-11-15 16:25:17 +01:00
**TODO pizza function with multiple arguments**
2021-10-26 14:31:10 +02:00
# Coding challenge - Cheerleader chant
2021-11-03 15:43:18 +01:00
Can you make me a program that outputs this type of cheerleader chant?
You can make it *prettier* by importing your `pretty_print` function, plus you can add some `time.sleep` in it to make it more *musical*.
```
Give me an m
M
Give me an a
A
Give me an x
X
Gooooooooo, MAX!
Give me an m
M
Give me an i
I
Give me an k
K
Give me an e
E
Gooooooooo, MIKE!
Give me an c
C
Give me an a
A
Give me an m
M
Give me an i
I
Give me an l
L
Give me an l
L
Give me an e
E
Gooooooooo, CAMILLE!
```
<details>
<summary>Spoiler warning</summary>
```python3
friends = ["max", "mike", "camille"]
for friend in friends:
for letter in friend:
print("Give me an {}".format(letter))
print("{}".format(letter.upper()))
print("Gooooooooo, {}!".format(friend.upper()))
```
</details>
# Coding challenge - ROT13
ROT13 is one of the oldest cryptographic cyphers know to mankind.
It dates back to the Roman empire and is also known as a [Caesar cypher](https://en.wikipedia.org/wiki/Caesar_cipher).
The algorithm is pretty simple, you just shift a letter 13 places in the alphabet so `a` becomes `n` or `x` becomes `k`.
Have a look at [this](https://rot13.com/) website to see the cypher in action.
Now, can you make a program that encrypts a phrase with ROT13?
Something along these lines:
```python3
What's your secret? hello world!
encoded secret: uryyb jbeyq!
```
<details>
<summary>Spoiler warning</summary>
```python3
import string
def encode_rot(msg, rot=13):
msg = msg.lower()
letters = list(string.ascii_lowercase)
coded_msg = []
for letter in msg:
if letter not in letters:
coded_msg.append(letter)
else:
idx = letters.index(letter) + rot
coded_letter = letters[idx % len(letters)]
coded_msg.append(coded_letter)
coded_msg = "".join(coded_msg)
return coded_msg
def decode_rot(msg, rot=13):
pass
if __name__ == "__main__":
clear_message = input("What's your secret? ")
encoded_message = encode_rot(clear_message)
print("encoded secret: {}".format(encoded_message))
```
</details>
🏃 Try it
---
To make things more interesting you can add a decode function.
Plus you could add a prompt that asks how big the shift should be (ROT13, ROT16, ...).
2021-11-12 17:32:47 +01:00
You can also make the cypher a **lot** harder to break if you use a word (or phrase as a key).
2021-11-12 11:25:13 +01:00
For example, if the key is `abc` and the message is `hello world` the first letter will be offset by a ROT0 (will remain `h`).
The second letter, `e` will be offset by a ROT1 so will become `f`.
The third letter by ROT2 so will become `n`.
Now, the fourth letter will be offset again by `a` so ROT0 and will become `l`.
And so on...
2021-11-03 15:43:18 +01:00
2022-04-11 22:22:31 +02:00
<details>
<summary>Spoiler warning</summary>
```python
import string
def encrypt_message(msg, rot=13):
encoded_message = []
for letter in msg:
if letter in alphabet:
letter_index = alphabet.index(letter)
encoded_letter_index = (letter_index + rot) % 26
encoded_letter = alphabet[encoded_letter_index]
encoded_message.append(encoded_letter)
else:
encoded_message.append(letter)
encoded_message = "".join(encoded_message)
return encoded_message
def encrypt_with_key(msg, key):
encrypted_message = []
counter = 0
for letter in msg:
key_letter = key[counter % len(key)]
key_index = alphabet.index(key_letter)
encrypted_letter = encrypt_message(letter, key_index)
counter += 1
encrypted_message.append(encrypted_letter)
return "".join(encrypted_message)
if __name__ == "__main__":
alphabet = list(string.ascii_lowercase)
key = "mehdi"
print("hello, I'm an encryption program!")
result = input("what is your secret? ")
secret = encrypt_with_key(result, key)
print("your secret message is: {}".format(secret))
```
</details>
# Coding challenge - Christmas Tree
Can you make me a program that draws various size Christmas trees?
Like the code block below.
```
#
###
#####
#######
#########
###########
#############
###############
#################
###
###
#
###
#####
#######
###
###
```
Can you add in some balls as below?
```
#
##°
#°###
#°###°#
###°#####
°##°##°#°##
#°###°°°#####
#°############°
#####°###°#°####°
###
###
```
What about this *super funky* gif...
![tree](./assets/tree.gif)
2021-11-15 14:59:11 +01:00
<details>
<summary>Spoiler warning</summary>
**Nice try... You should be able to do this on your own!**
</details>
2021-11-03 15:43:18 +01:00
# List comprehension
This is a bit of an advanced topic but I'm putting it here to show you a very unique and powerful feature of python.
2021-11-03 16:07:10 +01:00
It's an *in-line* combination of `list`, `for` and conditional logic which allows us to make lists of lists which obey certain conditions.
You can [learn](https://12ft.io/proxy?q=https%3A%2F%2Frealpython.com%2Flist-comprehension-python%2F) about it online.
```python3
mixed_list = ["one", "2", "three", "4", "5", "six6", "se7en", "8"]
digits = [d for d in mixed_list if d.isdigit()]
print(digits)
```
The code above can be recreated **without** list comprehension as well, it will just be *a lot* longer.
```python3
mixed_list = ["one", "2", "three", "4", "5", "six6", "se7en", "8"]
digits = []
for d in mixed_list:
if d.isdigit():
digits.append(d)
print(digits)
```
As the `d` in the list comprehension is **always** a digit, we can convert it to an integer on the spot!
```python3
mixed_list = ["one", "2", "three", "4", "5", "six6", "se7en", "8"]
digits = [int(d) for d in mixed_list if d.isdigit()]
print(digits)
```
2021-10-26 14:31:10 +02:00
# Handling files
2021-11-09 09:56:45 +01:00
When we `import` a library python will read and execute the file in question.
We can also just *read* a simple text file and *use* the data that's in the file.
There are two ways of reading a file, one more *pythonic* and one more linear.
I'll outline both and you can use whichever seems more logical to you.
2021-10-26 14:31:10 +02:00
## Reading from a file
2021-11-09 09:56:45 +01:00
### In-line way
The builtin function `open` is what's new here.
It takes two arguments, one is the [path](https://en.wikipedia.org/wiki/Path_(computing)) and the other is a *mode*.
The most used modes are **read** or **write**, and this as either **text** or **binary**.
Have a look at the documentation to discover more modes.
```python3
fp = open("./examples/data.txt", "r")
data = fp.readlines()
print("file contains: {}".format(data))
fp.close()
```
### Pythonic way
The *exact* same thing can be done in a more pythonic way as follows.
The beauty of the `with` syntax is that the `close` function call is implied by the indentation.
I personally prefer this way of reading and writing files but you do you!
```python3
file_to_open = "./examples/data.txt"
with open(file_to_open, "r") as fp:
data = fp.readlines()
print("file contains: {}".format(data))
print("{} has {} lines".format(file_to_open, len(data)))
```
2021-10-26 14:31:10 +02:00
## Writing to a file
2021-11-09 09:56:45 +01:00
Writing to a file can also be done in two ways, a pythonic and *less* pythonic way.
As I prefer the pythonic way I'll only showcase that one but you'll be able to easily adapt the code yourself.
The only difference is the **mode** we use to `open` the file.
```python3
first_name = input("what's your first name? ")
last_name = input("what's your last name? ")
birthday = input("what's your date of birth? ")
data = [first_name, last_name, birthday]
file_to_open = "./examples/test.tmp"
2021-10-26 14:31:10 +02:00
2021-11-09 09:56:45 +01:00
with open(file_to_open, "w") as fp:
for element in data:
fp.write("{}\n".format(element))
print("done!")
```
There is also a way to write a batch of lines to a file in one go.
This is done as follows.
But, you'll notice there is no **newline** added after each element.
2021-10-26 14:31:10 +02:00
2021-11-09 09:56:45 +01:00
```python3
data = ["wouter", "gordts", "1986"]
file_to_open = "./examples/test.tmp"
with open(file_to_open, "w") as fp:
fp.writelines(data)
```
2021-10-26 21:52:08 +02:00
2021-10-26 14:31:10 +02:00
# Coding challenge - Login generator
2021-11-09 09:56:45 +01:00
Can you write me a program that creates random, but easy to remember, usernames?
Plus, for each username also a *not-so-easy-to-remember* password?
Maybe with a bit of flexibility?
For example variable password length?
This is an exercise you can take pretty far if you plan it out properly.
I would advise you to write the **generator** functions as a library.
Then import those functions into the program where you implement the user facing logic.
By doing so you can reuse your code for multiple interfaces (CLI, TUI, [argparse](https://docs.python.org/3/howto/argparse.html)).
If you want to you can also **save** your logins to a file!
An example of the output I expect:
```
how many login pairs would you like to create?3
you want complex passwords? (y/n)y
how long should the password be?32
username 0: EarnestCrocodile
password 0: :sdGV&[FDYZZ|RXUpZeo`J&t@*Z>^fEW
username 1: AbstractedDragon
password 1: 32hz5&C@<o\OMa9tnET(lk(3wF%d?$Dy
username 2: MeekStallion
password 2: +;^di8a":AD;_b4^w$Fj'RVkI`CoG,LX
```
<details>
<summary>Spoiler warning</summary>
This spoiler warning is in multiple steps.
If you're unsure how to generate the *random-yet-easy-to-remember* have a look at [this file](./assets/subjects.txt) and [this file](./assets/adjectives.txt).
**Stop here and try it out!**
If you're unsure how to tackle the *library part* of this exercise, have a look at [this file](./assets/login_generator.py).
Don't just copy this code, read it and recreate it yourself!
**Stop here and try it out!**
Once the library is done you can code the *interface*.
For some inspiration for a simple question/response system you can have a look [here](./assets/pwd_cli.py).
</details>
2021-10-26 14:31:10 +02:00
2021-10-26 21:52:08 +02:00
# Dictionaries as data containers
2022-04-11 22:22:31 +02:00
## Two dimensional lists
TODO - Currency converter with lists.
## Dictionaries
2021-11-09 11:42:48 +01:00
Of the *built-in* types we first say `str`, `int` and `float`.
Next we saw *sequence* types such as `list` and `tupple`.
Now we'll dive into a **mapping type** called `dict`.
I advise you to have a look at the [reference pages](https://docs.python.org/3/library/stdtypes.html#mapping-types-dict) when in doubt.
A dictionary is *kind* of like a list but it has **two** objects per **element**.
We call them **key** and **value**.
There are a couple of rules you need to be aware of though.
1. In a `dict` the keys have to be **unique**.
2. A dictionary is **unordered** meaning the *first* element is not garanteed to remain the first over the lifespan of the dictionary.
3. The keys used must be **hashable**.
Let's visualize a legal dictionary!
It's declared with **curly brackets** as follows `{}`.
```python3
my_data = {"key": "value", "name": "wouter", "age": 35}
same_data_different_layout = {
"key": "value",
"name": "wouter",
"age": 35,
}
```
Let's have a look at the **methods** we can invoke on a `dict`.
```python3
>>> my_data = {"key": "value", "name": "wouter", "age": 35,}
>>> my_data.
my_data.clear( my_data.get( my_data.pop( my_data.update(
my_data.copy( my_data.items( my_data.popitem( my_data.values(
my_data.fromkeys( my_data.keys( my_data.setdefault(
>>> my_data.keys()
dict_keys(['key', 'name', 'age'])
>>> my_data.values()
dict_values(['value', 'wouter', 35])
>>> my_data.items()
dict_items([('key', 'value'), ('name', 'wouter'), ('age', 35)])
>>> for key, value in my_data.items():
... print("key is {}".format(key))
... print("value is {}".format(value))
...
key is key
value is value
key is name
value is wouter
key is age
value is 35
>>>
```
We can reference specific **values** corresponding to specific **keys** as follows.
```pythons
>>> my_data["name"]
'wouter'
>>> my_data["age"]
35
>>>
```
We can use dictionaries as data containers and put them in a list to group together similar data elements.
The code below should explain it quite nicely.
```python3
login_ovh = {"username": "EarnestCrocodile", "password": ":sdGV&[FDYZZ|RXUpZeo`J&t@*Z>^fEW"}
login_mailbox = {"username": "AbstractedDragon", "password": "32hz5&C@<o\OMa9tnET(lk(3wF%d?$Dy"}
login_gitea = {"username": "MeekStallion", "password": "+;^di8af:AD;_b4^w$Fj'RVkI`CoG,LX"}
my_login_list = [login_ovh, login_mailbox, login_gitea]
for login in my_login_list:
print("login {}".format(my_login_list.index(login)))
for key in login.keys():
print("\t{}: {}".format(key, login[key]))
```
2021-10-26 21:52:08 +02:00
2021-11-12 12:38:15 +01:00
🏃 Try it
---
Go back to you *currency converter* program and add a `dict` with multiple currencies so you can enter one amount to convert and your program gives you how much that is in each currency of the `dict`.
2021-11-09 14:41:21 +01:00
# Coding challenge - Task manager
2021-10-26 14:31:10 +02:00
2021-11-09 14:41:21 +01:00
Can you create me a *task manager* please?
I made one that is run from the command line with different arguments to modify it's behaviour.
If this looks too challenging I can tell you that the *full code* is 64 lines long, including empty lines!
Those of you who already tried out [argparse](https://realpython.com/command-line-interfaces-python-argparse/) in the previous challenges will probably understand what's going on here.
Those who did not I urge you to have a look at the link above and don't hesitate to ask for help!
```bash
➜ python_course_doc git:(master) ✗ python3 test.py --help
usage: test.py [-h] [--file FILE] {add,delete,show} ...
positional arguments:
{add,delete,show}
add adds a todo item to you todo list
delete deletes an item from your todo list
show show all your tasks
optional arguments:
-h, --help show this help message and exit
--file FILE, -f FILE path to your todo file
➜ python_course_doc git:(master) ✗ python3 test.py show
ID: 0 --- buy milk
ID: 1 --- clean house
ID: 2 --- test my code
➜ python_course_doc git:(master) ✗ python3 test.py add write some documentation
➜ python_course_doc git:(master) ✗ python3 test.py show
ID: 0 --- buy milk
ID: 1 --- clean house
ID: 2 --- test my code
ID: 3 --- write some documentation
➜ python_course_doc git:(master) ✗ python3 test.py delete 2
➜ python_course_doc git:(master) ✗ python3 test.py show
ID: 0 --- buy milk
ID: 1 --- clean house
ID: 2 --- write some documentation
➜ python_course_doc git:(master) ✗
```
<details>
<summary>Spoiler warning</summary>
```python3
import argparse
import pathlib
def read_taskfile(taskfile_path):
tasks = []
if not pathlib.Path(taskfile_path).exists():
return tasks
with open(taskfile_path, "r") as fp:
lines = fp.readlines()
for line in lines:
task = line.strip()
tasks.append(task)
return tasks
def write_taskfile(taskfile_path, tasks):
with open(taskfile_path, "w") as fp:
for task in tasks:
fp.write("{}\n".format(task))
def show_tasks(tasks):
counter = 0
for task in tasks:
print("ID: {} --- {}".format(counter, task))
counter += 1
def add_task(tasks, task):
tasks.append(task)
return tasks
def delete_task(tasks, task_id):
tasks.pop(task_id)
return tasks
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--file", "-f", default="./todo.tasks", help="path to your todo file")
subparser = parser.add_subparsers()
add = subparser.add_parser("add", help="adds a todo item to you todo list")
add.add_argument("task", nargs="*")
delete = subparser.add_parser("delete", help="deletes an item from your todo list")
delete.add_argument("idx", type=int, nargs="*")
show = subparser.add_parser("show", help="show all your tasks")
show.add_argument("show", action="store_true")
args = parser.parse_args()
taskfile = args.file
tasks = read_taskfile(taskfile)
if "task" in args:
tasks = add_task(tasks, " ".join(args.task))
write_taskfile(taskfile, tasks)
elif "idx" in args:
for idx in args.idx:
tasks = delete_task(tasks, idx)
write_taskfile(taskfile, tasks)
elif args.show:
show_tasks(tasks)
```
</details>
2021-11-09 17:04:26 +01:00
# Text based databases
The todo list example from before is handy but quite *limited* as a database.
As is it only holds one form or information and that is the *actual task*.
What if we want to add urgency or mark tasks complete (instead of deleting)?
This can be done by grouping data together.
We already saw a dictionaries which are good mapping structures but how can we save them to disk?
A hacky way would be to write a python file containing the `dict` and `import` it when we need it.
But there are better ways.
2021-11-12 11:08:23 +01:00
The table below probably makes you think of *excel*.
We can use excel to create a text based **database** where the first line, called the **header**, defines what information is stored in the database.
Every **row** after the header is an **entry** with the item's values.
Sounds more complicated than it is.
| description | urgency | done |
|-------------------------------|---------|------|
| go to the shops | 9 | 0 |
| install music computer | 4 | 0 |
| download python documentation | 7 | 1 |
| bottle new beer | 9 | 1 |
We can *convert* this table to a **Comma Separated Value** file, which can easily be read by python.
```csv
description,urgency,done
go to the shops,9,0
install music computer,4,0
download python documentation,7,1
bottle new beer,9,1
```
If we save this to a blank file we can us the built-in `open` function to read and interpret the data as a `list` of `dict`.
The code below does exactly that, first without list comprehension, secondly with.
2021-11-12 17:32:47 +01:00
A CSV file is such a **standard** in programming that most languages come with a built-in [parser](https://en.wikipedia.org/wiki/Parsing), hence the `import csv`.
2021-11-12 11:08:23 +01:00
```python
import csv
with open("./data.csv", "r") as fp:
tasks = csv.DictReader(fp)
print(tasks)
for task in tasks:
print(task)
with open("./data.csv", "r") as fp:
tasks = [task for task in csv.DictReader(fp)]
print(tasks)
```
🏃 Try it
---
Adapt your task manager to read tasks for a CSV file.
Implement the urgency to sort your tasks by importance.
Don't delete tasks from the file but rather mark them as *done*.
2021-11-09 09:56:45 +01:00
2021-11-09 11:42:48 +01:00
# Now for some useful scripting
With everything we have learned up until now you can start doing some interesting and useful things.
Have a look at [these exercises](https://gitea.86thumbs.net/waldek/linux_course_doc/src/branch/master/modules/qualifying/exercise_python.md).
You can try them out at your own pace.
I can give some hints or pointers if needed, either in class or individually.
2021-11-09 17:04:26 +01:00
Don't hesitate to ask for help!
2021-11-09 11:42:48 +01:00
2021-11-09 18:46:06 +01:00
# Creating our own objects
2021-10-26 14:31:10 +02:00
2021-11-10 13:48:59 +01:00
We've been using built-in objects like `str`, `int` and `float` but we can create our own objects as well!
You might wonder *why* you would do this and that's a valid reflection.
For beginners it's often a bit unclear when to create your own objects and when not.
We'll go over some abstract examples to showcase the syntax and usage, then do a couple of practical exercises.
2021-11-09 18:46:06 +01:00
## First some *abstract* examples
2021-10-26 14:31:10 +02:00
2021-11-10 13:48:59 +01:00
A basic abstraction can be seen below.
The main thing to take away from this code block is the new [keyword](https://docs.python.org/3/reference/compound_stmts.html#class-definitions) `class`.
2021-11-12 17:32:47 +01:00
Once the new `class` is defined (remember what `pass` does?), we can create an **instance** of this class.
2021-11-10 13:48:59 +01:00
We can create as many instances as we want, just as with `str`!
```python
class Animal(object):
pass
dog = Animal()
dog.name = "bobby"
dog.legs = 4
print("the {} named {} has {} legs".format(
dog.__class__.__name__,
dog.name,
dog.legs,
)
)
```
We can create as many attributes as we want and in the example above we do this for a `name` and a number of `legs`.
An `Animal` will *always* have a `name` and a number of `legs` so why don't we foresee their existence?
We can do this as follows.
```python
class Animal(object):
def __init__(self, name, legs):
self.name = name
self.legs = legs
dog_1 = Animal("bobby", 4)
dog_2 = Animal("dianne", 4)
dog_3 = Animal("rex", 3)
all_my_dogs = [dog_1, dog_2, dog_3]
for dog in all_my_dogs:
print("the {} named {} has {} legs".format(
dog.__class__.__name__,
dog.name,
dog.legs,
)
)
```
Now, objects don't just possess *attributes*, they can also *do* things!
Executing a piece of code should get you thinking about **functions**.
A function belonging to a `class` is called a **method** and we can easily create them!
```python
class Animal(object):
def __init__(self, name, legs):
self.name = name
self.legs = legs
def speaks(self):
print("barks ~~{}~~!".format(self.name.upper()))
dog_1 = Animal("bobby", 4)
dog_2 = Animal("dianne", 4)
dog_3 = Animal("rex", 3)
all_my_dogs = [dog_1, dog_2, dog_3]
for dog in all_my_dogs:
print("the {} named {} has {} legs".format(
dog.__class__.__name__,
dog.name,
dog.legs,
)
)
dog.speaks()
```
And just as with functions defined outside of a `class` we can add arguments to our own methods.
```python
class Animal(object):
def __init__(self, name, legs, owner):
self.name = name
self.legs = legs
self.owner = owner
def speaks(self):
print("barks ~~{}~~!".format(self.name.upper()))
def jumps_on(self, person):
if person == self.owner:
print("{} licks {}'s face...".format(self.name, person))
else:
print("{} growls in {}'s face".format(self.name, person))
2021-11-10 14:57:09 +01:00
2021-11-10 13:48:59 +01:00
dog_1 = Animal("bobby", 4, "dave")
dog_2 = Animal("dianne", 4, "alice")
dog_3 = Animal("rex", 3, "dave")
all_my_dogs = [dog_1, dog_2, dog_3]
for dog in all_my_dogs:
print("the {} named {} has {} legs".format(
dog.__class__.__name__,
dog.name,
dog.legs,
)
)
dog.speaks()
dog.jumps_on("alice")
print("\t---\n")
```
🏃 Try it
---
Think of some other world objects you can create a class for and do so.
The first one that comes to mind is vehicles but you can do anything that makes sense to you.
2021-10-26 14:31:10 +02:00
2021-11-15 16:25:17 +01:00
# Coding challenge - Bus line
There is a bus line with with 20 stops on it.
At each stop there are a number of people waiting according to the table below.
Throughout the day 4 buses will pass by and each bus has 30 seats on it.
* How many people are left at each stop at the end of the day?
* How many buses would the bus line need in order to get everybody to the terminal?
* What if at each stop 10% of the people on the bus get off?
| bus stop | people waiting |
|-------------------|----------------|
| 1 | 6 |
| 2 | 5 |
| 2 | 3 |
| 3 | 10 |
| 4 | 14 |
| 5 | 3 |
| 6 | 5 |
| 7 | 7 |
| 8 | 4 |
| 9 | 12 |
| 10 | 14 |
| 11 | 9 |
| 12 | 2 |
| 13 | 1 |
| 14 | 5 |
| 15 | 12 |
| 16 | 4 |
| 17 | 3 |
| 18 | 8 |
| 19 | 12 |
| 20 | 1 |
<details>
<summary>Spoiler warning</summary>
**TODO**
</details>
2021-10-26 14:31:10 +02:00
## Class inheritance
2021-11-10 13:48:59 +01:00
Inheritance is one of the four main pillars of Object Orientated Programming.
The idea is pretty simple, classes can inherit behaviour from a *parent* class.
For example, `Dog` and `Cat` are both `Animal` so they share common attributes and methods.
```python
class Animal(object):
def __init__(self, name, legs, owner):
self.name = name
self.legs = legs
self.owner = owner
def jumps_on(self, person):
if person == self.owner:
print("{} licks {}'s face...".format(self.name, person))
else:
print("{} growls in {}'s face".format(self.name, person))
class Dog(Animal):
CALL = "barks"
def __init__(self, name, legs=4, owner=None):
Animal.__init__(self, name, legs, owner)
def speaks(self):
print("{} ~~{}~~!".format(self.__class__.CALL, self.name.upper()))
class Cat(Animal):
CALL = "miauws"
def __init__(self, name, legs=4, owner=None):
Animal.__init__(self, name, legs, owner)
def speaks(self):
print("{} ~~{}~~!".format(self.__class__.CALL, self.name.lower()))
class Parrot(Animal):
CALL = "creaks"
def __init__(self, name, legs=2, owner=None):
Animal.__init__(self, name, legs, owner)
def speaks(self):
print("{} ~~{}~~!".format(self.__class__.CALL, self.name.lower()))
def jumps_on(self, person):
print("you're silly, I'm a {}, I don't jump...".format(
self.__class__.__name__)
)
animal_1 = Dog(name="bobby", owner="dave")
animal_2 = Dog(name="dianne", owner="alice")
animal_3 = Cat("garfield", 3, "dave")
animal_4 = Parrot("coco", "alice")
animal_5 = Cat("gertrude", 3, "dave")
all_animals = [animal_1, animal_2, animal_3, animal_4, animal_5]
for animal in all_animals:
print("the {} named {} has {} legs".format(
animal.__class__.__name__,
animal.name,
animal.legs,
)
)
animal.speaks()
animal.jumps_on("alice")
print("\t---\n")
```
🏃 Try it
---
2021-11-10 14:57:09 +01:00
Take the additional class you made, such as vehicle and do some inheritance.
2021-11-10 13:48:59 +01:00
For vehicles you could create a base class with a brand and number of wheels and seats.
Then inherit for a bus, bike, car, cycle, etc.
2021-11-12 12:16:49 +01:00
# Coding challenge - Chalet floor
Can you calculate me how much wood I need to order to build the base of my chalet?
I have three different plans, you can see below.
The idea behind this exercise is to create a `class` for each *base* shape.
If you implement the *right* methods you can add, subtract, multiply your shapes as you wish.
I created a library and imported it in my python shell so I can do the following.
```python
>>> import shapes
>>> sq1 = shapes.
shapes.Circle( shapes.Rectangle( shapes.Shape( shapes.Square( shapes.Triangle( shapes.math
>>> sq1 = shapes.Square(400)
>>> cr1 = shapes.Circle(30)
>>> re1 = shapes.Rectangle(400, 600)
>>> total = sq1 + re1 - cr1
>>> print(total)
I'm a Shape with an area of 397172.57 cm2
>>> print(re1)
I'm a Rectangle with an area of 240000.00 cm2
>>> print(cr1)
I'm a Circle with an area of 2827.43 cm2
>>> print(sq1)
I'm a Square with an area of 160000.00 cm2
>>>
```
There are quite a few things in this exercise we have not seen in detail, so don't hesitate to research online and ask questions!
This is a quite **advanced** challenge so you can be very proud of yourself if you understand the solution!
![first plan](./assets/plan_01_topview.png)
![second plan](./assets/plan_02_topview.png)
![third plan](./assets/plan_03_topview.png)
![third plan 3D view](./assets/plan_03_3d.png)
<details>
<summary>Spoiler warning</summary>
2021-11-10 14:57:09 +01:00
```python
import math
class Shape(object):
def __init__(self):
self.area = 0
def __str__(self):
return "I'm a {} with an area of {:.2f} cm2".format(
self.__class__.__name__,
self.area,
)
def __add__(self, other):
combi_shape = Shape()
combi_shape.area = self.area + other.area
return combi_shape
def __sub__(self, other):
combi_shape = Shape()
combi_shape.area = self.area - other.area
return combi_shape
def __div__(self, value):
combi_shape = Shape()
combi_shape.area = self.area / value
return combi_shape
class Square(Shape):
def __init__(self, width):
Shape.__init__(self)
self.area = width * width
class Rectangle(Shape):
def __init__(self, width, height):
Shape.__init__(self)
self.area = width * height
class Triangle(Shape):
def __init__(self, base, height):
Shape.__init__(self)
self.area = (self.width * self.height) / 2
class Circle(Shape):
def __init__(self, radius):
Shape.__init__(self)
self.area = math.pi * math.pow(radius, 2)
if __name__ == "__main__":
t = Square(4)
r = Rectangle(4, 6)
c = Circle(1.6)
print(t)
print(r)
print(c)
combi = t + r - c
print(combi)
```
2021-10-26 14:31:10 +02:00
2021-11-12 12:16:49 +01:00
</details>
2021-11-09 18:46:06 +01:00
## Now some *practical* improvements
### Improve the login generator
2021-10-26 14:31:10 +02:00
TODO convert the login generator to a class
2021-11-09 18:46:06 +01:00
### Improve the task manager
TODO convert the task manager to a class
2021-10-26 14:31:10 +02:00
# Infinite programs
* insist on the nature of scripts we did up until now
## Logic breakdown of a simple game
2021-11-09 11:42:48 +01:00
```
***********************
* welcome to hangman! *
***********************
guess the hidden word below
---------------------------
word: *****
guess a letter: a
word: a****
guess a letter: l
word: a**l*
guess a letter: p
word: appl*
guess a letter: e
word: apple
*****************
* you found it! *
*****************
do you want to play a new game? (Y/N)
```
2021-10-26 14:31:10 +02:00
2021-11-12 16:15:52 +01:00
When looking at the game above we can break down the game logic as follows.
1. A welcome message is printed
2. A *word-to-find* is chosen randomly from a database
3. The word is shown as `*****` to hint at the length of the word
4. A prompt is presented to the player and the can input **one** letter
5. If the letter is present in the *word-to-guess* it is saved and shown from now on
6. The previous 2 steps are **repeated** until the full word is found
7. A winning banner is shown
8. The player is asked to play a new game or not.
2021-10-26 14:31:10 +02:00
## Trivial pursuit multiple choice game
2021-11-12 16:15:52 +01:00
We can fetch questions for an online [api](https://opentdb.com/api_config.php) to make a game.
I downloaded a mini set of [questions](./assets/quiz_data.json) to help you get started.
Have a look at the code below to understand how to develop your game logic.
Once you successfully build a game you can try and integrate the [requests](https://docs.python-requests.org/en/latest/) library to get fresh questions each time you play the game.
```python
import json
import html
with open("./assets/quiz_data.json", "r") as fp:
data = json.load(fp)
for key, value in data.items():
print(key)
for question in data["results"]:
for key, value in question.items():
print(key)
for question in data["results"]:
print("{}".format(html.unescape(question["question"])))
choices = list()
choices.append(question["correct_answer"])
choices.extend(question["incorrect_answers"])
for choice in enumerate(choices):
print(html.unescape("\t {} {}".format(*choice)))```
2021-11-12 16:16:57 +01:00
```
2021-10-26 14:31:10 +02:00
2021-11-12 17:32:47 +01:00
You can get some *inspiration* from a small project I did for an other course.
The code can be found [here](https://gitea.86thumbs.net/waldek/ccpq/src/branch/master).
2021-10-26 21:52:08 +02:00
### Introduction to the `requests` library
2021-10-26 14:31:10 +02:00
## Threading
TODO add a countdown timer to the multiple choice game
2021-10-29 09:24:46 +02:00
* how apt does it's [progress bar](https://mdk.fr/blog/how-apt-does-its-fancy-progress-bar.html)
2021-10-26 14:31:10 +02:00
# GUI programming
2021-11-29 23:25:06 +01:00
TODO event loop introduction
2021-10-26 14:31:10 +02:00
## wxpython helloworld
2021-10-25 09:24:01 +02:00
2021-11-29 23:25:06 +01:00
The absolute most basic way to have a *hello world* GUI program up and running with wxpython is the following.
```python
import wx
app = wx.App()
frame = wx.Frame(None, title="hello world")
frame.Show()
app.MainLoop()
```
While it works we know better by now.
We should include the `if __name__ == "__main__"` statement.
```python
import wx
if __name__ == "__main__":
app = wx.App()
frame = wx.Frame(None, title="hello world")
frame.Show()
app.MainLoop()
```
The instance of `wx.App` is what will actually process our event loop and the `wx.Frame` is the content of our window.
In the latter we will create our button and labels so should create our own class and inherit from it.
```python
import wx
class MainWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="hello world")
self.Show()
if __name__ == "__main__":
app = wx.App()
win = MainWindow()
app.MainLoop()
```
We can add content to the *frame*, such as labels, input boxes and buttons as follows.
Note the first argument to the `wx.StaticText` creation.
This is *where* we put the label *into*.
It kind of works but we'll encounter a **problem** when we pack more visual **objects** into the same frame.
```python
import wx
class MainWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="hello world")
self.label = wx.StaticText(self, label="this is a label")
self.Show()
if __name__ == "__main__":
app = wx.App()
win = MainWindow()
app.MainLoop()
```
Let's try to put multiple visual object into the same frame.
```python
import wx
class MainWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="hello world")
self.label = wx.StaticText(self, label="this is a label")
self.input = wx.TextCtrl(self)
self.button = wx.Button(self)
self.Show()
if __name__ == "__main__":
app = wx.App()
win = MainWindow()
app.MainLoop()
```
You see how they are *stacked* one on top of the other?
We can overcome this problem with [sizers](https://wxpython.org/Phoenix/docs/html/sizers_overview.html).
There are multiple ones we can use but let's dive into a first one called a **box sizer**.
```python
import wx
class MainWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="hello world")
self.panel = wx.Panel(self)
self.box = wx.BoxSizer()
self.label = wx.StaticText(self.panel, label="this is a label")
self.input = wx.TextCtrl(self.panel)
self.button = wx.Button(self.panel, label="I'm clickable!")
self.box.Add(self.label)
self.box.Add(self.input)
self.box.Add(self.button)
self.panel.SetSizer(self.box)
self.Show()
if __name__ == "__main__":
app = wx.App()
win = MainWindow()
app.MainLoop()
```
This is looking better!
But it requires some explanation though.
Let's break it down.
The `wx.Frame` is your **window** in which you create a `wx.Panel` which is used to draw thing to.
To this panel you're adding three different *objects* (`wx.StaticText`, `wx.TextCtrl` and `wx.Button`) each with or without their own settings (such as a label or not).
Next you add these three objects to the `wx.BoxSizer` and you tell the panel to use this box as sizer.
It probably looks a bit convoluted but this is how most GUI libraries work internally.
1. You create a frame
2. Within this frame you create a *drawing area* and set some form of automatic layout to it (the `wx.BoxSizer`).
3. You create the visual elements you want and add them one by one to the drawing area.
4. Success
Now how do we link **user input** to **code actions**?
This is a complicated way of saying *actually do something when I click the damn button*!
For this we'll need to create a function, or better yet a method to the `wx.Frame` objects.
Each time we click the button, that method will be called.
Because it is a *method* it has access to *self* so it can modify *anything* within the scope of the instance.
```python
import wx
import random
class MainWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="hello world")
self.panel = wx.Panel(self)
self.box = wx.BoxSizer()
self.label = wx.StaticText(self.panel, label="this is a label")
self.input = wx.TextCtrl(self.panel)
self.button = wx.Button(self.panel, label="I'm clickable!")
self.button.Bind(wx.EVT_BUTTON, self.set_label_value)
self.box.Add(self.label)
self.box.Add(self.input)
self.box.Add(self.button)
self.panel.SetSizer(self.box)
self.Show()
def set_label_value(self, event):
number = random.randint(0, 100)
self.label.SetLabel("{}".format(number))
if __name__ == "__main__":
app = wx.App()
win = MainWindow()
app.MainLoop()
```
We can use the same *idea* to grab input from the textbox.
```python
import wx
class MainWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="hello world")
self.panel = wx.Panel(self)
self.box = wx.BoxSizer()
self.label = wx.StaticText(self.panel, label="this is a label")
self.input = wx.TextCtrl(self.panel)
self.button = wx.Button(self.panel, label="I'm clickable!")
self.button.Bind(wx.EVT_BUTTON, self.set_label_value)
self.box.Add(self.label)
self.box.Add(self.input)
self.box.Add(self.button)
self.panel.SetSizer(self.box)
self.Show()
def set_label_value(self, event):
msg = self.input.GetValue()
self.label.SetLabel("{}".format(msg))
if __name__ == "__main__":
app = wx.App()
win = MainWindow()
app.MainLoop()
```
2021-10-26 14:31:10 +02:00
## wxpython guess the number
2021-10-25 09:24:01 +02:00
2021-11-29 23:25:06 +01:00
```python
import wx
import random
class MainWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Guess the number")
self.number = random.randint(0, 100)
self.panel = wx.Panel(self)
self.box = wx.BoxSizer(wx.VERTICAL)
self.label = wx.StaticText(self.panel, label="I have a number in mind...")
self.input = wx.TextCtrl(self.panel)
self.button = wx.Button(self.panel, label="I'm clickable!")
self.button.Bind(wx.EVT_BUTTON, self.set_label_value)
self.box.Add(self.label)
self.box.Add(self.input)
self.box.Add(self.button)
self.panel.SetSizer(self.box)
self.Show()
def set_label_value(self, event):
result = self.input.GetValue()
if result.isdigit():
status, context = self.evaluate_user_number(int(result))
self.label.SetLabel(context)
else:
self.label.SetLabel("I need numbers!")
def evaluate_user_number(self, number):
if number > self.number:
return False, "my number is smaller"
elif number < self.number:
return False, "my number is bigger"
elif number == self.number:
return True, "You win!"
if __name__ == "__main__":
app = wx.App()
win = MainWindow()
app.MainLoop()
```
2021-10-26 21:52:08 +02:00
## MVC design pattern
2021-10-25 09:24:01 +02:00
2021-12-06 23:32:49 +01:00
A simple console only MVC.
We'll add the GUI view in a bit.
```python
import wx
class ConsoleView(object):
"""A view for console."""
def select_task(self):
"""Asks which index to look up."""
idx = input("which task do you want to see? ")
return idx
def show(self, task):
"""Displays the task to the console. This method is called from the
controller."""
print("your task: {}".format(task))
def error(self, msg):
"""Prints error messages coming from the controller."""
print("error: {}".format(msg))
class WxView(wx.Dialog):
pass
class Model(object):
"""The model houses add data and should implement all methods related to
adding, modifying and deleting tasks."""
db = ["go to the shops", "dryhop beer", "drop of motorbike"]
def get_task(self, idx):
"""Performs a task lookun into the database and returns it when found.
If no task is found, it returns an error message that will be displayed
in the view (via the controller)."""
try:
task = Model.db[idx]
except IndexError:
task = "task with {} not found!".format(idx)
return task
class Controller(object):
"""Binds the model and the view together."""
def __init__(self, view):
self.model = Model()
self.view = view
def run(self):
"""The controller's main function. Depending on what type of view is
selected, a different event loop is setup. Do note that the ConsoleView
is not a real event loop, just a basic flow of action."""
if self.view is ConsoleView:
self.view = self.view()
self._run_console_view()
elif self.view is WxView:
app = wx.App()
self.view = self.view()
self.view._set_controller(self)
app.MainLoop()
def get_task_from_model(self, idx):
"""Needed for the WxView to communicate with the controller."""
task = self.model.get_task(idx)
self.view.show_task(task)
def _run_console_view(self):
"""Super simple event loop."""
while True:
try:
idx = self.view.select_task()
idx = int(idx)
except Exception as e:
self.view.error(e)
continue
task = self.model.get_task(idx)
self.view.show(task)
if __name__ == "__main__":
view = ConsoleView
# view = WxView
app = Controller(view)
app.run()
```
And now with the implemented `WxView` class.
```python
import wx
class ConsoleView(object):
"""A view for console."""
def select_task(self):
"""Asks which index to look up."""
idx = input("which task do you want to see? ")
return idx
def show(self, task):
"""Displays the task to the console. This method is called from the
controller."""
print("your task: {}".format(task))
def error(self, msg):
"""Prints error messages coming from the controller."""
print("error: {}".format(msg))
class WxView(wx.Dialog):
"""A view using a wx.Dialog window"""
def __init__(self):
wx.Dialog.__init__(self, None, title="Task Manager")
self.panel = wx.Panel(self)
self.box = wx.BoxSizer(wx.VERTICAL)
self.task = wx.StaticText(self.panel, label="your task")
self.idx = wx.SpinCtrl(self.panel)
self.button = wx.Button(self.panel, label="submit")
self.button.Bind(wx.EVT_BUTTON, self.select_task)
self.box.Add(self.task, 0, wx.EXPAND, 1)
self.box.Add(self.idx, 0, wx.EXPAND, 1)
self.box.Add(self.button, 0, wx.EXPAND, 1)
self.panel.SetSizer(self.box)
self.Show()
def _set_controller(self, controller):
"""Set the controller so the view can communicate it's requests to it
and update it's values too."""
self.controller = controller
def select_task(self, event):
"""Gets the index to look up in the model and submits the request to
the controller."""
idx = self.idx.GetValue()
self.controller.get_task_from_model(idx)
def show_task(self, task):
"""Updates the visual label in the view with the task. This method is
called from the controller."""
self.task.SetLabel(task)
class Model(object):
"""The model houses add data and should implement all methods related to
adding, modifying and deleting tasks."""
db = ["go to the shops", "dryhop beer", "drop of motorbike"]
def get_task(self, idx):
"""Performs a task lookun into the database and returns it when found.
If no task is found, it returns an error message that will be displayed
in the view (via the controller)."""
try:
task = Model.db[idx]
except IndexError:
task = "task with {} not found!".format(idx)
return task
class Controller(object):
"""Binds the model and the view together."""
def __init__(self, view):
self.model = Model()
self.view = view
def run(self):
"""The controller's main function. Depending on what type of view is
selected, a different event loop is setup. Do note that the ConsoleView
is not a real event loop, just a basic flow of action."""
if self.view is ConsoleView:
self.view = self.view()
self._run_console_view()
elif self.view is WxView:
app = wx.App()
self.view = self.view()
self.view._set_controller(self)
app.MainLoop()
def get_task_from_model(self, idx):
"""Needed for the WxView to communicate with the controller."""
task = self.model.get_task(idx)
self.view.show_task(task)
def _run_console_view(self):
"""Super simple event loop."""
while True:
try:
idx = self.view.select_task()
idx = int(idx)
except Exception as e:
self.view.error(e)
continue
task = self.model.get_task(idx)
self.view.show(task)
if __name__ == "__main__":
view = WxView
app = Controller(view)
app.run()
```
For a GUI only login generator an implementation without MVC could look a bit like this.
Note that the actual calculation is done inside the window itself.
This is not a good idea because we should separate responsibilities into classes!
```python
import wx
import login_generator
class MainWindow(wx.Dialog):
def __init__(self):
wx.Dialog.__init__(self, None, title="Login Generator")
self.full_window = wx.Panel(self)
self.full_window_sizer = wx.BoxSizer(wx.VERTICAL)
self.full_window_sizer.Add(self.create_top_panel(), 0, wx.EXPAND | wx.ALL, 20)
self.full_window_sizer.Add(self.create_bottom_panel(), 0, wx.EXPAND, 0)
self.full_window.SetSizer(self.full_window_sizer)
self.Show()
def create_bottom_panel(self):
bottom_panel = wx.Panel(self.full_window)
bottom_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.login_list = wx.ListCtrl(bottom_panel, style=wx.LC_REPORT)
self.login_list.Bind(wx.EVT_RIGHT_UP, self.show_popup)
self.login_list.InsertColumn(0, 'username', width=200)
self.login_list.InsertColumn(1, 'password', width=200)
bottom_panel_sizer.Add(self.login_list, 0, wx.EXPAND | wx.ALL, 200)
return bottom_panel
def create_top_panel(self):
top_panel = wx.Panel(self.full_window)
top_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.login_amount = wx.TextCtrl(top_panel)
self.login_complex = wx.CheckBox(top_panel, label="complex")
self.login_create = wx.Button(top_panel, label="Create")
self.login_create.Bind(wx.EVT_BUTTON, self.add_login)
top_panel_sizer.Add(self.login_amount, 1, wx.EXPAND|wx.ALL,0)
top_panel_sizer.Add(self.login_complex, 1, wx.EXPAND|wx.ALL,0)
top_panel_sizer.Add(self.login_create, 1, wx.EXPAND|wx.ALL,0)
top_panel.SetSizer(top_panel_sizer)
return top_panel
def show_popup(self, event):
menu = wx.Menu()
menu.Append(1, "Copy selected items")
menu.Bind(wx.EVT_MENU, self.copy_items, id=1)
self.PopupMenu(menu)
def copy_items(self, event):
selected_items = []
for i in range(self.login_list.GetItemCount()):
if self.login_list.IsSelected(i):
username = selected_items.append(
self.login_list.GetItem(i, 0).GetText()
)
password = selected_items.append(
self.login_list.GetItem(i, 1).GetText()
)
clipdata = wx.TextDataObject()
clipdata.SetText("\n".join(selected_items))
wx.TheClipboard.Open()
wx.TheClipboard.SetData(clipdata)
wx.TheClipboard.Close()
def add_login(self, event):
amount = self.login_amount.GetValue()
complex = self.login_complex.GetValue()
try:
amount = int(amount)
except:
amount = 1
for i in range(0, amount):
username = login_generator.generate_username()
password = login_generator.generate_password(12, complex)
index = self.login_list.InsertItem(0, username)
self.login_list.SetItem(index, 1, password)
if __name__ == "__main__":
app = wx.App()
win = MainWindow()
app.MainLoop()
```
Now let's assume the generate username and password function take some calculation time.
We'll add in a fake `time.sleep` to simulate.
```python
import wx
import login_generator
import time
class MainWindow(wx.Dialog):
def __init__(self):
wx.Dialog.__init__(self, None, title="Login Generator")
self.full_window = wx.Panel(self)
self.full_window_sizer = wx.BoxSizer(wx.VERTICAL)
self.full_window_sizer.Add(self.create_top_panel(), 0, wx.EXPAND | wx.ALL, 20)
self.full_window_sizer.Add(self.create_bottom_panel(), 0, wx.EXPAND, 0)
self.full_window.SetSizer(self.full_window_sizer)
self.Show()
def create_bottom_panel(self):
bottom_panel = wx.Panel(self.full_window)
bottom_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.login_list = wx.ListCtrl(bottom_panel, style=wx.LC_REPORT)
self.login_list.Bind(wx.EVT_RIGHT_UP, self.show_popup)
self.login_list.InsertColumn(0, 'username', width=200)
self.login_list.InsertColumn(1, 'password', width=200)
bottom_panel_sizer.Add(self.login_list, 0, wx.EXPAND | wx.ALL, 200)
return bottom_panel
def create_top_panel(self):
top_panel = wx.Panel(self.full_window)
top_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.login_amount = wx.TextCtrl(top_panel)
self.login_complex = wx.CheckBox(top_panel, label="complex")
self.login_create = wx.Button(top_panel, label="Create")
self.login_create.Bind(wx.EVT_BUTTON, self.add_login)
top_panel_sizer.Add(self.login_amount, 1, wx.EXPAND|wx.ALL,0)
top_panel_sizer.Add(self.login_complex, 1, wx.EXPAND|wx.ALL,0)
top_panel_sizer.Add(self.login_create, 1, wx.EXPAND|wx.ALL,0)
top_panel.SetSizer(top_panel_sizer)
return top_panel
def show_popup(self, event):
menu = wx.Menu()
menu.Append(1, "Copy selected items")
menu.Bind(wx.EVT_MENU, self.copy_items, id=1)
self.PopupMenu(menu)
def copy_items(self, event):
selected_items = []
for i in range(self.login_list.GetItemCount()):
if self.login_list.IsSelected(i):
username = selected_items.append(
self.login_list.GetItem(i, 0).GetText()
)
password = selected_items.append(
self.login_list.GetItem(i, 1).GetText()
)
clipdata = wx.TextDataObject()
clipdata.SetText("\n".join(selected_items))
wx.TheClipboard.Open()
wx.TheClipboard.SetData(clipdata)
wx.TheClipboard.Close()
def add_login(self, event):
amount = self.login_amount.GetValue()
complex_bool = self.login_complex.GetValue()
try:
amount = int(amount)
except:
amount = 1
for i in range(0, amount):
username = login_generator.generate_username()
password = login_generator.generate_password(12, complex_bool)
time.sleep(1)
index = self.login_list.InsertItem(0, username)
self.login_list.SetItem(index, 1, password)
if __name__ == "__main__":
app = wx.App()
win = MainWindow()
app.MainLoop()
```
A clear separation of responsabilities can be acchieved via an MVC pattern and a login *library*.
The library code is quite straightforward and goes as follows.
It's basically the same code we did before but with added `try except` blocks.
Can you tell me why I added those?
```python
import random
import string
def load_file(filename):
"""
We load a file and make a list out of it. Note that the same function is
used for both files (both adjectives and subjects). Functions should be
made as generic as possible.
There IS a problem you can fix, some logins will have spaces in them. Try
to remove them in this function!
"""
words = []
with open(filename, "r") as fp:
lines = fp.readlines()
for line in lines:
words.append(line.strip()) # what does strip() do, what does append() do? remember CTRL+Q!
return words
def generate_username():
"""
We'll generate a random pair of adjectives and subjects from two wordlists.
You NEED to have both files in you python project for this to work! Note
the capitalize method call to make it all prettier...
"""
try:
adjectives = load_file("./adjectives.txt")
except:
adjectives = ["big", "funny", "normal", "red"]
try:
subjects = load_file("./subjects.txt")
except:
subjects = ["giraffe", "elephant", "cougar", "tiger"]
adjective = random.choice(adjectives)
subject = random.choice(subjects)
username = adjective.capitalize() + subject.capitalize()
return username
def generate_password(length=10, complictated=True):
"""
We generate a password with default settings. You can overide these by
changing the arguments in the function call.
"""
password = ""
if complictated:
chars = string.ascii_letters + string.digits + string.punctuation
else:
chars = string.ascii_letters
for i in range(0, length):
password += random.choice(chars)
return password
if __name__ == "__main__":
# let's do some testing!
username_test = generate_username()
print(username_test)
password_test = generate_password()
print(password_test)
```
And now the GUI code nicely split up in a **model**, **controller** and a **view**.
The overhead is quite large but it makes the code a lot more scalable.
```python
import wx
import login_generator
import time
import threading
import queue
class View(wx.Dialog):
def __init__(self, controller):
wx.Dialog.__init__(self, None, title="Login Generator")
self.controller = controller
self.full_window = wx.Panel(self)
self.full_window_sizer = wx.BoxSizer(wx.VERTICAL)
self.full_window_sizer.Add(self.create_top_panel(), 0, wx.EXPAND | wx.ALL, 20)
self.full_window_sizer.Add(self.create_bottom_panel(), 0, wx.EXPAND, 0)
self.full_window.SetSizer(self.full_window_sizer)
self.Show()
def create_bottom_panel(self):
bottom_panel = wx.Panel(self.full_window)
bottom_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.login_list = wx.ListCtrl(bottom_panel, style=wx.LC_REPORT)
self.login_list.Bind(wx.EVT_RIGHT_UP, self.show_popup)
self.login_list.InsertColumn(0, 'username', width=200)
self.login_list.InsertColumn(1, 'password', width=200)
bottom_panel_sizer.Add(self.login_list, 0, wx.EXPAND | wx.ALL, 200)
return bottom_panel
def create_top_panel(self):
top_panel = wx.Panel(self.full_window)
top_panel_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.login_amount = wx.TextCtrl(top_panel)
self.login_complex = wx.CheckBox(top_panel, label="complex")
self.login_create = wx.Button(top_panel, label="Create")
self.login_create.Bind(wx.EVT_BUTTON, self.submit_request)
top_panel_sizer.Add(self.login_amount, 1, wx.EXPAND|wx.ALL,0)
top_panel_sizer.Add(self.login_complex, 1, wx.EXPAND|wx.ALL,0)
top_panel_sizer.Add(self.login_create, 1, wx.EXPAND|wx.ALL,0)
top_panel.SetSizer(top_panel_sizer)
return top_panel
def show_popup(self, event):
menu = wx.Menu()
menu.Append(1, "Copy selected items")
menu.Bind(wx.EVT_MENU, self.copy_items, id=1)
self.PopupMenu(menu)
def copy_items(self, event):
selected_items = []
for i in range(self.login_list.GetItemCount()):
if self.login_list.IsSelected(i):
username = selected_items.append(
self.login_list.GetItem(i, 0).GetText()
)
password = selected_items.append(
self.login_list.GetItem(i, 1).GetText()
)
clipdata = wx.TextDataObject()
clipdata.SetText("\n".join(selected_items))
wx.TheClipboard.Open()
wx.TheClipboard.SetData(clipdata)
wx.TheClipboard.Close()
def submit_request(self, event):
amount = self.login_amount.GetValue()
complex_bool = self.login_complex.GetValue()
try:
amount = int(amount)
except:
amount = 1
self.controller.get_new_logins(amount, complex_bool)
def update_logins(self, login):
username, password = login
index = self.login_list.InsertItem(0, username)
self.login_list.SetItem(index, 1, password)
class Controller(object):
def __init__(self):
self.app = wx.App()
self.view = View(self)
self.model = Model(self)
def get_new_logins(self, amount, complex_bool):
self.model.generate_login(amount, complex_bool)
def set_new_logins(self, logins):
self.view.update_logins(logins)
class Model(threading.Thread):
def __init__(self, controller):
threading.Thread.__init__(self, target=self.main)
self.controller = controller
self.queue = queue.Queue()
self.start()
def main(self):
while True:
amount, complex_bool = self.queue.get()
username = login_generator.generate_username()
password = login_generator.generate_password(12, complex_bool)
logins = (username, password)
self.controller.set_new_logins(logins)
time.sleep(1)
def generate_login(self, amount, complex_bool):
for i in range(0, amount):
self.queue.put((amount, complex_bool))
if __name__ == "__main__":
mvc = Controller()
mvc.app.MainLoop()
```
2021-10-26 14:31:10 +02:00
# Coding challenge - Login generator with GUI
2021-10-26 21:52:08 +02:00
# Coding challenge - Trivial pursuit with GUI