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)
2. the **syntax** of one particular language, in our case Python3
3. the **tools** needed to start programming in our language of choice
Within each of these topics there are *subtopics* but there are not bottomless!
Below is a small overview of how I would subdivide them.
## Concepts
The subtopics behind the concept of programming can be sliced (in no particular order) as follows:
The concept behind these topics are the same in most languages, it's just *how* you write them that is different.
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.
> The syntax of a language defines its surface form.[1] Text-based computer languages are based on sequences of characters,
The quote above is taken shamelessly from [wikipedia](https://en.wikipedia.org/wiki/Syntax_(programming_languages)).
Nano, vim, notepad++ all do a good job of editing plain text files but some make it *easier* than others.
You've noticed that vim colors the code of a shell script no?
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
In order to **run** Python3 code you need the Python3 interpreter.
This is because when you **execute** your script, the interpreter will **read and execute** each line of the text file **line by line**.
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 they should include:
* 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
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**.
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!")
⛑ **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.**
We can use conditional logic to create quite elaborate decision processes.
Let's build a mini text based adventure game.
Granted it's not a *tripple A* game but it will train your `if` and `else` skills plus it will highlight some issues we'll overcome in the next section.
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.
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!
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?
We need some for of *randomness* in our code.
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`.
It allows you to add extra functionality to your program.
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)!**
[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.
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.
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.
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.
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.
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
---
Go back to the Celsius to Farenheit converter and add a while loop to ensure the user put's in only numbers.
# 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.
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.
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).
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!
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`.
Once the new `class` is defined, we can create an **instance** of this class.
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.