├── .gitignore
├── 000 - Python intro
├── README.md
├── base.py
├── base_solution.py
├── import_datetime.py
├── import_faker.py
└── python_vs
│ ├── example.php
│ ├── example.py
│ └── example.rb
├── 100 - Basics
├── README.md
├── exceptions.py
├── exceptions_solutions.py
├── for_else.py
├── python_switch.py
└── while_else.py
├── 200 - Container types introduction
├── README.md
├── count_words.py
├── count_words_solution.py
├── dict_order.py
├── lists_basics.py
├── lists_basics_solution.py
├── measure_words_length.py
├── measure_words_length_solution.py
├── mutable_element_in_immutable_tuple.py
├── set_example.py
├── set_example_solution.py
├── set_order.py
├── slicing.py
├── slicing_solution.py
└── unhashable_elements.py
├── 300 - Comprehentions
├── README.md
├── count_words.py
├── count_words_solution.py
├── list_comprehension.py
├── list_comprehension_solution.py
├── measure_words_length.py
└── measure_words_length_solution.py
├── 400 - Strings and bytes
├── README.md
├── count_words.py
├── count_words_solution.py
├── data
│ ├── easy_text.txt
│ ├── matrix
│ └── regular_text.txt
├── rewrite_order.py
└── rewrite_order_solution.py
├── 450 - Serialization
├── .gitignore
├── README.md
├── json_example.py
├── pickle_example.py
└── pickle_example_ml.py
├── 500 - Python style
├── README.md
├── enum_exercise.py
├── enum_exercise_solution.py
├── if_name_main
│ ├── __init__.py
│ ├── file1.py
│ └── file2.py
└── ugly_module
│ ├── __init__.py
│ └── ugly_exercise.py
├── 510 - Requests
├── README.md
├── json_placeholder.py
├── json_placeholder_solution.py
└── test_json_placeholder.py
├── 525 - Classes 101
├── README.md
├── abstract_classes.py
├── decoder_exercise.py
├── decoder_exercise_solution.py
├── properties.py
├── simple_class.py
└── test_decoder_exercise.py
├── 550 - Testing
├── README.md
├── conftest.py
├── decoder_exercise_solution.py
├── test_decoder_exercise.py
├── test_decoder_exercise_mocking.py
├── test_decoder_exercise_pytest.py
└── test_example.py
├── 600 - Functions
├── README.md
├── decorator_example.py
├── decorator_exercise.py
├── decorator_exercise_solution.py
├── default_arguments.py
├── default_mutable.py
├── function_args.py
├── global_variables.py
├── greetings.py
├── greetings_passing_args_and_kwargs.py
└── nonlocal_variables.py
├── 700 - Generators and Itertools
├── README.md
├── generate_alphabet.py
├── generate_alphabet_solution.py
├── generators.py
├── generators_solution.py
├── two_way_generators.py
└── two_way_generators_solution.py
├── 800 - Classes 202
├── README.md
├── dag.py
├── dag_solution.py
├── descriptors.py
├── duck_typing.py
├── dunders.py
├── heavy_instances.py
└── super_example.py
├── 850 - Metaprogramming
├── README.md
└── meta_decorator.py
├── 850 - os module
└── python_path.py
├── 900 - app
├── README.md
├── app
│ ├── __init__.py
│ ├── config.py
│ ├── database
│ │ ├── __init__.py
│ │ ├── crete_models.py
│ │ └── crete_models_extended.py
│ ├── posts
│ │ ├── __init__.py
│ │ └── models.py
│ └── users
│ │ ├── __init__.py
│ │ ├── accessors.py
│ │ └── models.py
├── requirements.txt
└── tests
│ ├── __init__.py
│ ├── test_app.py
│ └── tmp.py
├── 950 - Personal recomendations
└── README.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | # pyCharm
132 | .idea
133 |
--------------------------------------------------------------------------------
/000 - Python intro/README.md:
--------------------------------------------------------------------------------
1 | # Python introduction
2 |
3 | ## Python version
4 | Check python version in shell.
5 | ```shell
6 | python --version # most often links to deprecated python2 or doesn't exist
7 | python3 --version
8 | python3 -V
9 | ```
10 |
11 | ### Currently used python versions ([source](https://www.python.org/downloads/))
12 |
13 |
14 | Python version |
15 | Maintenance status |
16 | First released |
17 | End of support |
18 | Release schedule |
19 |
20 |
21 | 3.10 |
22 | bugfix |
23 | 2020-10-05 |
24 | 2025-10 |
25 | PEP 619 |
26 |
27 |
28 | 3.9 |
29 | bugfix |
30 | 2020-10-05 |
31 | 2025-10 |
32 | PEP 596 |
33 |
34 |
35 | 3.8 |
36 | security |
37 | 2019-10-14 |
38 | 2024-10 |
39 | PEP 569 |
40 |
41 |
42 | 3.7 |
43 | security |
44 | 2018-06-27 |
45 | 2023-06-27 |
46 | PEP 537 |
47 |
48 |
49 | 2.7 |
50 | end-of-life |
51 | 2010-07-03 |
52 | 2020-01-01 |
53 | PEP 373 |
54 |
55 |
56 |
57 | ## Python vs ...
58 | * [php vs Python][]
59 | * [Ruby vs Python][]
60 |
61 | Let's look at the code.
62 | ```shell
63 | $ ls -1 ./python_vs
64 | $ php ./python_vs/example.php # try some PHP
65 | > ...
66 | $ ruby ./python_vs/example.php # try some Ruby
67 | > ...
68 | $ python3 ./python_vs/example.py # finally try some Python
69 | > ...
70 | ```
71 |
72 | ## Run python code
73 | ### Interactive interpreter
74 | Run interpreter in terminal by typing `python3`
75 |
76 | 1. Define two variables `x = 42` and `y = 2`
77 | 1. Print `x` and `y`
78 | 1. Print `x` `y` times in a loop.
79 |
80 | ### File
81 | Open file [`base.py`](base.py)
82 | 1. Reproduce all steps from previous assignment
83 |
84 | #### Run in terminal
85 | Run in terminal:
86 | ```shell
87 | python3 ./base.py
88 | ```
89 | Now run files: [`import_datetime.py`](import_datetime.py) and [`import_faker.py`](import_faker.py).
90 |
91 | ### External dependencies
92 | Let's use 3rd party [Faker][].
93 | ```shell
94 | # Create new python environment in '~/.envs/test'
95 | python3 -m venv ~/.envs/training_env
96 | # Activate environment
97 | source ~/.envs/training_env/bin/activate
98 | # Now we can use 'python' instead of 'python3'
99 | which python
100 |
101 | # install dependencies
102 | pip install faker
103 | # check what's installed
104 | pip freeze
105 | # And run code containing 3rd party libraries
106 | python ./import_faker.py
107 | ```
108 |
109 | But, what's [PIP][]?
110 | > The Python Package Index (PyPI) is a repository of software for the Python programming language.
111 |
112 | Kind of like [composer][] (PHP) or [RubyGems][] (Ruby)?
113 |
114 | More info about [Virtual Environments and Packages][]
115 |
116 | And great podcast about [Tools for Setting Up Python on a New Machine][]
117 |
118 | ### Run in pyCharm
119 | [Create and edit run/debug configurations][]
120 |
121 | `Right Click on ` → `Run `
122 |
123 | To set custom environment used by pyCharm go to:
124 | ```
125 | File | Settings | Project: python_course | Python Interpreter
126 | ```
127 | `Right Click on gear` → `Add...` → `Existing environment` → `Path to python executable`
128 |
129 |
130 | [php vs Python]: https://kinsta.com/blog/php-vs-python/
131 | [Ruby vs Python]: https://www.upguard.com/blog/python-vs-ruby
132 | [Faker]: https://github.com/joke2k/faker
133 | [PIP]: https://pypi.org/
134 | [RubyGems]: https://rubygems.org/
135 | [composer]: https://getcomposer.org/
136 | [Virtual Environments and Packages]: https://docs.python.org/3/tutorial/venv.html
137 | [Tools for Setting Up Python on a New Machine]: https://realpython.com/podcasts/rpp/101/
138 | [Create and edit run/debug configurations]: https://www.jetbrains.com/help/pycharm/creating-and-editing-run-debug-configurations.html
139 |
--------------------------------------------------------------------------------
/000 - Python intro/base.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shnela/python_course/842f94c2b0f8d73428919a3c9f397720554c30a7/000 - Python intro/base.py
--------------------------------------------------------------------------------
/000 - Python intro/base_solution.py:
--------------------------------------------------------------------------------
1 | x = 42
2 | y = 2
3 | print(x, y)
4 |
5 | for _ in range(y):
6 | print(x)
7 |
--------------------------------------------------------------------------------
/000 - Python intro/import_datetime.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | print(datetime.now())
4 |
--------------------------------------------------------------------------------
/000 - Python intro/import_faker.py:
--------------------------------------------------------------------------------
1 | from faker import Faker
2 | fake = Faker()
3 |
4 | print(fake.name())
5 |
--------------------------------------------------------------------------------
/000 - Python intro/python_vs/example.php:
--------------------------------------------------------------------------------
1 | 1,
44 | 'sheep' => 5,
45 | ];
46 | print($herd_count['sheep'] . "\n");
47 | $herd_count['dog'] = 1;
48 | $herd_count['sheep'] -= 1;
49 | print($herd_count['dog'] . "\n");
50 | print($herd_count['sheep'] . "\n");
51 |
52 |
53 | function square($x) {
54 | return $x * $x;
55 | }
56 |
57 | print(square(3) . "\n");
58 |
59 | class Dog {
60 | public $fierceness;
61 |
62 | function __construct($fierceness) {
63 | $this->fierceness = $fierceness;
64 | }
65 | function bark() {
66 | print(str_repeat("Woof! ", $this->fierceness) . "\n");
67 | }
68 | }
69 |
70 | (new Dog(2))->bark();
71 |
72 |
73 | class ItalianDog extends Dog {
74 | function bark() {
75 | print(str_repeat("Bau! ", $this->fierceness) . "\n");
76 | }
77 | }
78 |
79 |
80 | (new ItalianDog(2))->bark();
81 |
82 |
83 | function factorial($n) {
84 | if ($n == 0) {
85 | return 1;
86 | } else {
87 | return $n * factorial($n - 1);
88 | }
89 | }
90 |
91 |
92 | print(factorial(1) . "\n");
93 | print(factorial(100) . "\n");
94 | # print(factorial(10000) . "\n");
95 |
96 | ?>
--------------------------------------------------------------------------------
/000 - Python intro/python_vs/example.py:
--------------------------------------------------------------------------------
1 | # Single line comment
2 | """Multi
3 | line
4 | comment
5 | """
6 |
7 | int_val = 1
8 | print(int_val)
9 |
10 | float_val = 3.14
11 | print(float_val)
12 |
13 | text1 = 'dog\'s bone'
14 | text2 = " and cat's one"
15 | print(text1 + text2)
16 |
17 | for i in range(3):
18 | print(str(i) + ' ' + text1)
19 |
20 | counter = 0
21 | while counter < 3:
22 | print(counter)
23 | counter += 1
24 |
25 | if counter == 0:
26 | print('Not even started')
27 | elif counter == 3:
28 | print('Everything is under control')
29 | else:
30 | print("I'm confused.")
31 |
32 | animals = ['cow', 'sheep']
33 | print(animals)
34 | animals.append('dog')
35 | print(animals)
36 |
37 | herd_count = {
38 | 'cow': 1,
39 | 'sheep': 5,
40 | }
41 | print(herd_count)
42 | herd_count['dog'] = 1
43 | herd_count['sheep'] -= 1
44 | print(herd_count)
45 |
46 |
47 | def square(x):
48 | return x ** 2
49 |
50 |
51 | print(square(3))
52 |
53 |
54 | class Dog:
55 | def __init__(self, fierceness):
56 | self.fierceness = fierceness
57 |
58 | def bark(self):
59 | print("Woof! " * self.fierceness)
60 |
61 |
62 | Dog(2).bark()
63 |
64 |
65 | class ItalianDog(Dog):
66 | def bark(self):
67 | print("Bau! " * self.fierceness)
68 |
69 |
70 | ItalianDog(2).bark()
71 |
72 |
73 | def factorial(n):
74 | if n == 0:
75 | return 1
76 | else:
77 | return n * factorial(n - 1)
78 |
79 |
80 | print(factorial(1))
81 | print(factorial(100))
82 | # https://stackoverflow.com/questions/13591970/does-python-optimize-tail-recursion
83 | # https://stackoverflow.com/questions/310974/what-is-tail-call-optimization
84 | # TODO: use some tail recursion optimized algo
85 | # print(factorial(10000))
86 |
--------------------------------------------------------------------------------
/000 - Python intro/python_vs/example.rb:
--------------------------------------------------------------------------------
1 | # Single line comment
2 | =begin Multi
3 | line
4 | comment
5 | =end
6 |
7 | int_val = 1
8 | puts(int_val)
9 |
10 | float_val = 3.14
11 | puts(float_val)
12 |
13 | text1 = 'dog\'s bone'
14 | text2 = " and cat's one"
15 | puts(text1 + text2)
16 |
17 | for i in 0..2
18 | puts(String(i) + ' ' + text1)
19 | end
20 |
21 | counter = 0
22 | while counter < 3
23 | puts(counter)
24 | counter += 1
25 | end
26 |
27 | if counter == 0
28 | puts('Not even started')
29 | elsif counter == 3
30 | puts('Everything is under control')
31 | else
32 | puts("I'm confused.")
33 | end
34 |
35 | animals = %w(cow sheep)
36 | puts(animals)
37 | animals.push('dog')
38 | puts(animals)
39 |
40 | herd_count = {
41 | 'cow' => 1,
42 | 'sheep' => 5,
43 | }
44 | puts(herd_count)
45 | herd_count['dog'] = 1
46 | herd_count['sheep'] -= 1
47 | puts(herd_count)
48 |
49 |
50 | def square(x)
51 | x ** 2
52 | end
53 |
54 | puts(square(3))
55 |
56 |
57 | class Dog
58 | def initialize(fierceness)
59 | @fierceness = fierceness
60 | end
61 |
62 | def bark()
63 | puts("Woof! " * @fierceness)
64 | end
65 | end
66 |
67 |
68 | Dog.new(2).bark()
69 |
70 |
71 | class ItalianDog < Dog
72 | def bark()
73 | puts("Bau! " * @fierceness)
74 | end
75 | end
76 |
77 |
78 | ItalianDog.new(2).bark()
79 |
80 |
81 | def factorial(n)
82 | n == 0 ? 1 : n * factorial(n - 1)
83 | end
84 |
85 |
86 | puts(factorial(1))
87 | puts(factorial(100))
88 | # puts(factorial(10000))
--------------------------------------------------------------------------------
/100 - Basics/README.md:
--------------------------------------------------------------------------------
1 | # Basics
2 |
3 | ## Basic data types
4 | [Basic Data Types in Python][]
5 |
6 | ### Integers
7 | ```python
8 | print(12345)
9 | print(2 ** 100)
10 | # casting
11 | string_val = '12'
12 | integer_val = int(string_val)
13 | ```
14 | Integers have unlimited range. [Python Max Int][]
15 |
16 | #### Base systems
17 | ```python
18 | val_8 = 0o10
19 | val_16 = 0x10
20 | val_2 = 0b10
21 | ```
22 |
23 | ### Floats
24 | ```python
25 | print(12345.)
26 | print(2. ** 100)
27 | # casting
28 | string_val = '.5'
29 | integer_val = float(string_val)
30 | ```
31 |
32 | > Almost all platforms represent Python float values as 64-bit “double-precision” values, according to the IEEE 754
33 | > standard. In that case, the maximum value a floating-point number can have is approximately 1.8e10+308.
34 | > Python will indicate a number greater than that by the string inf:
35 | >
36 | > The closest a nonzero number can be to zero is approximately 5.0e10-324.
37 | > Anything closer to zero than that is effectively zero.
38 |
39 | ### Boolean Type
40 | ```python
41 | assert bool(42) is True
42 | assert bool(0) is False
43 | assert bool('text') is True
44 | assert bool('') is False
45 | assert bool([1, 2, 3]) is True
46 | assert bool([]) is False
47 | ```
48 |
49 | ### Strings
50 | ```python
51 | print('This string contains a single quote (\') character.')
52 | print("This string contains a double quote (\") character.")
53 | print('''This string has
54 | a single (')
55 | and a double (") quote.''')
56 | ```
57 |
58 | ## Formatting output
59 | [Using % and .format() for great good!][] - "old" and "new" way of formatting strings.
60 |
61 | But there's the newest: [Python 3's f-Strings][]
62 | ```python
63 | name = 'Mark'
64 | age = 41
65 | print(f"He's {name}, next year he'll be {age + 1}")
66 | ```
67 |
68 | ### f-string features
69 | #### [Formatted string literals]
70 | ```python
71 | width = 10
72 | precision = 4
73 | value = decimal.Decimal("12.34567")
74 | print(f"result: {value:{width}.{precision}}")
75 | # output: "result: 12.35"
76 | ```
77 | #### [f-strings support = for self-documenting expressions and debugging][]
78 | ```python
79 | user = 'eric_idle'
80 | member_since = date(1975, 7, 31)
81 | print(f'{user=} {member_since=}')
82 | # output: "user='eric_idle' member_since=datetime.date(1975, 7, 31)"
83 |
84 | print(f'{theta=} {cos(radians(theta))=:.3f}')
85 | # output: "theta=30 cos(radians(theta))=0.866"
86 | ```
87 | _new in pytyhon3.8_
88 |
89 | ## Exception handling
90 | ```python
91 | try:
92 | # do something
93 | do_action()
94 | print('great - action done')
95 | except ExceptionI as e:
96 | print('we have problem')
97 | raise
98 | except (ExceptionII, ExceptionIII) as e:
99 | raise OtherException('explanation') from e
100 | else:
101 | print('everything went fine - no problem')
102 | finally:
103 | print('always invoke it, good place for closing files etc')
104 | ```
105 | [exceptions.py](exceptions.py)
106 |
107 | ## Unuseful or missing features
108 | ### Switch statement
109 | Python doesn't have one.
110 |
111 | [python_switch.py](python_switch.py)
112 |
113 | ### for else block
114 | ```python
115 | for i in range(x):
116 | print(i)
117 | if i == 5:
118 | break
119 | else:
120 | print('else block')
121 | ```
122 | [for_else.py](for_else.py)
123 |
124 | ### while else block
125 | ```python
126 | i = 0
127 | while i < x:
128 | print(i)
129 | if i == 5:
130 | break
131 | else:
132 | print('else block')
133 | ```
134 |
135 | [while_else.py](while_else.py)
136 |
137 | [Basic Data Types in Python]: https://realpython.com/python-data-types/
138 | [Python Max Int]: https://www.pythonpool.com/python-max-int/
139 | [Float type and its methods]: https://www.geeksforgeeks.org/python-float-type-and-its-methods/
140 | [Using % and .format() for great good!]: https://pyformat.info/
141 | [Python 3's f-Strings]: https://realpython.com/python-f-strings/
142 | [Formatted string literals]: https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals
143 | [f-strings support = for self-documenting expressions and debugging]: https://docs.python.org/3/whatsnew/3.8.html#f-strings-support-for-self-documenting-expressions-and-debugging
--------------------------------------------------------------------------------
/100 - Basics/exceptions.py:
--------------------------------------------------------------------------------
1 | def get_from_dict(d, key, default=None):
2 | """Implement dict.get using only `[]`"""
3 |
4 |
5 | def list_from_iterator(g):
6 | """Implement equivalent of `list(g)` using next() and StopIteration exception"""
7 |
8 |
9 | if __name__ == '__main__':
10 | d = {'a': 1}
11 | assert get_from_dict(d, 'a') == 1
12 | assert get_from_dict(d, 'b') is None
13 | assert get_from_dict(d, 'b', default='fallback') == 'fallback'
14 |
15 | i = iter(range(10))
16 | next(i)
17 | assert list_from_iterator(i) == [1, 2, 3, 4, 5, 6, 7, 8, 9]
18 |
--------------------------------------------------------------------------------
/100 - Basics/exceptions_solutions.py:
--------------------------------------------------------------------------------
1 | def get_from_dict(d, key, default=None):
2 | """Implement dict.get using only `[]`"""
3 | try:
4 | return d[key]
5 | except KeyError:
6 | return default
7 |
8 |
9 | def list_from_iterator(g):
10 | """Implement equivalent of `list(g)` using next() and StopIteration exception"""
11 | l = list()
12 | while True:
13 | try:
14 | l.append(next(g))
15 | except StopIteration:
16 | return l
17 |
18 |
19 | if __name__ == '__main__':
20 | d = {'a': 1}
21 | assert get_from_dict(d, 'a') == 1
22 | assert get_from_dict(d, 'b') is None
23 | assert get_from_dict(d, 'b', default='fallback') == 'fallback'
24 |
25 | i = iter(range(10))
26 | next(i)
27 | assert list_from_iterator(i) == [1, 2, 3, 4, 5, 6, 7, 8, 9]
--------------------------------------------------------------------------------
/100 - Basics/for_else.py:
--------------------------------------------------------------------------------
1 | """for else blocks are counterintuitive, please do not use them
2 | """
3 |
4 |
5 | def print_range(x: int):
6 | print('Start: ', end=' ')
7 | for i in range(x):
8 | print(i, end=' ')
9 | if i == 5:
10 | break
11 | else:
12 | print('(invoked only when there was no break)', end='')
13 | print()
14 |
15 |
16 | if __name__ == '__main__':
17 | print_range(0)
18 | print_range(2)
19 | print_range(5)
20 | print_range(6)
21 | print_range(10)
22 |
--------------------------------------------------------------------------------
/100 - Basics/python_switch.py:
--------------------------------------------------------------------------------
1 | """There's no such thing as switch statemtnt in python"""
2 |
3 |
4 | def digit_to_text(digit: int) -> str:
5 | if digit == 1:
6 | return 'one'
7 | elif digit == 2:
8 | return 'two'
9 | else:
10 | return 'definitely nor one or two'
11 |
12 |
13 | if __name__ == '__main__':
14 | print(digit_to_text(1))
15 |
--------------------------------------------------------------------------------
/100 - Basics/while_else.py:
--------------------------------------------------------------------------------
1 | """while else blocks are counterintuitive, please do not use them
2 | """
3 |
4 |
5 | def print_range(x: int):
6 | print('Start: ', end=' ')
7 | i = 0
8 | while i < x:
9 | print(i, end=' ')
10 | if i == 5:
11 | break
12 | i += 1
13 | else:
14 | print('While condition is no longer true', end='')
15 | print()
16 |
17 |
18 | if __name__ == '__main__':
19 | print_range(0)
20 | print_range(2)
21 | print_range(5)
22 | print_range(6)
23 | print_range(10)
24 |
--------------------------------------------------------------------------------
/200 - Container types introduction/README.md:
--------------------------------------------------------------------------------
1 | # Container types
2 |
3 | ## List
4 | ```python
5 | # list of only integers
6 | a = [1, 2, 3, 4, 5, 6]
7 | print(a)
8 |
9 | # list of only strings
10 | b = ['hello', 'john', 'reese']
11 | print(b)
12 |
13 | # list of both integers and strings
14 | c = ['hey', 'you', 1, 2, 3, 'go']
15 | print(c)
16 |
17 | # index are 0 based. this will print a single character
18 | print(c[1]) # this will print 'you' in list c
19 |
20 | # constructing list
21 | letters = list('some text')
22 | print(letters)
23 | # list is mutable
24 | letters[0] = 's replacement'
25 | print(letters)
26 | ```
27 |
28 | Let's do [lists_basics.py](./lists_basics.py)
29 |
30 | ### List [Slicing][]
31 | ```python
32 | a[start:stop] # items start through stop-1
33 | a[start:stop:step] # items start through stop-1 with step
34 | a[start:] # items start through the rest of the array
35 | a[:stop] # items from the beginning through stop-1
36 | a[:] # a copy of the whole array
37 |
38 | a[-1] # last item in the array
39 | a[-2:] # last two items in the array
40 | a[:-2] # everything except the last two items
41 |
42 | a[::-1] # all items in the array, reversed
43 | a[1::-1] # the first two items, reversed
44 | a[:-3:-1] # the last two items, reversed
45 | a[-3::-1] # everything except the last two items, reversed
46 | ```
47 | Exercise: [slicing.py](./slicing.py)
48 |
49 | ## Tuple
50 | ```python
51 | # tuple having only integer type of data.
52 | a = (1, 2, 3, 4)
53 | print(a) # prints the whole tuple
54 |
55 | # tuple having multiple types of data.
56 | b = ('hello', 1, 2, 3, 'go')
57 | print(b) # prints the whole tuple
58 |
59 | # index of tuples are also 0 based.
60 | print(b[4]) # this prints a single element in a tuple, in this case 'go'
61 |
62 | # constructing tuple
63 | letters = tuple('some text')
64 | print(letters)
65 | # list is mutable
66 | letters[0] = 's replacement' # Will throw TypeError
67 | ```
68 |
69 | ### [Tuples unpacking](https://www.w3schools.com/python/python_tuples_unpack.asp)
70 |
71 | ```python
72 | fruits = ("apple", "banana", "cherry")
73 | (green, yellow, red) = fruits
74 |
75 | fruits = ("apple", "mango", "papaya", "pineapple", "cherry")
76 | (green, *tropic, red) = fruits
77 | # (green, *tropic, *red) = fruits # SyntaxError: multiple starred expressions in assignment
78 | ```
79 |
80 | ### Other about tuple
81 | [mutable_element_in_immutable_tuple.py](mutable_element_in_immutable_tuple.py)
82 |
83 | ## Dict
84 | ```python
85 | # a sample dictionary variable
86 | a = {1: 'first name', 2: 'last name', 'age': 33}
87 | print(a) # prints the whole dict
88 |
89 | # print value having key=1
90 | print(a[1])
91 | # print value having key=2
92 | print(a[2])
93 | # print value having key='age'
94 | print(a['age'])
95 | ```
96 |
97 | ### Accessing dict elements
98 | ```python
99 | d = {
100 | 'one': 1,
101 | }
102 | assert d['one'] == 1
103 | # d['three'] # KeyError: 'three'
104 | assert d.get('one') == 1
105 | assert d.get('three') is None
106 | assert d.get('three', 'fallback val') == 'fallback val'
107 | ```
108 |
109 | ### Modifying dict and checking if there are elements
110 | ```python
111 | d = {
112 | 'one': 1,
113 | }
114 | assert 'two' not in d
115 | d['two'] = 2
116 | assert 'two' in d
117 | ```
118 |
119 | ### Iterating over dict
120 | ```python
121 | d = {
122 | 'one': 1,
123 | 'two': 2,
124 | }
125 | assert list(d) == list(d.keys()) == ['one', 'two']
126 | assert list(d.values()) == [1, 2]
127 | assert list(d.items()) == [('one', 1), ('two', 2)]
128 |
129 | # Tuple unpacking is useful
130 | for k, v in d.values():
131 | print(k, v)
132 | ```
133 |
134 | ### Exercises:
135 | * [measure_words_length.py](measure_words_length.py)
136 | * [count_words.py](count_words.py)
137 |
138 | ### [dictionaries ordered since Python 3.6+][]
139 | > They are insertion ordered. As of Python 3.6, for the CPython implementation of Python,
140 | > dictionaries remember the order of items inserted.
141 | > This is considered an implementation detail in Python 3.6; you need to use OrderedDict if you want insertion
142 | > ordering that's guaranteed across other implementations of Python (and other ordered behavior).
143 | >
144 | > As of Python 3.7, this is no longer an implementation detail and instead becomes a language feature.
145 |
146 | Look at [dict_order.py](./dict_order.py).
147 |
148 |
149 | ## [Set][]
150 | ```python
151 | one_set = set()
152 | one_set.add(1)
153 | one_set.add(1)
154 | print(one_set)
155 | # {1}
156 |
157 | initialized_set = {1, 2, 1}
158 | print(initialized_set)
159 | # {1, 2}
160 |
161 | assert one_set < initialized_set # one_set is subset of initialized_set
162 | assert initialized_set - one_set == {2}
163 | assert initialized_set & one_set == {1}
164 | assert initialized_set | one_set == {1, 2}
165 | ```
166 |
167 | ### But set isn't ordered
168 | [`set_order.py`](set_order.py)
169 |
170 | ### Exercise
171 | [`set_example.py`](set_example.py)
172 |
173 | ## Hashable / unhashable
174 | All set items and dictionary keys must be hashable.
175 | [`unhashable_elements.py`](unhashable_elements.py)
176 |
177 | [dictionaries ordered since Python 3.6+]: https://stackoverflow.com/a/39980744/1565454
178 | [Slicing]: https://stackoverflow.com/a/509295/1565454
179 | [Set]: https://docs.python.org/3.8/library/stdtypes.html#set-types-set-frozenset
180 |
--------------------------------------------------------------------------------
/200 - Container types introduction/count_words.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 |
4 | def remove_non_alpha_characters(text):
5 | regex = re.compile('[^a-zA-Z $]')
6 | return regex.sub('', text)
7 |
8 |
9 | def count_words(text):
10 | return dict()
11 |
12 |
13 | if __name__ == '__main__':
14 | assert "there will be code".split() == ['there', 'will', 'be', 'code']
15 | assert "there-will-be-code".split('-') == ['there', 'will', 'be', 'code']
16 |
17 | small_dict = {
18 | 'Alice': 1,
19 | }
20 | assert 'Alice' in small_dict
21 | assert 'Bob' not in small_dict
22 | small_dict['Bob'] = 0
23 | assert small_dict['Bob'] == 0
24 |
25 | # Exercise 1: make it pass
26 | easy_text = 'a small text with a few repetitions'
27 | assert count_words(easy_text) == {'a': 2, 'small': 1, 'text': 1, 'with': 1, 'few': 1, 'repetitions': 1}
28 |
29 | # Exercise 2: make it pass
30 | assert remove_non_alpha_characters('Remove non alpha!') == 'Remove non alpha'
31 | assert 'Capitalized'.lower() == 'capitalized'
32 | regular_text = """ALICE was beginning to get very tired of sitting by her sister on the bank and of having nothing
33 | to do: once or twice she had peeped into the book her sister was reading, but it had no pictures
34 | or conversations in it, "and what is the use of a book," thought Alice, "without pictures or conversations?'
35 | So she was considering, in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid),
36 | whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies,
37 | when suddenly a White Rabbit with pink eyes ran close by her."""
38 | alice_words = count_words(regular_text)
39 | assert alice_words['was'] == 3
40 | assert alice_words['of'] == 5
41 | assert 'Alice' not in alice_words
42 | assert alice_words['alice'] == 2
43 |
44 | # Exercise 3: use defaultdict in count_words: https://stackoverflow.com/a/5900628/1565454
45 |
--------------------------------------------------------------------------------
/200 - Container types introduction/count_words_solution.py:
--------------------------------------------------------------------------------
1 | import re
2 | from collections import defaultdict
3 |
4 |
5 | def remove_non_alpha_characters(text):
6 | regex = re.compile('[^a-zA-Z $]')
7 | return regex.sub('', text)
8 |
9 |
10 | def count_words(text):
11 | word_counts = defaultdict(int)
12 | raw_words = remove_non_alpha_characters(text).lower().split()
13 | for word in raw_words:
14 | word_counts[word] += 1
15 | return word_counts
16 |
17 |
18 | if __name__ == '__main__':
19 | assert "there will be code".split() == ['there', 'will', 'be', 'code']
20 | assert "there-will-be-code".split('-') == ['there', 'will', 'be', 'code']
21 |
22 | small_dict = {
23 | 'Alice': 1,
24 | }
25 | assert 'Alice' in small_dict
26 | assert 'Bob' not in small_dict
27 | small_dict['Bob'] = 0
28 | assert small_dict['Bob'] == 0
29 |
30 | # Exercise 1: make it pass
31 | easy_text = 'a small text with a few repetitions'
32 | assert count_words(easy_text) == {'a': 2, 'small': 1, 'text': 1, 'with': 1, 'few': 1, 'repetitions': 1}
33 |
34 | # Exercise 2: make it pass
35 | assert remove_non_alpha_characters('Remove non alpha!') == 'Remove non alpha'
36 | assert 'Capitalized'.lower() == 'capitalized'
37 | regular_text = """ALICE was beginning to get very tired of sitting by her sister on the bank and of having nothing
38 | to do: once or twice she had peeped into the book her sister was reading, but it had no pictures
39 | or conversations in it, "and what is the use of a book," thought Alice, "without pictures or conversations?'
40 | So she was considering, in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid),
41 | whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies,
42 | when suddenly a White Rabbit with pink eyes ran close by her."""
43 | alice_words = count_words(regular_text)
44 | assert alice_words['was'] == 3
45 | assert alice_words['of'] == 5
46 | assert 'Alice' not in alice_words
47 | assert alice_words['alice'] == 2
48 |
49 | # Exercise 3: use defaultdict in count_words: https://stackoverflow.com/a/5900628/1565454
50 |
--------------------------------------------------------------------------------
/200 - Container types introduction/dict_order.py:
--------------------------------------------------------------------------------
1 | def test_order_of_accessing_dict_data():
2 | """
3 | ord('a') == 97
4 | chr(97) == 'a'
5 | :return:
6 | """
7 | letters = dict()
8 | for ascii_no in range(ord('A'), ord('z') + 1):
9 | letters[chr(ascii_no)] = ascii_no
10 |
11 | for letter, ascii_no in letters.items():
12 | print(letter, ascii_no)
13 |
14 |
15 | if __name__ == '__main__':
16 | test_order_of_accessing_dict_data()
17 |
--------------------------------------------------------------------------------
/200 - Container types introduction/lists_basics.py:
--------------------------------------------------------------------------------
1 | from random import randint
2 |
3 |
4 | def print_elements(elements):
5 | print(f'List len: {len(elements)}')
6 | for e in elements:
7 | print(f'* {e}')
8 |
9 |
10 | def sum_elements(elements):
11 | """Return 0 if list is empty"""
12 |
13 |
14 | def min_element(elements):
15 | """Return None if list is empty"""
16 |
17 |
18 | def avg_of_elements(elements):
19 | pass
20 |
21 |
22 | if __name__ == '__main__':
23 | numbers = [randint(-5, 5) for _ in range(4)]
24 | print_elements(numbers)
25 |
26 | sum_val = sum_elements(numbers)
27 | print(f'SUM: {sum_val}')
28 | assert sum_val == sum(numbers)
29 |
30 | min_val = min_element(numbers)
31 | print(f'MIN: {min_val}')
32 | assert min_val == min(numbers)
33 |
34 | avg_val = avg_of_elements(numbers)
35 | print(f'AVG: {avg_val}')
36 | assert avg_val == sum(numbers) / len(numbers)
37 |
38 | # test empty sequences
39 | assert sum_elements([]) == 0
40 | assert min_element([]) is None
41 |
--------------------------------------------------------------------------------
/200 - Container types introduction/lists_basics_solution.py:
--------------------------------------------------------------------------------
1 | from random import randint
2 |
3 |
4 | def print_elements(elements):
5 | print(f'List len: {len(elements)}')
6 | for e in elements:
7 | print(f'* {e}')
8 |
9 |
10 | def sum_elements(elements):
11 | acc_sum = 0
12 | for e in elements:
13 | acc_sum += e
14 | return acc_sum
15 |
16 |
17 | def min_element(elements):
18 | if not elements:
19 | return None
20 | min_val = elements[0]
21 | for e in elements:
22 | min_val = e if e < min_val else min_val
23 | return min_val
24 |
25 |
26 | def avg_of_elements(elements):
27 | return sum_elements(elements) / len(elements)
28 |
29 |
30 | if __name__ == '__main__':
31 | numbers = [randint(-5, 5) for _ in range(4)]
32 | print_elements(numbers)
33 |
34 | sum_val = sum_elements(numbers)
35 | print(f'SUM: {sum_val}')
36 | assert sum_val == sum(numbers)
37 |
38 | min_val = min_element(numbers)
39 | print(f'MIN: {min_val}')
40 | assert min_val == min(numbers)
41 |
42 | avg_val = avg_of_elements(numbers)
43 | print(f'AVG: {avg_val}')
44 | assert avg_val == sum(numbers) / len(numbers)
45 |
46 | # test empty sequences
47 | assert sum_elements([]) == 0
48 | assert min_element([]) is None
49 |
--------------------------------------------------------------------------------
/200 - Container types introduction/measure_words_length.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, List
2 |
3 |
4 | def measure_lengths(words: List[str]) -> Dict[str, int]:
5 | return dict()
6 |
7 |
8 | def filter_words_with_even_length(words_with_len: Dict[str, int]) -> Dict[str, int]:
9 | return words_with_len
10 |
11 |
12 | def filter_words_with_prefix(words_with_len: Dict[str, int], required_prefix: str) -> Dict[str, int]:
13 | return words_with_len
14 |
15 |
16 | if __name__ == '__main__':
17 | animals = [
18 | 'Brown Recluse Spider',
19 | 'Camels',
20 | 'Cape Gannet Bird',
21 | 'Chickens',
22 | 'Chimpanzee',
23 | 'Cuviers Dwarf Caimans',
24 | 'Dog',
25 | ]
26 |
27 | # Exercise 1: make it pass
28 | words_with_len = measure_lengths(animals)
29 | assert words_with_len == {
30 | 'Brown Recluse Spider': 20, 'Camels': 6, 'Cape Gannet Bird': 16, 'Chickens': 8, 'Chimpanzee': 10,
31 | 'Cuviers Dwarf Caimans': 21, 'Dog': 3
32 | }
33 |
34 | # Exercise 2: make it pass
35 | words_with_even_len = filter_words_with_even_length(words_with_len)
36 | assert words_with_even_len == {
37 | 'Brown Recluse Spider': 20, 'Camels': 6, 'Cape Gannet Bird': 16, 'Chickens': 8, 'Chimpanzee': 10
38 | }
39 |
40 | # Exercise 3: make it pass
41 | assert 'Confluence'.startswith('Conf')
42 | words_with_prefix1 = filter_words_with_prefix(words_with_len, required_prefix='C')
43 | assert words_with_prefix1 == {
44 | 'Camels': 6, 'Cape Gannet Bird': 16, 'Chickens': 8, 'Chimpanzee': 10, 'Cuviers Dwarf Caimans': 21
45 | }
46 | words_with_prefix2 = filter_words_with_prefix(words_with_len, required_prefix='Ch')
47 | assert words_with_prefix2 == {
48 | 'Chickens': 8, 'Chimpanzee': 10,
49 | }
50 |
--------------------------------------------------------------------------------
/200 - Container types introduction/measure_words_length_solution.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, List
2 |
3 |
4 | def measure_lengths(words: List[str]) -> Dict[str, int]:
5 | words_with_len = dict()
6 | for word in words:
7 | words_with_len[word] = len(word)
8 | return words_with_len
9 |
10 |
11 | def filter_words_with_even_length(words_with_len: Dict[str, int]) -> Dict[str, int]:
12 | words_with_even_len = dict()
13 | for word, word_len in words_with_len.items():
14 | if not word_len % 2:
15 | words_with_even_len[word] = word_len
16 | return words_with_even_len
17 |
18 |
19 | def filter_words_with_prefix(words_with_len: Dict[str, int], required_prefix: str) -> Dict[str, int]:
20 | words_with_even_len = dict()
21 | for word, word_len in words_with_len.items():
22 | if word.startswith(required_prefix):
23 | words_with_even_len[word] = word_len
24 | return words_with_even_len
25 |
26 |
27 | if __name__ == '__main__':
28 | animals = [
29 | 'Brown Recluse Spider',
30 | 'Camels',
31 | 'Cape Gannet Bird',
32 | 'Chickens',
33 | 'Chimpanzee',
34 | 'Cuviers Dwarf Caimans',
35 | 'Dog',
36 | ]
37 |
38 | # Exercise 1: make it pass
39 | words_with_len = measure_lengths(animals)
40 | assert words_with_len == {
41 | 'Brown Recluse Spider': 20, 'Camels': 6, 'Cape Gannet Bird': 16, 'Chickens': 8, 'Chimpanzee': 10,
42 | 'Cuviers Dwarf Caimans': 21, 'Dog': 3
43 | }
44 |
45 | # Exercise 2: make it pass
46 | words_with_even_len = filter_words_with_even_length(words_with_len)
47 | assert words_with_even_len == {
48 | 'Brown Recluse Spider': 20, 'Camels': 6, 'Cape Gannet Bird': 16, 'Chickens': 8, 'Chimpanzee': 10
49 | }
50 |
51 | # Exercise 3: make it pass
52 | assert 'Confluence'.startswith('Conf')
53 | words_with_prefix = filter_words_with_prefix(words_with_len, required_prefix='C')
54 | assert words_with_prefix == {
55 | 'Camels': 6, 'Cape Gannet Bird': 16, 'Chickens': 8, 'Chimpanzee': 10, 'Cuviers Dwarf Caimans': 21
56 | }
57 | words_with_prefix2 = filter_words_with_prefix(words_with_len, required_prefix='Ch')
58 | assert words_with_prefix2 == {
59 | 'Chickens': 8, 'Chimpanzee': 10,
60 | }
61 |
--------------------------------------------------------------------------------
/200 - Container types introduction/mutable_element_in_immutable_tuple.py:
--------------------------------------------------------------------------------
1 | if __name__ == '__main__':
2 | # give tuple
3 | t = (1, 'a', [])
4 |
5 | # you can't explicitly modify it's elements
6 | # t[0] += 1 # TypeError: 'tuple' object does not support item assignment
7 | # t[2] += [1] # TypeError: 'tuple' object does not support item assignment
8 |
9 | # ...but
10 | t[2].append(1) # works...
11 | print(t)
12 |
13 | """"Bonus from Fluent Python by Luciano_Ramalho
14 | Corner case in console:
15 |
16 | >>> t = (1, 'a', [])
17 | >>> t[2] += [1]
18 | Traceback (most recent call last):
19 | File "", line 1, in
20 | TypeError: 'tuple' object does not support item assignment
21 | >>> t
22 | (1, 'a', [1])
23 | >>> import dis
24 | >>> dis.dis('t[2] += [1]')
25 | 1 0 LOAD_NAME 0 (t)
26 | 2 LOAD_CONST 0 (2)
27 | 4 DUP_TOP_TWO
28 | 6 BINARY_SUBSCR
29 | 8 LOAD_CONST 1 (1)
30 | 10 BUILD_LIST 1
31 | 12 INPLACE_ADD
32 | 14 ROT_THREE
33 | 16 STORE_SUBSCR
34 | 18 LOAD_CONST 2 (None)
35 | 20 RETURN_VALUE
36 |
37 | # https://docs.python.org/3/library/dis.html#python-bytecode-instructions
38 | """
--------------------------------------------------------------------------------
/200 - Container types introduction/set_example.py:
--------------------------------------------------------------------------------
1 | from typing import List, Tuple
2 |
3 |
4 | def detect_new_and_deleted_entries(prev_elements, current_elements) -> Tuple[List, List]:
5 | """Returns tuple of two lists: (, )"""
6 | return list(), list()
7 |
8 |
9 | def detect_non_modified_elements(prev_elements, current_elements) -> List:
10 | """Returns list of elements common for both `prev_elements` and `current_elements`"""
11 | return list()
12 |
13 |
14 | if __name__ == '__main__':
15 | old_values = {'Alice', 'Bob to Del', 'Chris'}
16 | new_values = {'Alice', 'Chris', 'Dave New'}
17 |
18 | # Exercise 1: make it pass
19 | assert detect_new_and_deleted_entries(old_values, new_values) == (['Dave New'], ['Bob to Del'])
20 |
21 | # Exercise 2:
22 | assert set(detect_non_modified_elements(old_values, new_values)) == {'Alice', 'Chris'}
23 | # we can't be sure of order of elements
24 | # assert detect_non_modified_elements(old_values, new_values) == ['Alice', 'Chris']
25 |
26 | # Exercise 3: make it pass when list was passed instead of set
27 | assert detect_new_and_deleted_entries(list(old_values), list(new_values)) == (['Dave New'], ['Bob to Del'])
28 | assert set(detect_non_modified_elements(list(old_values), list(new_values))) == {'Alice', 'Chris'}
29 |
--------------------------------------------------------------------------------
/200 - Container types introduction/set_example_solution.py:
--------------------------------------------------------------------------------
1 | from typing import List, Tuple
2 |
3 |
4 | def detect_new_and_deleted_entries(prev_elements, current_elements) -> Tuple[List, List]:
5 | """Returns tuple of two lists: (, )"""
6 | prev_elements, current_elements = set(prev_elements), set(current_elements)
7 | new_elements = current_elements - prev_elements
8 | deleted_elements = prev_elements - current_elements
9 | return list(new_elements), list(deleted_elements)
10 |
11 |
12 | def detect_non_modified_elements(prev_elements, current_elements) -> List:
13 | """Returns list of elements common for both `prev_elements` and `current_elements`"""
14 | return list(set(prev_elements) & set(current_elements))
15 |
16 |
17 | if __name__ == '__main__':
18 | old_values = {'Alice', 'Bob to Del', 'Chris'}
19 | new_values = {'Alice', 'Chris', 'Dave New'}
20 |
21 | # Exercise 1: make it pass
22 | assert detect_new_and_deleted_entries(old_values, new_values) == (['Dave New'], ['Bob to Del'])
23 |
24 | # Exercise 2:
25 | assert set(detect_non_modified_elements(old_values, new_values)) == {'Alice', 'Chris'}
26 | # we can't be sure of order of elements
27 | # assert detect_non_modified_elements(old_values, new_values) == ['Alice', 'Chris']
28 |
29 | # Exercise 3: make it pass when list was passed instead of set
30 | assert detect_new_and_deleted_entries(list(old_values), list(new_values)) == (['Dave New'], ['Bob to Del'])
31 | assert set(detect_non_modified_elements(list(old_values), list(new_values))) == {'Alice', 'Chris'}
32 |
--------------------------------------------------------------------------------
/200 - Container types introduction/set_order.py:
--------------------------------------------------------------------------------
1 | def test_order_of_accessing_set_data():
2 | """
3 | ord('a') == 97
4 | chr(97) == 'a'
5 | :return:
6 | """
7 | letters = set()
8 | for ascii_no in range(ord('A'), ord('z') + 1):
9 | letters.add(chr(ascii_no))
10 |
11 | for letter in letters:
12 | print(letter)
13 |
14 |
15 | if __name__ == '__main__':
16 | test_order_of_accessing_set_data()
17 |
--------------------------------------------------------------------------------
/200 - Container types introduction/slicing.py:
--------------------------------------------------------------------------------
1 | def print_indexes(text):
2 | position_width = 3
3 | for i in range(len(text)):
4 | print(f'{i:{position_width}}', end=' ')
5 | print()
6 | for i in range(len(text)):
7 | negative_index = -(len(text) - i)
8 | print(f'{negative_index:{position_width}}', end=' ')
9 | print()
10 | text_without_spaces = text.replace(' ', '_')
11 | for l in text_without_spaces:
12 | print(f'{l:>{position_width}}', end=' ')
13 | print()
14 |
15 |
16 | if __name__ == '__main__':
17 | text = "There was an old dog."
18 | """python
19 | list(text)
20 | # ['T', 'h', 'e', 'r', 'e', ' ', 'w', 'a', 's', ' ', 'a', 'n', ' ', 'o', 'l', 'd', ' ', 'd', 'o', 'g', '.']
21 | '-'.join(list(text))
22 | # 'T-h-e-r-e- -w-a-s- -a-n- -o-l-d- -d-o-g-.'
23 | """
24 | print_indexes(text)
25 |
26 | assert text[0] == 'T'
27 | assert text[-2] == 'g'
28 | assert text[13:16] == 'old'
29 | assert text[-8:-5] == 'old'
30 | assert text[-8:16] == 'old' # it works, but doesn't look good
31 | assert text[::-1] == '.god dlo na saw erehT'
32 |
33 | step_by_2 = 'Level'[::2]
34 | assert step_by_2 == 'Lvl'
35 |
36 | one_two_three = [1, 2, 3]
37 | assert one_two_three[1] == 2
38 | assert one_two_three[1:2] == [2]
39 | one_two_three[1:2] = [10, 20, 30]
40 | assert one_two_three == [1, 10, 20, 30, 3]
41 | assert text[-8:16] == 'old' # it works, but doesn't look good
42 | assert text[::-1] == '.god dlo na saw erehT'
43 |
44 | reverse_level = 'Level'[:] # Exercise
45 | assert reverse_level == 'leveL'
46 |
47 | # replace '.' for '!' in the ned of the string
48 | with_bang = list(text)
49 | # with_bang[] = # Exercise
50 | assert ''.join(with_bang) == "There was an old dog!"
51 |
52 | # replace part of array, so joined string will contain 'cat' instead of a 'dog'
53 | with_cat = list(text)
54 | # with_cat[] = # Exercise
55 | assert ''.join(with_cat) == "There was an old cat."
56 |
57 | numbers = list(range(10))
58 | even_numbers = numbers[:] # Exercise
59 | assert even_numbers == [0, 2, 4, 6, 8]
60 |
61 | even_numbers = numbers[:] # Exercise
62 | assert even_numbers == [1, 3, 5, 7, 9]
63 |
--------------------------------------------------------------------------------
/200 - Container types introduction/slicing_solution.py:
--------------------------------------------------------------------------------
1 | def print_indexes(text):
2 | position_width = 3
3 | for i in range(len(text)):
4 | print(f'{i:{position_width}}', end=' ')
5 | print()
6 | for i in range(len(text)):
7 | negative_index = -(len(text) - i)
8 | print(f'{negative_index:{position_width}}', end=' ')
9 | print()
10 | text_without_spaces = text.replace(' ', '_')
11 | for l in text_without_spaces:
12 | print(f'{l:>{position_width}}', end=' ')
13 | print()
14 |
15 |
16 | if __name__ == '__main__':
17 | text = "There was an old dog."
18 | """python
19 | list(text)
20 | # ['T', 'h', 'e', 'r', 'e', ' ', 'w', 'a', 's', ' ', 'a', 'n', ' ', 'o', 'l', 'd', ' ', 'd', 'o', 'g', '.']
21 | '-'.join(list(text))
22 | # 'T-h-e-r-e- -w-a-s- -a-n- -o-l-d- -d-o-g-.'
23 | """
24 | print_indexes(text)
25 |
26 | assert text[0] == 'T'
27 | assert text[-2] == 'g'
28 | assert text[13:16] == 'old'
29 | assert text[-8:-5] == 'old'
30 | assert text[-8:16] == 'old' # it works, but doesn't look good
31 | assert text[::-1] == '.god dlo na saw erehT'
32 |
33 | reverse_level = 'Level'[::-1]
34 | assert reverse_level == 'leveL'
35 |
36 | # replace '.' for '!' in the ned of the string
37 | with_bang = list(text)
38 | with_bang[-1] = '!'
39 | assert ''.join(with_bang) == "There was an old dog!"
40 |
41 | # replace part of array, so joined string will contain 'cat' instead of a 'dog'
42 | with_cat = list(text)
43 | with_cat[-4:-1] = 'cat'
44 | assert ''.join(with_cat) == "There was an old cat."
45 |
46 | numbers = list(range(10))
47 | even_numbers = numbers[::2] # Exercise
48 | assert even_numbers == [0, 2, 4, 6, 8]
49 |
50 | even_numbers = numbers[1::2] # Exercise
51 | assert even_numbers == [1, 3, 5, 7, 9]
52 |
--------------------------------------------------------------------------------
/200 - Container types introduction/unhashable_elements.py:
--------------------------------------------------------------------------------
1 | integer = 1
2 | string = 'some text'
3 | our_tuple = (integer, string)
4 | our_list = [integer, string]
5 | our_dict = {string: integer}
6 | our_set = {string, integer}
7 | our_frozenset = frozenset({integer, string})
8 |
9 |
10 | if __name__ == '__main__':
11 | print(f'{hash(integer) = }')
12 | print(f'{hash(string) = }')
13 | print(f'{hash(our_tuple) = }')
14 | # print(f'{hash(our_list) = }') # TypeError: unhashable type: 'list'
15 | # print(f'{hash(our_dict) = }') # TypeError: unhashable type: 'dict'
16 | # print(f'{hash(our_set) = }' # TypeError: unhashable type: 'set'
17 | print(f'{hash(our_frozenset) = }')
18 |
19 | # ...but
20 | # our_frozenset.add('other val') # AttributeError: 'frozenset' object has no attribute 'add'
21 |
--------------------------------------------------------------------------------
/300 - Comprehentions/README.md:
--------------------------------------------------------------------------------
1 | # [List Comprehension][]
2 |
3 | ## Example
4 | ```python
5 | fruits = ["apple", "banana", "cherry", "kiwi", "mango"]
6 | fruits_with_a = [f for f in fruits if "a" in f]
7 | assert fruits_with_a == ['apple', 'banana', 'mango']
8 | ```
9 |
10 | ## Syntax
11 | ```
12 | newlist = [expression for item in iterable if condition == True]
13 | ```
14 |
15 | ## Other comprehensions
16 |
17 | ### Dict
18 | ```python
19 | fruits = ["apple", "banana", "cherry", "kiwi", "mango"]
20 | fruits_with_a = {f: len(f) for f in fruits if "a" in f}
21 | assert fruits_with_a == {'apple': 5, 'banana': 6, 'mango': 5}
22 | ```
23 |
24 | ### Set
25 | ```python
26 | fruits = ["apple", "banana", "cherry", "kiwi", "mango"]
27 | fruits_with_a = {f for f in fruits if "a" in f}
28 | assert fruits_with_a == {'apple', 'banana', 'mango'}
29 | ```
30 |
31 | ### Generator
32 | ```python
33 | fruits = ["apple", "banana", "cherry", "kiwi", "mango"]
34 | fruits_with_a = (f for f in fruits if "a" in f)
35 | assert list(fruits_with_a) == ['apple', 'banana', 'mango']
36 | ```
37 |
38 | ## Constructing collections
39 |
40 | | Object type | Constructor | | Comprehension |
41 | |-------------|-------------|------|----------------------------------|
42 | | list | `list()` | `[]` | `[e for e in range(10)]` |
43 | | dict | `dict()` | `{}` | `{e: len(e) for e in range(10)}` |
44 | | set | `set()` | | `{e for e in range(10)}` |
45 | | tuple | `tuple()` | `()` | |
46 | | generator | | | `(e for e in range(10))` |
47 |
48 |
49 | ## Exercises
50 | * [measure_words_length.py](measure_words_length.py)
51 |
52 | [List Comprehension]: https://www.w3schools.com/python/python_lists_comprehension.asp
53 |
--------------------------------------------------------------------------------
/300 - Comprehentions/count_words.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 |
4 | def remove_non_alpha_characters(text):
5 | regex = re.compile('[^a-zA-Z $]')
6 | return regex.sub('', text)
7 |
8 |
9 | def count_words(text):
10 | return dict()
11 |
12 |
13 | if __name__ == '__main__':
14 | assert "there will be code".split() == ['there', 'will', 'be', 'code']
15 | assert "there-will-be-code".split('-') == ['there', 'will', 'be', 'code']
16 |
17 | small_dict = {
18 | 'Alice': 1,
19 | }
20 | assert 'Alice' in small_dict
21 | assert 'Bob' not in small_dict
22 | small_dict['Bob'] = 0
23 | assert small_dict['Bob'] == 0
24 |
25 | # Exercise 1: make it pass
26 | easy_text = 'a small text with a few repetitions'
27 | assert count_words(easy_text) == {'a': 2, 'small': 1, 'text': 1, 'with': 1, 'few': 1, 'repetitions': 1}
28 |
29 | # Exercise 2: make it pass
30 | assert remove_non_alpha_characters('Remove non alpha!') == 'Remove non alpha'
31 | assert 'Capitalized'.lower() == 'capitalized'
32 | regular_text = """ALICE was beginning to get very tired of sitting by her sister on the bank and of having nothing
33 | to do: once or twice she had peeped into the book her sister was reading, but it had no pictures
34 | or conversations in it, "and what is the use of a book," thought Alice, "without pictures or conversations?'
35 | So she was considering, in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid),
36 | whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies,
37 | when suddenly a White Rabbit with pink eyes ran close by her."""
38 | alice_words = count_words(regular_text)
39 | assert alice_words['was'] == 3
40 | assert alice_words['of'] == 5
41 | assert 'Alice' not in alice_words
42 | assert alice_words['alice'] == 2
43 |
44 | # Exercise 3: use defaultdict in count_words: https://stackoverflow.com/a/5900628/1565454
45 |
--------------------------------------------------------------------------------
/300 - Comprehentions/count_words_solution.py:
--------------------------------------------------------------------------------
1 | import re
2 | from collections import defaultdict
3 |
4 |
5 | def remove_non_alpha_characters(text):
6 | regex = re.compile('[^a-zA-Z $]')
7 | return regex.sub('', text)
8 |
9 |
10 | def count_words(text):
11 | word_counts = defaultdict(int)
12 | raw_words = remove_non_alpha_characters(text).lower().split()
13 | for word in raw_words:
14 | word_counts[word] += 1
15 | return word_counts
16 |
17 |
18 | if __name__ == '__main__':
19 | assert "there will be code".split() == ['there', 'will', 'be', 'code']
20 | assert "there-will-be-code".split('-') == ['there', 'will', 'be', 'code']
21 |
22 | small_dict = {
23 | 'Alice': 1,
24 | }
25 | assert 'Alice' in small_dict
26 | assert 'Bob' not in small_dict
27 | small_dict['Bob'] = 0
28 | assert small_dict['Bob'] == 0
29 |
30 | # Exercise 1: make it pass
31 | easy_text = 'a small text with a few repetitions'
32 | assert count_words(easy_text) == {'a': 2, 'small': 1, 'text': 1, 'with': 1, 'few': 1, 'repetitions': 1}
33 |
34 | # Exercise 2: make it pass
35 | assert remove_non_alpha_characters('Remove non alpha!') == 'Remove non alpha'
36 | assert 'Capitalized'.lower() == 'capitalized'
37 | regular_text = """ALICE was beginning to get very tired of sitting by her sister on the bank and of having nothing
38 | to do: once or twice she had peeped into the book her sister was reading, but it had no pictures
39 | or conversations in it, "and what is the use of a book," thought Alice, "without pictures or conversations?'
40 | So she was considering, in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid),
41 | whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies,
42 | when suddenly a White Rabbit with pink eyes ran close by her."""
43 | alice_words = count_words(regular_text)
44 | assert alice_words['was'] == 3
45 | assert alice_words['of'] == 5
46 | assert 'Alice' not in alice_words
47 | assert alice_words['alice'] == 2
48 |
49 | # Exercise 3: use defaultdict in count_words: https://stackoverflow.com/a/5900628/1565454
50 |
--------------------------------------------------------------------------------
/300 - Comprehentions/list_comprehension.py:
--------------------------------------------------------------------------------
1 | if __name__ == '__main__':
2 | even_numbers = list()
3 | assert even_numbers[0] == 2
4 | assert even_numbers[10] == 22
5 | assert even_numbers[-1] == 98
6 | assert len(even_numbers) == 49
7 |
8 | powers_of_2 = list()
9 | assert powers_of_2[0] == 1
10 | assert powers_of_2[10] == 1024
11 | assert powers_of_2[20] == 1048576
12 | assert powers_of_2[-1] == 633825300114114700748351602688
13 | assert len(powers_of_2) == 100
14 |
--------------------------------------------------------------------------------
/300 - Comprehentions/list_comprehension_solution.py:
--------------------------------------------------------------------------------
1 | if __name__ == '__main__':
2 | even_numbers = [n for n in range(2, 100) if not n % 2]
3 | assert even_numbers[0] == 2
4 | assert even_numbers[10] == 22
5 | assert even_numbers[-1] == 98
6 | assert len(even_numbers) == 49
7 |
8 | powers_of_2 = [2 ** n for n in range(100)]
9 | assert powers_of_2[0] == 1
10 | assert powers_of_2[10] == 1024
11 | assert powers_of_2[20] == 1048576
12 | assert powers_of_2[-1] == 633825300114114700748351602688
13 | assert len(powers_of_2) == 100
14 |
--------------------------------------------------------------------------------
/300 - Comprehentions/measure_words_length.py:
--------------------------------------------------------------------------------
1 | if __name__ == '__main__':
2 | animals = [
3 | 'Brown Recluse Spider',
4 | 'Camels',
5 | 'Cape Gannet Bird',
6 | 'Chickens',
7 | 'Chimpanzee',
8 | 'Cuviers Dwarf Caimans',
9 | 'Dog',
10 | ]
11 |
12 | # Exercise 1: make it pass
13 | words_with_len = dict()
14 | assert words_with_len == {
15 | 'Brown Recluse Spider': 20, 'Camels': 6, 'Cape Gannet Bird': 16, 'Chickens': 8, 'Chimpanzee': 10,
16 | 'Cuviers Dwarf Caimans': 21, 'Dog': 3
17 | }
18 |
19 | # Exercise 2: make it pass using `words_with_len` dict (filter elements with odd values)
20 | words_with_even_len = dict()
21 | assert words_with_even_len == {
22 | 'Brown Recluse Spider': 20, 'Camels': 6, 'Cape Gannet Bird': 16, 'Chickens': 8, 'Chimpanzee': 10
23 | }
24 |
25 | # Exercise 3: make it pass using `words_with_len` dict
26 | assert 'Confluence'.startswith('Conf')
27 | # Filter words_with_len where key starts with 'prefix_to_filter'
28 | prefix_to_filter = 'C'
29 | words_with_prefix = dict()
30 | assert words_with_prefix == {
31 | 'Camels': 6, 'Cape Gannet Bird': 16, 'Chickens': 8, 'Chimpanzee': 10, 'Cuviers Dwarf Caimans': 21
32 | }
33 | prefix_to_filter = 'Ch'
34 | words_with_prefix2 = dict()
35 | assert words_with_prefix2 == {
36 | 'Chickens': 8, 'Chimpanzee': 10,
37 | }
38 |
--------------------------------------------------------------------------------
/300 - Comprehentions/measure_words_length_solution.py:
--------------------------------------------------------------------------------
1 | if __name__ == '__main__':
2 | animals = [
3 | 'Brown Recluse Spider',
4 | 'Camels',
5 | 'Cape Gannet Bird',
6 | 'Chickens',
7 | 'Chimpanzee',
8 | 'Cuviers Dwarf Caimans',
9 | 'Dog',
10 | ]
11 |
12 | # Exercise 1: make it pass
13 | words_with_len = {animal: len(animal) for animal in animals}
14 | assert words_with_len == {
15 | 'Brown Recluse Spider': 20, 'Camels': 6, 'Cape Gannet Bird': 16, 'Chickens': 8, 'Chimpanzee': 10,
16 | 'Cuviers Dwarf Caimans': 21, 'Dog': 3
17 | }
18 |
19 | # Exercise 2: make it pass
20 | words_with_even_len = {word: word_len for word, word_len in words_with_len.items() if not word_len % 2}
21 | assert words_with_even_len == {
22 | 'Brown Recluse Spider': 20, 'Camels': 6, 'Cape Gannet Bird': 16, 'Chickens': 8, 'Chimpanzee': 10
23 | }
24 |
25 | # Exercise 3: make it pass
26 | assert 'Confluence'.startswith('Conf')
27 | # Filter words_with_len where key starts with 'prefix_to_filter'
28 | prefix_to_filter = 'C'
29 | words_with_prefix = {
30 | word: word_len for word, word_len in words_with_len.items() if word.startswith(prefix_to_filter)
31 | }
32 | assert words_with_prefix == {
33 | 'Camels': 6, 'Cape Gannet Bird': 16, 'Chickens': 8, 'Chimpanzee': 10, 'Cuviers Dwarf Caimans': 21
34 | }
35 | prefix_to_filter = 'Ch'
36 | words_with_prefix2 = {
37 | word: word_len for word, word_len in words_with_len.items() if word.startswith(prefix_to_filter)
38 | }
39 | assert words_with_prefix2 == {
40 | 'Chickens': 8, 'Chimpanzee': 10,
41 | }
42 |
--------------------------------------------------------------------------------
/400 - Strings and bytes/README.md:
--------------------------------------------------------------------------------
1 | # Python strings and bytes
2 |
3 | ## [A Guide to Unicode, UTF-8 and Strings in Python][]
4 |
5 | ### What is ASCII?
6 | > While reading bytes from a file, a reader needs to know what those bytes mean.
7 | > So if you write a JSON file and send it over to your friend,
8 | > your friend would need to know how to deal with the bytes in your JSON file.
9 | > For the first 20 years or so of computing, upper and lower case English characters,
10 | > some punctuations and digits were enough. These were all encoded into a 127 symbol list called ASCII.
11 | > 7 bits of information or 1 byte is enough to encode every English character.
12 | > You could tell your friend to decode your JSON file in ASCII encoding, and voila
13 | > — she would be able to read what you sent her.
14 | >
15 | > This was cool for the initial few decades or so,
16 | > but slowly we realized that there are way more number of characters than just English characters.
17 | > We tried extending 127 characters to 256 characters (via Latin-1 or ISO-8859–1)
18 | > to fully utilize the 8 bit space — but that was not enough.
19 | > We needed an international standard that we all agreed on to deal with hundreds and thousands
20 | > of non-English characters.
21 |
22 | ### What is Unicode?
23 | > Unicode is international standard where a mapping of individual characters and a unique number
24 | > is maintained. As of May 2019, the most recent version of Unicode is 12.1 which contains over
25 | > 137k characters including different scripts including English, Hindi, Chinese and Japanese,
26 | > as well as emojis. These 137k characters are each represented by a unicode code point.
27 | > So unicode code points refer to actual characters that are displayed.
28 | >
29 | > These code points are encoded to bytes and decoded from bytes back to code points.
30 | > Examples: Unicode code point for alphabet a is U+0061, emoji 🖐 is U+1F590, and for Ω is U+03A9.
31 |
32 | ### What does it have to do with Python?
33 | [Pragmatic Unicode by Ned Batchelder][]
34 | [Unicode sandwich][]
35 |
36 | ## String and bytes in python3
37 | [Łona - ąę][] (album "Absurd i Nonsens")
38 |
39 | ```python
40 | unicode_string = 'Łona - ąę'
41 | print(unicode_string)
42 | # Łona - ąę
43 | assert len(unicode_string) == 9
44 | assert type(unicode_string) is str
45 | assert unicode_string[0] == 'Ł'
46 | assert unicode_string[::-1] == 'ęą - anoŁ'
47 |
48 | bytes_sequence = unicode_string.encode()
49 | print(bytes_sequence)
50 | # b'\xc5\x81ona - \xc4\x85\xc4\x99'
51 | assert len(bytes_sequence) == 12
52 | assert bytes_sequence[0] == 0xc5 # 197 (int)
53 |
54 | assert bytes_sequence.decode() == unicode_string
55 | print(bytes_sequence[::-1])
56 | # b'\x99\xc4\x85\xc4 - ano\x81\xc5'
57 | bytes_sequence[::-1].decode() # UnicodeDecodeError: 'utf-8' ...
58 | ```
59 |
60 | ## Working with files
61 | [Python open file][]
62 | ```python
63 | f = open('output1.txt', 'w')
64 | f.write('Internal of file')
65 | f.close()
66 |
67 | # But it's more convenient to skip close
68 | with open('output2.txt', 'w') as f:
69 | f.write('Internal of file')
70 | ```
71 |
72 | ### Exercises
73 | * [count_words.py](count_words.py)
74 | * [rewrite_order.py](rewrite_order.py)
75 |
76 |
77 |
78 | [Python open file]: https://www.w3schools.com/python/python_file_handling.asp
79 | [A Guide to Unicode, UTF-8 and Strings in Python]: https://towardsdatascience.com/a-guide-to-unicode-utf-8-and-strings-in-python-757a232db95c
80 | [Pragmatic Unicode by Ned Batchelder]: https://bit.ly/unipain
81 | [Unicode sandwich]: https://nedbatchelder.com/text/unipain/unipain.html#35
82 | [Łona - ąę]: https://www.youtube.com/watch?v=T2iISWltdzc
83 |
--------------------------------------------------------------------------------
/400 - Strings and bytes/count_words.py:
--------------------------------------------------------------------------------
1 | import re
2 | from collections import defaultdict
3 |
4 |
5 | def remove_non_alpha_characters(text):
6 | regex = re.compile('[^a-zA-Z $]')
7 | return regex.sub('', text)
8 |
9 |
10 | def count_words_from_file(filename):
11 | return dict()
12 |
13 |
14 | if __name__ == '__main__':
15 | # TIP: remember about defaultdict in count_words: https://stackoverflow.com/a/5900628/1565454
16 | assert "there will be code".split() == ['there', 'will', 'be', 'code']
17 | assert "there-will-be-code".split('-') == ['there', 'will', 'be', 'code']
18 |
19 | small_dict = {
20 | 'Alice': 1,
21 | }
22 | assert 'Alice' in small_dict
23 | assert 'Bob' not in small_dict
24 | small_dict['Bob'] = 0
25 | assert small_dict['Bob'] == 0
26 |
27 | # Exercise 1: make it pass
28 | easy_counts = count_words_from_file('./data/easy_text.txt')
29 | assert easy_counts == {'a': 2, 'small': 1, 'text': 1, 'with': 1, 'few': 1, 'repetitions': 1}
30 |
31 | # Exercise 2: make it pass
32 | assert remove_non_alpha_characters('Remove non alpha!') == 'Remove non alpha'
33 | assert 'Capitalized'.lower() == 'capitalized'
34 | alice_words = count_words_from_file('./data/regular_text.txt')
35 | assert alice_words['was'] == 3
36 | assert alice_words['of'] == 5
37 | assert 'Alice' not in alice_words
38 | assert alice_words['alice'] == 2
39 |
--------------------------------------------------------------------------------
/400 - Strings and bytes/count_words_solution.py:
--------------------------------------------------------------------------------
1 | import re
2 | from collections import defaultdict
3 |
4 |
5 | def remove_non_alpha_characters(text):
6 | regex = re.compile('[^a-zA-Z $]')
7 | return regex.sub('', text)
8 |
9 |
10 | def count_words_from_file(filename):
11 | word_counts = defaultdict(int)
12 | with open(filename) as f:
13 | raw_words = remove_non_alpha_characters(f.read()).lower().split()
14 | for word in raw_words:
15 | word_counts[word] += 1
16 | return word_counts
17 |
18 |
19 | if __name__ == '__main__':
20 | # TIP: remember about defaultdict in count_words: https://stackoverflow.com/a/5900628/1565454
21 | assert "there will be code".split() == ['there', 'will', 'be', 'code']
22 | assert "there-will-be-code".split('-') == ['there', 'will', 'be', 'code']
23 |
24 | small_dict = {
25 | 'Alice': 1,
26 | }
27 | assert 'Alice' in small_dict
28 | assert 'Bob' not in small_dict
29 | small_dict['Bob'] = 0
30 | assert small_dict['Bob'] == 0
31 |
32 | # Exercise 1: make it pass
33 | easy_counts = count_words_from_file('./data/easy_text.txt')
34 | assert easy_counts == {'a': 2, 'small': 1, 'text': 1, 'with': 1, 'few': 1, 'repetitions': 1}
35 |
36 | # Exercise 2: make it pass
37 | assert remove_non_alpha_characters('Remove non alpha!') == 'Remove non alpha'
38 | assert 'Capitalized'.lower() == 'capitalized'
39 | alice_words = count_words_from_file('./data/regular_text.txt')
40 | assert alice_words['was'] == 3
41 | assert alice_words['of'] == 5
42 | assert 'Alice' not in alice_words
43 | assert alice_words['alice'] == 2
44 |
--------------------------------------------------------------------------------
/400 - Strings and bytes/data/easy_text.txt:
--------------------------------------------------------------------------------
1 | a small text with a few repetitions
2 |
--------------------------------------------------------------------------------
/400 - Strings and bytes/data/matrix:
--------------------------------------------------------------------------------
1 | a b c d e f g h i j k l m n o p
2 | 1 2 3 4 5 6 7 8 9
3 | . ! ? " | >
4 | ( ͡° ͜ʖ ͡°)
--------------------------------------------------------------------------------
/400 - Strings and bytes/data/regular_text.txt:
--------------------------------------------------------------------------------
1 | ALICE was beginning to get very tired of sitting by her sister on the bank and of having nothing
2 | to do: once or twice she had peeped into the book her sister was reading, but it had no pictures
3 | or conversations in it, "and what is the use of a book," thought Alice, "without pictures or conversations?'
4 | So she was considering, in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid),
5 | whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies,
6 | when suddenly a White Rabbit with pink eyes ran close by her.
--------------------------------------------------------------------------------
/400 - Strings and bytes/rewrite_order.py:
--------------------------------------------------------------------------------
1 | def display_enumerated_lines(filename):
2 | """Display lines of files with line number as prefix
3 | use `enumerate` function"""
4 |
5 |
6 | def reverse_lines_y(filename):
7 | """Reads data from `` and writes reversed lines to file `_reversed_y'
8 | e.g.
9 | a b > b a
10 | c d d c
11 | """
12 |
13 |
14 | def reverse_lines_x(filename):
15 | """Reads data from `` and writes reversed lines to file `_reversed_x'
16 | e.g.
17 | a b > c d
18 | c d a b
19 | """
20 |
21 |
22 | def reverse_lines(filename):
23 | """Reads data from `` and writes reversed lines to file `_reversed'
24 | e.g.
25 | a b > d c
26 | c d b a
27 | """
28 |
29 |
30 | if __name__ == '__main__':
31 | filename = './data/matrix'
32 |
33 | display_enumerated_lines(filename)
34 | reverse_lines_x(filename)
35 | reverse_lines_y(filename)
36 | reverse_lines(filename)
37 |
--------------------------------------------------------------------------------
/400 - Strings and bytes/rewrite_order_solution.py:
--------------------------------------------------------------------------------
1 | def display_enumerated_lines(filename):
2 | """Display lines of files with line number as prefix
3 | use `enumerate` function"""
4 |
5 | def reverse_lines_y(filename):
6 | """Reads data from `` and writes reversed lines to file `_reversed_y'
7 | e.g.
8 | a b > b a
9 | c d > d c
10 | """
11 | with open(filename) as in_f, open(f'{filename}_reversed_y', 'w') as out_f:
12 | in_lines = in_f.read().splitlines()
13 | out_f.write('\n'.join(in_lines[::-1]))
14 |
15 |
16 | def reverse_lines_x(filename):
17 | """Reads data from `` and writes reversed lines to file `_reversed_x'
18 | e.g.
19 | a b > c d
20 | c d > a b
21 | """
22 | with open(filename) as in_f, open(f'{filename}_reversed_x', 'w') as out_f:
23 | in_lines = in_f.read().splitlines()
24 | out_f.write('\n'.join((l[::-1] for l in in_lines)))
25 |
26 |
27 | def reverse_lines(filename):
28 | """Reads data from `` and writes reversed lines to file `_reversed'
29 | e.g.
30 | a b > d c
31 | c d > b a
32 | """
33 | with open(filename) as in_f, open(f'{filename}_reversed', 'w') as out_f:
34 | in_lines = in_f.read().splitlines()
35 | out_f.write('\n'.join((l[::-1] for l in in_lines[::-1])))
36 |
37 |
38 | if __name__ == '__main__':
39 | filename = './data/matrix'
40 |
41 | display_enumerated_lines(filename)
42 | reverse_lines_x(filename)
43 | reverse_lines_y(filename)
44 | reverse_lines(filename)
45 |
--------------------------------------------------------------------------------
/450 - Serialization/.gitignore:
--------------------------------------------------------------------------------
1 | list.json
2 | list.pkl
--------------------------------------------------------------------------------
/450 - Serialization/README.md:
--------------------------------------------------------------------------------
1 | # Serialization
2 | ## [JSON]
3 | [python json]
4 | * [json_example.py](../450%20-%20serialization/json_example.py)
5 |
6 | ## [Pickle]
7 | ### Examples
8 | * [pickle_example.py](../450%20-%20serialization/pickle_example.py)
9 | * [pickle_example_ml.py](../450%20-%20serialization/pickle_example_ml.py)
10 |
11 | ## Exercises:
12 | TODO
13 |
14 |
15 | [python json]: https://docs.python.org/3/library/json.html
16 | [JSON]: https://www.w3schools.com/whatis/whatis_json.asp
17 | [Pickle]: https://docs.python.org/3/library/pickle.html
18 |
--------------------------------------------------------------------------------
/450 - Serialization/json_example.py:
--------------------------------------------------------------------------------
1 | import json
2 | from decimal import Decimal
3 |
4 |
5 | def direct_conversion(obj):
6 | obj_json = json.dumps(obj)
7 | deserialized_object = json.loads(obj_json)
8 | return deserialized_object
9 |
10 |
11 | def file_conversion(obj, filename):
12 | with open(filename, 'w') as f:
13 | json.dump(obj, f)
14 | with open(filename, 'r') as f:
15 | deserialized_object = json.load(f)
16 | return deserialized_object
17 |
18 |
19 | if __name__ == '__main__':
20 | elements = [1, 2, 3.14, 'simple_text']
21 | direct_conversion(elements)
22 | file_conversion(elements, './list.json')
23 |
24 | """
25 | What's the problem with float?
26 | >>> 1.1 + 2.2
27 | 3.3000000000000003
28 | >>> Decimal('1.1') + Decimal('2.2')
29 | Decimal('3.3')
30 | """
31 |
32 | elements.append(Decimal(10))
33 | json.dumps(elements)
34 |
--------------------------------------------------------------------------------
/450 - Serialization/pickle_example.py:
--------------------------------------------------------------------------------
1 | import pickle
2 | from decimal import Decimal
3 |
4 |
5 | def direct_conversion(obj):
6 | obj_json = pickle.dumps(obj)
7 | deserialized_object = pickle.loads(obj_json)
8 | return deserialized_object
9 |
10 |
11 | def file_conversion(obj, filename):
12 | with open(filename, 'wb') as f:
13 | pickle.dump(obj, f)
14 | with open(filename, 'rb') as f:
15 | deserialized_object = pickle.load(f)
16 | return deserialized_object
17 |
18 |
19 | if __name__ == '__main__':
20 | elements = [1, 2, 3.14, 'simple_text']
21 | direct_conversion(elements)
22 | direct_conversion(elements)
23 | file_conversion(elements, './list.pkl')
24 |
25 | """
26 | What's the problem with float?
27 | >>> 1.1 + 2.2
28 | 3.3000000000000003
29 | >>> Decimal('1.1') + Decimal('2.2')
30 | Decimal('3.3')
31 | """
32 |
33 | elements.append(Decimal(10))
34 | pickled_elements = pickle.dumps(elements)
35 | print(pickled_elements)
36 |
--------------------------------------------------------------------------------
/450 - Serialization/pickle_example_ml.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | import numpy as np
4 | from sklearn import datasets
5 | import pickle
6 | from sklearn.neighbors import KNeighborsClassifier
7 |
8 |
9 | def reload_pickle_model(knn_model, filename='trained_knn_model.sav'):
10 | # save the knn_model to disk
11 | pickle.dump(knn_model, open(filename, 'wb'))
12 | # load the model from disk
13 | knn_model_reloaded = pickle.load(open(filename, 'rb'))
14 |
15 | return knn_model_reloaded
16 |
17 |
18 | def reload_test_data(test_x_data, test_y_data, filename='iris_test_data.csv'):
19 | common_array = np.c_[test_y_data, test_x_data] # set test_y_data as first column
20 |
21 | # save as csv file
22 | np.savetxt(filename, common_array, delimiter=';')
23 | # and read back
24 | common_array_reloaded = np.genfromtxt(filename, delimiter=";")
25 |
26 | # split read common array to x and y test data again
27 | test_y_data_reloaded = common_array_reloaded[:, 0]
28 | test_x_data_reloaded = common_array_reloaded[:, 1:]
29 |
30 | return test_x_data_reloaded, test_y_data_reloaded
31 |
32 |
33 | def count_score(test_data: List, prediction: List) -> float:
34 | """Dummy score standard"""
35 | assert len(test_data) == len(prediction)
36 | number_of_equal = sum(1 for test_el, predicted_el in zip(test_data, prediction) if test_el == predicted_el)
37 | return number_of_equal / len(test_data)
38 |
39 |
40 | def run_iris_example(use_file=False):
41 | iris = datasets.load_iris()
42 | iris_x = iris.data
43 | iris_y = iris.target
44 | np.unique(iris_y)
45 |
46 | # Split iris data in train and test data
47 | # A random permutation, to split the data randomly
48 | np.random.seed(0)
49 | indices = np.random.permutation(len(iris_x))
50 | iris_x_train = iris_x[indices[:-10]]
51 | iris_y_train = iris_y[indices[:-10]]
52 | iris_x_test = iris_x[indices[-10:]]
53 | iris_y_test = iris_y[indices[-10:]]
54 |
55 | # Create and fit a nearest-neighbor classifier
56 | knn_model = KNeighborsClassifier()
57 | knn_model.fit(iris_x_train, iris_y_train)
58 |
59 | if use_file:
60 | knn_model = reload_pickle_model(knn_model)
61 | iris_x_test, iris_y_test = reload_test_data(iris_x_test, iris_y_test)
62 |
63 | prediction = knn_model.predict(iris_x_test)
64 | score = count_score(iris_y_test, prediction)
65 | return score
66 |
67 |
68 | if __name__ == '__main__':
69 | score = run_iris_example(use_file=True)
70 | print(score)
71 |
--------------------------------------------------------------------------------
/500 - Python style/README.md:
--------------------------------------------------------------------------------
1 | # Python style
2 |
3 | ## [PEP 20 - The Zen of Python]
4 | ```python
5 | import this
6 | # The Zen of Python, by Tim Peters
7 | # Beautiful is better than ugly.
8 | # Explicit is better than implicit.
9 | # Simple is better than complex.
10 | # Complex is better than complicated.
11 | # Flat is better than nested.
12 | # Sparse is better than dense.
13 | # Readability counts.
14 | # Special cases aren't special enough to break the rules.
15 | # Although practicality beats purity.
16 | # Errors should never pass silently.
17 | # Unless explicitly silenced.
18 | # In the face of ambiguity, refuse the temptation to guess.
19 | # There should be one-- and preferably only one --obvious way to do it.
20 | # Although that way may not be obvious at first unless you're Dutch.
21 | # Now is better than never.
22 | # Although never is often better than *right* now.
23 | # If the implementation is hard to explain, it's a bad idea.
24 | # If the implementation is easy to explain, it may be a good idea.
25 | # Namespaces are one honking great idea -- let's do more of those!
26 | ```
27 |
28 | [The Zen of Python, Explained][]
29 |
30 | ## [PEP 8 - Style Guide for Python Code]
31 | [PEP 8 — the Style Guide for Python Code (for humans)][]
32 |
33 | > One of Guido’s key insights is that code is read much more often than it is written.
34 | > The guidelines provided here are intended to improve the readability of code
35 | > and make it consistent across the wide spectrum of Python code. As PEP 20 says,
36 | > “Readability counts”.
37 | >
38 | > A style guide is about consistency. Consistency with this style guide is important.
39 | > Consistency within a project is more important. Consistency within one module
40 | > or function is the most important.
41 | >
42 | > However, know when to be inconsistent—sometimes style guide recommendations
43 | > just aren't applicable. When in doubt, use your best judgment.
44 | > Look at other examples and decide what looks best. And don’t hesitate to ask!
45 | >
46 | > In particular: do not break backwards compatibility just to comply with this PEP!
47 |
48 | ### [🎵 The PEP 8 Song 🎵]
49 |
50 | ### What is Lint?
51 | > In computer programming lint or lint-like tools performing static analysis of
52 | > source code checking for symantec discrepancies.
53 | >
54 | > “Linting” means running a basic quality tool against your code.
55 | > The tool will check your code syntax and provide instructions on how to clean it.
56 |
57 | From [What is Flake8 and why we should use it?]
58 |
59 | #### Python Lint tools:
60 | * [flake8]
61 | * [pylint]
62 | * [black]
63 |
64 | ```shell
65 | # in env
66 | pip install flake8 pylint
67 | flake8 ugly_module
68 | # flake8 is complaining
69 | pylint ugly_module
70 | # pylint is complaining even more
71 | black ugly_module
72 | # black makes some changes
73 | ```
74 | * [ugly_exercise.py](ugly_module/ugly_exercise.py)
75 |
76 |
77 | ## Exercises:
78 | * [enum_exercise.py](enum_exercise.py)
79 |
80 | [The Zen of Python, Explained]: https://inventwithpython.com/blog/2018/08/17/the-zen-of-python-explained/
81 | [PEP 8 - Style Guide for Python Code]: https://www.python.org/dev/peps/pep-0008/
82 | [PEP 20 - The Zen of Python]: https://www.python.org/dev/peps/pep-0020/
83 | [PEP 8 — the Style Guide for Python Code (for humans)]: https://pep8.org/
84 | [🎵 The PEP 8 Song 🎵]: https://www.youtube.com/watch?v=hgI0p1zf31k
85 | [What is Flake8 and why we should use it?]: https://medium.com/python-pandemonium/what-is-flake8-and-why-we-should-use-it-b89bd78073f2
86 | [flake8]:https://flake8.pycqa.org/en/latest/
87 | [pylint]: https://pylint.org/
88 | [black]: https://pypi.org/project/black/
89 |
--------------------------------------------------------------------------------
/500 - Python style/enum_exercise.py:
--------------------------------------------------------------------------------
1 | from collections import defaultdict
2 | from enum import Enum
3 | from random import randint, choice
4 | from typing import List, Dict
5 |
6 |
7 | class Color(Enum):
8 | red = 'RED'
9 | green = 'GREEN'
10 |
11 |
12 | class WeightCategory(Enum):
13 | light = 'LIGHT'
14 | heavy = 'HEAVY'
15 |
16 |
17 | class Apple:
18 | def __init__(self, color: Color, weight: int):
19 | self.color = color
20 | self.weight = weight
21 |
22 | def __str__(self):
23 | return f'Apple(color={self.color.value}, weight={self.weight})'
24 |
25 | @staticmethod
26 | def generate_random_apple():
27 | return Apple(color=choice(list(Color)), weight=randint(50, 150))
28 |
29 | def get_weight_category(self) -> WeightCategory:
30 | """Return WeightCategory.light if weight is below 100"""
31 | pass
32 |
33 |
34 | def group_apples_by_color(apples: List[Apple]) -> Dict[Color, List[Apple]]:
35 | pass
36 |
37 |
38 | def group_apples_by_color_then_weight(apples: List[Apple]) -> Dict[Color, Dict[WeightCategory, List[Apple]]]:
39 | pass
40 |
41 |
42 | if __name__ == '__main__':
43 | light_apple = Apple(color=Color.red, weight=55)
44 | assert light_apple.get_weight_category() == WeightCategory.light
45 |
46 | random_apple = Apple.generate_random_apple()
47 | print(random_apple)
48 |
49 | # Exercise1: generate 100 random apples
50 | apples = list()
51 | assert apples
52 |
53 | # Exercise2: group apples by color
54 | apples_by_color = group_apples_by_color(apples)
55 | assert apples_by_color[Color.red]
56 | assert all(a.color == Color.red for a in apples_by_color[Color.red])
57 |
58 | # Exercise3: group apples by color, then by WeightCategory
59 | apples_by_color_and_weight = group_apples_by_color_then_weight(apples)
60 | assert apples_by_color_and_weight[Color.red][WeightCategory.light]
61 | assert all(
62 | a.color == Color.red and a.weight < 100
63 | for a in apples_by_color_and_weight[Color.red][WeightCategory.light]
64 | )
65 |
--------------------------------------------------------------------------------
/500 - Python style/enum_exercise_solution.py:
--------------------------------------------------------------------------------
1 | from collections import defaultdict
2 | from enum import Enum
3 | from random import randint, choice
4 | from typing import List, Dict
5 |
6 |
7 | class Color(Enum):
8 | red = 'RED'
9 | green = 'GREEN'
10 |
11 |
12 | class WeightCategory(Enum):
13 | light = 'LIGHT'
14 | heavy = 'HEAVY'
15 |
16 |
17 | class Apple:
18 | def __init__(self, color: Color, weight: int):
19 | self.color = color
20 | self.weight = weight
21 |
22 | def __repr__(self):
23 | return f'Apple(color={self.color.value}, weight={self.weight})'
24 |
25 | @staticmethod
26 | def getnerate_random_apple():
27 | return Apple(color=choice(list(Color)), weight=randint(50, 150))
28 |
29 | def get_weight_category(self) -> WeightCategory:
30 | """Return WeightCategory.light if weight is below 100"""
31 | return WeightCategory.light if self.weight < 100 else WeightCategory.heavy
32 |
33 |
34 | def group_apples_by_color(apples: List[Apple]) -> Dict[Color, List[Apple]]:
35 | apples_by_color = defaultdict(list)
36 | for apple in apples:
37 | apples_by_color[apple.color].append(apple)
38 | return apples_by_color
39 |
40 |
41 | def group_apples_by_color_then_weight(apples: List[Apple]) -> Dict[Color, Dict[WeightCategory, List[Apple]]]:
42 | apples_by_color_and_weight = defaultdict(lambda: defaultdict(list))
43 | for apple in apples:
44 | apples_by_color_and_weight[apple.color][apple.get_weight_category()].append(apple)
45 | return apples_by_color_and_weight
46 |
47 |
48 | if __name__ == '__main__':
49 | light_apple = Apple(color=Color.red, weight=55)
50 | assert light_apple.get_weight_category() == WeightCategory.light
51 |
52 | random_apple = Apple.generate_random_apple()
53 | print(random_apple)
54 |
55 | # Exercise1: generate 100 random apples
56 | apples = [Apple.generate_random_apple() for _ in range(100)]
57 | print(apples)
58 |
59 | # Exercise2: group apples by color
60 | apples_by_color = group_apples_by_color(apples)
61 | assert all(a.color == Color.red for a in apples_by_color[Color.red])
62 |
63 | # Exercise3: group apples by color, then by WeightCategory
64 | apples_by_color_and_weight = group_apples_by_color_then_weight(apples)
65 | assert all(
66 | a.color == Color.red and a.weight < 100
67 | for a in apples_by_color_and_weight[Color.red][WeightCategory.light]
68 | )
69 |
--------------------------------------------------------------------------------
/500 - Python style/if_name_main/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shnela/python_course/842f94c2b0f8d73428919a3c9f397720554c30a7/500 - Python style/if_name_main/__init__.py
--------------------------------------------------------------------------------
/500 - Python style/if_name_main/file1.py:
--------------------------------------------------------------------------------
1 | from file2 import do_sth
2 |
3 | print(f'file1 loaded ({__name__})')
4 |
5 | if __name__ == '__main__':
6 | print('file_1 in block if')
7 | do_sth()
8 |
--------------------------------------------------------------------------------
/500 - Python style/if_name_main/file2.py:
--------------------------------------------------------------------------------
1 | print(f'file2 loaded ({__name__})')
2 |
3 |
4 | def do_sth():
5 | print('do sth invoked')
6 |
7 |
8 | if __name__ == '__main__':
9 | # it won't be executed when `file2` is imported as module
10 | print('file_2 in block if')
11 |
--------------------------------------------------------------------------------
/500 - Python style/ugly_module/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shnela/python_course/842f94c2b0f8d73428919a3c9f397720554c30a7/500 - Python style/ugly_module/__init__.py
--------------------------------------------------------------------------------
/500 - Python style/ugly_module/ugly_exercise.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | fileToDump = 'duped_class.json'
4 |
5 | from random import randint
6 |
7 | class SomeClass:
8 | def __init__(self, ATTR1, attr2):
9 | self.ATTR1 = ATTR1
10 | self.attr2 = attr2
11 |
12 | def printAttributes(self):
13 | print(f'attrs: {self.ATTR1}, {self.attr2}')
14 |
15 | class inheritedClass(SomeClass):
16 | def printAttributes(self):
17 | print(f"attrs: {self.attr2}, {self.ATTR1}")
18 |
19 | def generate_int() -> str:
20 | return randint(0, 10)
21 |
22 | if __name__ == '__main__':
23 | Number = generate_int()
24 | classInstance = SomeClass(Number, 'other attribute')
25 | classInstance.printAttributes()
26 |
27 | subclassInstance = inheritedClass(Number, "other attribute")
28 | subclassInstance.printAttributes()
29 |
--------------------------------------------------------------------------------
/510 - Requests/README.md:
--------------------------------------------------------------------------------
1 | # [Requests: HTTP for Humans]
2 |
3 | ## Quick example
4 | ```python
5 | r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
6 |
7 | r.status_code
8 | 200
9 |
10 | r.headers['content-type']
11 | 'application/json; charset=utf8'
12 |
13 | r.encoding
14 | 'utf-8'
15 |
16 | r.text
17 | '{"type":"User"...'
18 |
19 | r.json()
20 | {'private_gists': 419, 'total_private_repos': 77, ...}
21 | ```
22 |
23 | ## Requests debugging
24 |
25 | ### [More complicated POST requests][]
26 |
27 | ```python
28 | import json
29 | import requests
30 |
31 | url = 'https://api.github.com/some/endpoint'
32 |
33 | payload = {'some': 'data'}
34 |
35 | r1 = requests.post(url, data=json.dumps(payload))
36 | ```
37 | > Please note that the above code will NOT add the Content-Type header (so in particular it will NOT set it to application/json).
38 | ```python
39 | r2 = requests.post(url, json=payload)
40 | ```
41 |
42 | ### [RequestsToCurl][]
43 |
44 | ```bash
45 | data = {
46 | "title": 't',
47 | "body": 'b',
48 | "userId": 1,
49 | }
50 |
51 | # this POST won't send JSON serialized data
52 | response1 = requests.post(
53 | "https://jsonplaceholder.typicode.com/posts",
54 | data=data
55 | )
56 | print(curl.parse(response1))
57 |
58 | # this will, but without `Content-Type` header
59 | response2 = requests.post(
60 | "https://jsonplaceholder.typicode.com/posts",
61 | data=json.dumps(data)
62 | )
63 | print(curl.parse(response2))
64 |
65 | response3 = requests.post(
66 | "https://jsonplaceholder.typicode.com/posts",
67 | json=data
68 | )
69 | print(curl.parse(response3))
70 | ```
71 |
72 | #### Result:
73 | ```
74 | curl -X POST -H ... -H 'Content-Type: application/x-www-form-urlencoded' -H 'User-Agent: python-requests/2.28.0' -d 'title=t&body=b&userId=1' https://jsonplaceholder.typicode.com:443/posts
75 | curl -X POST -H ... -H 'User-Agent: python-requests/2.28.0' -d '{"title": "t", "body": "b", "userId": 1}' https://jsonplaceholder.typicode.com:443/posts
76 | curl -X POST -H ... -H 'Content-Type: application/json' -H 'User-Agent: python-requests/2.28.0' -d '{"title": "t", "body": "b", "userId": 1}' https://jsonplaceholder.typicode.com:443/posts
77 | ```
78 |
79 | ## Fake REST API [{JSON} Placeholder][]
80 |
81 | Fake REST API for testing.
82 |
83 | [{JSON} Placeholder - guide][]
84 | > Important: resource will not be really updated on the server but it will be faked as if.
85 |
86 | [json_placeholder.py](json_placeholder.py)
87 |
88 |
89 | [Requests: HTTP for Humans]: https://requests.readthedocs.io/en/latest/
90 | [RequestsToCurl]: https://pypi.org/project/requests-to-curl/
91 | [More complicated POST requests]: https://requests.readthedocs.io/en/latest/user/quickstart/#more-complicated-post-requests
92 | [{JSON} Placeholder]: https://jsonplaceholder.typicode.com/
93 | [{JSON} Placeholder - guide]: https://jsonplaceholder.typicode.com/guide/
94 |
--------------------------------------------------------------------------------
/510 - Requests/json_placeholder.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 |
4 | def get_posts():
5 | pass
6 |
7 |
8 | def get_post(post_id: int):
9 | pass
10 |
11 |
12 | def publish_post(title, body, user_id):
13 | pass
14 |
15 |
16 | def delete_post(post_id):
17 | pass
18 |
19 |
20 | def get_user_posts(user_id: int):
21 | pass
22 |
23 |
24 | if __name__ == "__main__":
25 | get_user_posts(1)
26 |
27 | all_posts = get_posts()
28 | post_one = get_post(1)
29 | assert post_one == {
30 | 'userId': 1,
31 | 'id': 1,
32 | 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
33 | 'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\n'
34 | 'reprehenderit molestiae ut ut quas totam\n'
35 | 'nostrum rerum est autem sunt rem eveniet architecto'
36 | }
37 | publish_post("title", "Lore impsum", 1)
38 |
39 |
--------------------------------------------------------------------------------
/510 - Requests/json_placeholder_solution.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 |
4 | def get_posts():
5 | response = requests.get(
6 | "https://jsonplaceholder.typicode.com/posts",
7 | )
8 | assert response.status_code == 200
9 | response_object = response.json()
10 | return response_object
11 |
12 |
13 | def get_post(post_id: int):
14 | response = requests.get(
15 | f"https://jsonplaceholder.typicode.com/posts/{post_id}",
16 | )
17 | assert response.status_code == 200
18 | response_object = response.json()
19 | return response_object
20 |
21 |
22 | def publish_post(title, body, user_id):
23 | data = {
24 | "title": title,
25 | "body": body,
26 | "userId": user_id,
27 | }
28 | response = requests.post(
29 | "https://jsonplaceholder.typicode.com/posts",
30 | json=data
31 | )
32 | assert response.status_code == 201
33 | response_object = response.json()
34 | return response_object
35 |
36 |
37 | def delete_post(post_id):
38 | pass
39 |
40 |
41 | def get_user_posts(user_id: int):
42 | response = requests.get(
43 | f"https://jsonplaceholder.typicode.com/users/{user_id}/posts",
44 | )
45 | assert response.status_code == 200
46 | response_object = response.json()
47 | return response_object
48 |
49 |
50 | if __name__ == "__main__":
51 | get_user_posts(1)
52 |
53 | all_posts = get_posts()
54 | post_one = get_post(1)
55 | assert post_one == {
56 | 'userId': 1,
57 | 'id': 1,
58 | 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
59 | 'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\n'
60 | 'reprehenderit molestiae ut ut quas totam\n'
61 | 'nostrum rerum est autem sunt rem eveniet architecto'
62 | }
63 | publish_post("title", "Lore impsum", 1)
64 |
65 |
--------------------------------------------------------------------------------
/510 - Requests/test_json_placeholder.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from json_placeholder import *
4 |
5 |
6 | class TestJsonPlaceholder(unittest.TestCase):
7 | def test_get_posts(self):
8 | posts = get_posts()
9 | self.assertEqual(len(posts), 100)
10 | self.assertEqual(posts[0]['id'], 1)
11 |
12 | def test_get_post(self):
13 | expected_post = {
14 | 'id': 1,
15 | 'userId': 1,
16 | 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
17 | 'body': 'quia et suscipit\n'
18 | 'suscipit recusandae consequuntur expedita et cum\n'
19 | 'reprehenderit molestiae ut ut quas totam\n'
20 | 'nostrum rerum est autem sunt rem eveniet architecto'
21 | }
22 | post1 = get_post(1)
23 | self.assertEqual(post1, expected_post)
24 |
25 | def test_get_user_posts(self):
26 | user_id = 1
27 | expected_posts = [post for post in get_posts() if post['userId'] == user_id]
28 | user_posts = get_user_posts(user_id)
29 | self.assertEqual(user_posts, expected_posts)
30 |
--------------------------------------------------------------------------------
/525 - Classes 101/README.md:
--------------------------------------------------------------------------------
1 | # Classes
2 |
3 | ### Examples:
4 | * [simple_class.py](./simple_class.py)
5 | * [abstract_classes.py](./abstract_classes.py)
6 |
7 | ## Properties
8 | [Properties]
9 |
10 | ### Examples:
11 | * [properties.py](./properties.py)
12 | * [decoder_exercise.py](./decoder_exercise.py)
13 |
14 |
15 | [Properties]: https://www.programiz.com/python-programming/property
--------------------------------------------------------------------------------
/525 - Classes 101/abstract_classes.py:
--------------------------------------------------------------------------------
1 | import abc
2 |
3 |
4 | class AbstractClass(abc.ABC):
5 | @abc.abstractmethod
6 | def abstract_method1(self):
7 | """undefined method"""
8 |
9 | def defined_method(self):
10 | return 'defined return value'
11 |
12 |
13 | class DefinedClass(AbstractClass):
14 | def abstract_method1(self):
15 | return 42
16 |
17 |
18 | if __name__ == '__main__':
19 | # a = AbstractClass()
20 | b = DefinedClass()
21 | print(b.abstract_method1())
22 |
23 | assert isinstance(b, AbstractClass)
24 |
--------------------------------------------------------------------------------
/525 - Classes 101/decoder_exercise.py:
--------------------------------------------------------------------------------
1 | import enum
2 |
3 |
4 | __all__ = [
5 | 'DecoderException',
6 | 'Decoder',
7 | 'State',
8 | 'ResolutionChoice'
9 | ]
10 |
11 |
12 | class DecoderException(Exception):
13 | pass
14 |
15 |
16 | class State(enum.Enum):
17 | standby = 'STANDBY'
18 | on = 'ON'
19 |
20 |
21 | class ResolutionChoice(enum.Enum):
22 | hd = 'HD'
23 | full_hd = 'FHD'
24 |
25 |
26 | class Decoder:
27 | pass
28 |
--------------------------------------------------------------------------------
/525 - Classes 101/decoder_exercise_solution.py:
--------------------------------------------------------------------------------
1 | import enum
2 | from functools import wraps
3 |
4 |
5 | class DecoderException(Exception):
6 | pass
7 |
8 |
9 | class State(enum.Enum):
10 | standby = 'STANDBY'
11 | on = 'ON'
12 |
13 |
14 | class ResolutionChoice(enum.Enum):
15 | hd = 'HD'
16 | full_hd = 'FHD'
17 |
18 |
19 | def do_not_operate_on_standby(f):
20 | """Probably no better way of doing it
21 | https://stackoverflow.com/a/11731208/1565454
22 | """
23 | @wraps(f)
24 | def inner_func(self, *args, **kwargs):
25 | if self._state == State.standby:
26 | raise DecoderException
27 | return f(self, *args, **kwargs)
28 |
29 | return inner_func
30 |
31 |
32 | class Decoder:
33 | def __init__(self):
34 | self._resolution = ResolutionChoice.hd
35 | self._current_channel = 1
36 | self._state = State.standby
37 |
38 | @do_not_operate_on_standby
39 | def set_resolution(self, resolution: ResolutionChoice) -> None:
40 | self._resolution = resolution
41 |
42 | @do_not_operate_on_standby
43 | def get_resolution(self) -> ResolutionChoice:
44 | return self._resolution
45 |
46 | @do_not_operate_on_standby
47 | def set_channel(self, channel: int) -> None:
48 | if channel <= 0:
49 | raise DecoderException
50 | self._current_channel = channel
51 |
52 | def get_current_channel(self) -> int:
53 | return self._current_channel
54 |
55 | def turn_off(self):
56 | self._state = State.standby
57 |
58 | def turn_on(self):
59 | self._state = State.on
60 |
61 | def get_state(self) -> State:
62 | return self._state
63 |
64 | @do_not_operate_on_standby
65 | def channel_up(self):
66 | self._current_channel += 1
67 |
68 | @do_not_operate_on_standby
69 | def channel_down(self):
70 | if self._current_channel == 1:
71 | raise DecoderException
72 |
73 | self._current_channel -= 1
74 |
--------------------------------------------------------------------------------
/525 - Classes 101/properties.py:
--------------------------------------------------------------------------------
1 | class Ball:
2 | def __init__(self, weight, diameter):
3 | self._weight = weight
4 | self.__diameter = diameter
5 |
6 | @property
7 | def weight(self):
8 | return self._weight
9 |
10 | @weight.setter
11 | def weight(self, value):
12 | if value < 0:
13 | raise ValueError('Value should be greater than 0')
14 | self._weight = value
15 |
16 | @property
17 | def diameter(self):
18 | return self.__diameter
19 |
20 | @diameter.setter
21 | def diameter(self, value):
22 | if value < 0:
23 | raise ValueError('Value should be greater than 0')
24 | self.__diameter = value
25 |
26 |
27 | if __name__ == '__main__':
28 | a = Ball(weight=10, diameter=5)
29 | print(a.weight)
30 | a.weight = 11
31 | print(a.weight)
32 |
33 | # but
34 | a._weight = -1
35 | print(a.weight)
36 |
37 | #
38 | # a.__diameter = -1
39 | # a._Ball__diameter = -1
40 | print(a.diameter)
41 |
--------------------------------------------------------------------------------
/525 - Classes 101/simple_class.py:
--------------------------------------------------------------------------------
1 | from random import random
2 | from functools import partial
3 |
4 |
5 | def multiply(a, b):
6 | return a * b
7 |
8 |
9 | multiply_by2 = partial(multiply, 2)
10 | assert multiply_by2(5) == 10
11 |
12 |
13 | class Simple:
14 | def __init__(self, attr):
15 | self.attr = attr
16 |
17 | def multiplied_attr(self, x):
18 | return self.attr * x
19 |
20 | @classmethod
21 | def factory(cls):
22 | # return Simple(random())
23 | return cls(random())
24 |
25 | @staticmethod
26 | def out_of_context():
27 | return 42
28 |
29 |
30 | if __name__ == '__main__':
31 | instance = Simple(10)
32 | print(instance.attr)
33 | instance.attr = 5
34 | print(instance.attr)
35 | instance.attr2 = 42
36 | print(instance.attr2)
37 |
38 | print(instance.multiplied_attr(10))
39 | print(Simple.multiplied_attr(instance, 10))
40 | print(partial(Simple.multiplied_attr, instance)(10))
41 |
42 |
43 |
--------------------------------------------------------------------------------
/525 - Classes 101/test_decoder_exercise.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from decoder_exercise import *
4 |
5 |
6 | class TestDecoder(unittest.TestCase):
7 | decoder = None
8 |
9 | def setUp(self) -> None:
10 | self.decoder = Decoder()
11 |
12 | def test_by_default_on_standby(self):
13 | self.assertEqual(self.decoder.get_state(), State.standby)
14 |
15 | def test_turning_on_and_off(self):
16 | self.decoder.turn_on()
17 | self.assertEqual(self.decoder.get_state(), State.on)
18 | self.decoder.turn_off()
19 | self.assertEqual(self.decoder.get_state(), State.standby)
20 |
21 | def test_changing_channels(self):
22 | # given
23 | self.decoder.turn_on()
24 |
25 | # when
26 | self.decoder.set_channel(42)
27 |
28 | # then
29 | self.assertEqual(self.decoder.get_current_channel(), 42)
30 |
31 | def test_channel_up_and_down(self):
32 | # given
33 | self.decoder.turn_on()
34 |
35 | # when
36 | self.decoder.set_channel(42)
37 | self.decoder.channel_up()
38 |
39 | # then
40 | self.assertEqual(self.decoder.get_current_channel(), 43)
41 |
42 | # when
43 | for _ in range(5):
44 | self.decoder.channel_down()
45 |
46 | # then
47 | self.assertEqual(self.decoder.get_current_channel(), 38)
48 |
49 | def test_channel_down_below_0(self):
50 | # given
51 | self.decoder.turn_on()
52 |
53 | # when
54 | self.decoder.set_channel(1)
55 | with self.assertRaises(DecoderException):
56 | self.decoder.channel_down()
57 |
58 | def test_setting_negative_channel(self):
59 | # given
60 | self.decoder.turn_on()
61 |
62 | # when
63 | for channel in range(-5, 1):
64 | with self.assertRaises(DecoderException):
65 | self.decoder.set_channel(channel)
66 |
67 | def test_preserving_channel_channels(self):
68 | # given
69 | self.decoder.turn_on()
70 | self.decoder.set_channel(42)
71 |
72 | # when
73 | self.decoder.turn_off()
74 | self.decoder.turn_on()
75 |
76 | # then
77 | self.assertEqual(self.decoder.get_current_channel(), 42)
78 |
79 | def test_default_resolution(self):
80 | # given
81 | self.decoder.turn_on()
82 |
83 | # expect
84 | self.assertEqual(self.decoder.get_resolution(), ResolutionChoice.hd)
85 |
86 | def test_changing_resolution(self):
87 | # given
88 | self.decoder.turn_on()
89 |
90 | # when
91 | self.decoder.set_resolution(ResolutionChoice.full_hd)
92 |
93 | # then
94 | self.assertEqual(self.decoder.get_resolution(), ResolutionChoice.full_hd)
95 |
96 | def test_do_not_operate_on_standby(self):
97 | with self.assertRaises(DecoderException):
98 | self.decoder.set_channel(1)
99 | with self.assertRaises(DecoderException):
100 | self.decoder.set_resolution(ResolutionChoice.full_hd)
101 | with self.assertRaises(DecoderException):
102 | self.decoder.get_resolution()
103 | with self.assertRaises(DecoderException):
104 | self.decoder.channel_up()
105 | with self.assertRaises(DecoderException):
106 | self.decoder.channel_down()
107 |
108 |
109 |
--------------------------------------------------------------------------------
/550 - Testing/README.md:
--------------------------------------------------------------------------------
1 | # Testing
2 |
3 | ## [xUnit] approach
4 |
5 | ## [unittest — Unit testing framework]
6 |
7 | ### Basic example
8 |
9 | ```python
10 | import unittest
11 |
12 | class TestStringMethods(unittest.TestCase):
13 |
14 | def test_upper(self):
15 | self.assertEqual('foo'.upper(), 'FOO')
16 |
17 | def test_isupper(self):
18 | self.assertTrue('FOO'.isupper())
19 | self.assertFalse('Foo'.isupper())
20 |
21 | def test_split(self):
22 | s = 'hello world'
23 | self.assertEqual(s.split(), ['hello', 'world'])
24 | # check that s.split fails when the separator is not a string
25 | with self.assertRaises(TypeError):
26 | s.split(2)
27 |
28 | if __name__ == '__main__':
29 | unittest.main()
30 | ```
31 |
32 | ### [Test cases]
33 |
34 | ```python
35 | def setUp(self):
36 | ...
37 |
38 | @classmethod
39 | def setUpClass(cls):
40 | ...
41 | ```
42 |
43 | ### [Command-Line Interface]
44 |
45 | ```shell
46 | python -m unittest
47 | ```
48 |
49 | ## [pytest: helps you write better programs]
50 |
51 | ```shell
52 | pip install pytest
53 | pytest
54 | ```
55 |
56 | ### [Creating JUnitXML format files]
57 |
58 | ```shell
59 | pytest --junitxml=path
60 | ```
61 | [Jenkins - JUnit]
62 |
63 | ### pytest vs unittest
64 | [test_decoder_exercise.py](./test_decoder_exercise.py)
65 | [test_decoder_exercise_pytest.py](./test_decoder_exercise_pytest.py)
66 |
67 | ### Features
68 | * [yield fixtures]
69 | * [How to parametrize fixtures and test functions] - look at `test_changing_channels` and `test_setting_negative_channel`
70 |
71 |
72 | ## Mocking
73 | [unittest.mock — mock object library]
74 |
75 |
76 | [xUnit]: https://en.wikipedia.org/wiki/XUnit
77 | [unittest — Unit testing framework]: https://docs.python.org/3/library/unittest.html
78 | [Test cases]: https://docs.python.org/3/library/unittest.html#test-cases
79 | [Command-Line Interface]: https://docs.python.org/3/library/unittest.html#command-line-interface
80 | [pytest: helps you write better programs]: https://docs.pytest.org/
81 | [Creating JUnitXML format files]: https://docs.pytest.org/en/7.1.x/how-to/output.html#creating-junitxml-format-files
82 | [Jenkins - JUnit]: https://plugins.jenkins.io/junit/
83 | [yield fixtures]: https://docs.pytest.org/en/7.1.x/how-to/fixtures.html#yield-fixtures-recommended
84 | [How to parametrize fixtures and test functions]: https://docs.pytest.org/en/7.1.x/how-to/parametrize.html#parametrize
85 | [unittest.mock — mock object library]: https://docs.python.org/3/library/unittest.mock.html
86 |
--------------------------------------------------------------------------------
/550 - Testing/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from decoder_exercise_solution import Decoder
3 |
4 |
5 | @pytest.fixture
6 | def decoder():
7 | yield Decoder()
8 |
--------------------------------------------------------------------------------
/550 - Testing/decoder_exercise_solution.py:
--------------------------------------------------------------------------------
1 | import enum
2 | from functools import wraps
3 | from random import random
4 | from time import sleep
5 |
6 |
7 | class DecoderException(Exception):
8 | pass
9 |
10 |
11 | class State(enum.Enum):
12 | standby = 'STANDBY'
13 | on = 'ON'
14 |
15 |
16 | class ResolutionChoice(enum.Enum):
17 | hd = 'HD'
18 | full_hd = 'FHD'
19 |
20 |
21 | class Server:
22 | def check_payment(self):
23 | sleep(1)
24 | val = random()
25 | return val > 0.1
26 |
27 |
28 | def do_not_operate_on_standby(f):
29 | """Probably no better way of doing it
30 | https://stackoverflow.com/a/11731208/1565454
31 | """
32 | @wraps(f)
33 | def inner_func(self, *args, **kwargs):
34 | if self._state == State.standby:
35 | raise DecoderException
36 | return f(self, *args, **kwargs)
37 |
38 | return inner_func
39 |
40 |
41 | class Decoder:
42 | def __init__(self):
43 | self._resolution = ResolutionChoice.hd
44 | self._current_channel = 1
45 | self._state = State.standby
46 | self._server = Server()
47 | self._blocked = False
48 |
49 | def check_payment(self):
50 | payment_ok = self._server.check_payment()
51 | if not payment_ok:
52 | self._blocked = True
53 |
54 | def is_blocked(self):
55 | return self._blocked
56 |
57 | @do_not_operate_on_standby
58 | def set_resolution(self, resolution: ResolutionChoice) -> None:
59 | self._resolution = resolution
60 |
61 | @do_not_operate_on_standby
62 | def get_resolution(self) -> ResolutionChoice:
63 | return self._resolution
64 |
65 | @do_not_operate_on_standby
66 | def set_channel(self, channel: int) -> None:
67 | if channel <= 0:
68 | raise DecoderException
69 | self._current_channel = channel
70 |
71 | def get_current_channel(self) -> int:
72 | return self._current_channel
73 |
74 | def turn_off(self):
75 | self._state = State.standby
76 |
77 | def turn_on(self):
78 | self._state = State.on
79 |
80 | def get_state(self) -> State:
81 | return self._state
82 |
83 | @do_not_operate_on_standby
84 | def channel_up(self):
85 | self._current_channel += 1
86 |
87 | @do_not_operate_on_standby
88 | def channel_down(self):
89 | if self._current_channel == 1:
90 | raise DecoderException
91 |
92 | self._current_channel -= 1
93 |
--------------------------------------------------------------------------------
/550 - Testing/test_decoder_exercise.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from decoder_exercise_solution import *
4 |
5 |
6 | class TestDecoder(unittest.TestCase):
7 | decoder = None
8 |
9 | def setUp(self) -> None:
10 | self.decoder = Decoder()
11 |
12 | def test_by_default_on_standby(self):
13 | self.assertEqual(self.decoder.get_state(), State.standby)
14 |
15 | def test_turning_on_and_off(self):
16 | self.decoder.turn_on()
17 | self.assertEqual(self.decoder.get_state(), State.on)
18 | self.decoder.turn_off()
19 | self.assertEqual(self.decoder.get_state(), State.standby)
20 |
21 | def test_changing_channels(self):
22 | # given
23 | self.decoder.turn_on()
24 |
25 | # when
26 | self.decoder.set_channel(42)
27 |
28 | # then
29 | self.assertEqual(self.decoder.get_current_channel(), 42)
30 |
31 | def test_channel_up_and_down(self):
32 | # given
33 | self.decoder.turn_on()
34 |
35 | for channel in range(1, 100):
36 | with self.subTest():
37 | # when
38 | self.decoder.set_channel(42)
39 | self.decoder.channel_up()
40 |
41 | # then
42 | self.assertEqual(self.decoder.get_current_channel(), 43)
43 |
44 | # when
45 | for _ in range(5):
46 | self.decoder.channel_down()
47 |
48 | # then
49 | self.assertEqual(self.decoder.get_current_channel(), 38)
50 |
51 | def test_channel_down_below_0(self):
52 | # given
53 | self.decoder.turn_on()
54 |
55 | # when
56 | self.decoder.set_channel(1)
57 | with self.assertRaises(DecoderException):
58 | self.decoder.channel_down()
59 |
60 | def test_setting_negative_channel(self):
61 | # given
62 | self.decoder.turn_on()
63 |
64 | # when
65 | for channel in range(-5, 1):
66 | with self.subTest():
67 | with self.assertRaises(DecoderException):
68 | self.decoder.set_channel(channel)
69 |
70 | def test_preserving_channel_channels(self):
71 | # given
72 | self.decoder.turn_on()
73 | self.decoder.set_channel(42)
74 |
75 | # when
76 | self.decoder.turn_off()
77 | self.decoder.turn_on()
78 |
79 | # then
80 | self.assertEqual(self.decoder.get_current_channel(), 42)
81 |
82 | def test_default_resolution(self):
83 | # given
84 | self.decoder.turn_on()
85 |
86 | # expect
87 | self.assertEqual(self.decoder.get_resolution(), ResolutionChoice.hd)
88 |
89 | def test_changing_resolution(self):
90 | # given
91 | self.decoder.turn_on()
92 |
93 | # when
94 | self.decoder.set_resolution(ResolutionChoice.full_hd)
95 |
96 | # then
97 | self.assertEqual(self.decoder.get_resolution(), ResolutionChoice.full_hd)
98 |
99 | def test_do_not_operate_on_standby(self):
100 | with self.assertRaises(DecoderException):
101 | self.decoder.set_channel(1)
102 | with self.assertRaises(DecoderException):
103 | self.decoder.set_resolution(ResolutionChoice.full_hd)
104 | with self.assertRaises(DecoderException):
105 | self.decoder.get_resolution()
106 | with self.assertRaises(DecoderException):
107 | self.decoder.channel_up()
108 | with self.assertRaises(DecoderException):
109 | self.decoder.channel_down()
110 |
--------------------------------------------------------------------------------
/550 - Testing/test_decoder_exercise_mocking.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from unittest.mock import patch
3 |
4 | from decoder_exercise_solution import *
5 |
6 |
7 | class TestDecoder(unittest.TestCase):
8 | decoder = None
9 |
10 | def setUp(self) -> None:
11 | self.decoder = Decoder()
12 |
13 | def test_check_payment_noblock(self):
14 | # given
15 | self.decoder.turn_on()
16 |
17 | # when
18 | with patch.object(Server, 'check_payment', return_value=True) as mocked_payment:
19 | self.decoder.check_payment()
20 |
21 | # then
22 | self.assertFalse(self.decoder.is_blocked())
23 |
24 | def test_check_payment_block(self):
25 | # given
26 | self.decoder.turn_on()
27 |
28 | # when
29 | with patch.object(Server, 'check_payment', return_value=False) as mocked_payment:
30 | self.decoder.check_payment()
31 |
32 | # then
33 | self.assertTrue(self.decoder.is_blocked())
34 |
35 |
--------------------------------------------------------------------------------
/550 - Testing/test_decoder_exercise_pytest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from decoder_exercise_solution import *
4 |
5 |
6 | class TestDecoder:
7 | def test_by_default_on_standby(self, decoder):
8 | assert decoder.get_state() == State.standby
9 |
10 | def test_turning_on_and_off(self, decoder):
11 | decoder.turn_on()
12 | assert decoder.get_state() == State.on
13 | decoder.turn_off()
14 | assert decoder.get_state() == State.standby
15 |
16 | @pytest.mark.parametrize("channel", range(1, 100))
17 | def test_changing_channels(self, decoder, channel):
18 | # given
19 | decoder.turn_on()
20 |
21 | # when
22 | decoder.set_channel(channel)
23 |
24 | # then
25 | assert decoder.get_current_channel() == channel
26 |
27 | def test_channel_up_and_down(self, decoder):
28 | # given
29 | decoder.turn_on()
30 |
31 | # when
32 | decoder.set_channel(42)
33 | decoder.channel_up()
34 |
35 | # then
36 | assert decoder.get_current_channel() == 43
37 |
38 | # when
39 | for _ in range(5):
40 | decoder.channel_down()
41 |
42 | # then
43 | assert decoder.get_current_channel() == 38
44 |
45 | def test_channel_down_below_0(self, decoder):
46 | # given
47 | decoder.turn_on()
48 |
49 | # when
50 | decoder.set_channel(1)
51 | with pytest.raises(DecoderException):
52 | decoder.channel_down()
53 |
54 | @pytest.mark.parametrize("channel", range(-5, 1))
55 | def test_setting_negative_channel(self, decoder, channel):
56 | # given
57 | decoder.turn_on()
58 |
59 | # when
60 | with pytest.raises(DecoderException):
61 | decoder.set_channel(channel)
62 |
63 | def test_preserving_channel_channels(self, decoder):
64 | # given
65 | decoder.turn_on()
66 | decoder.set_channel(42)
67 |
68 | # when
69 | decoder.turn_off()
70 | decoder.turn_on()
71 |
72 | # then
73 | assert decoder.get_current_channel() == 42
74 |
75 | def test_default_resolution(self, decoder):
76 | # given
77 | decoder.turn_on()
78 |
79 | # expect
80 | assert decoder.get_resolution() == ResolutionChoice.hd
81 |
82 | def test_changing_resolution(self, decoder):
83 | # given
84 | decoder.turn_on()
85 |
86 | # when
87 | decoder.set_resolution(ResolutionChoice.full_hd)
88 |
89 | # then
90 | assert decoder.get_resolution() == ResolutionChoice.full_hd
91 |
92 | def test_do_not_operate_on_standby(self, decoder):
93 | with pytest.raises(DecoderException):
94 | decoder.set_channel(1)
95 | with pytest.raises(DecoderException):
96 | decoder.set_resolution(ResolutionChoice.full_hd)
97 | with pytest.raises(DecoderException):
98 | decoder.get_resolution()
99 | with pytest.raises(DecoderException):
100 | decoder.channel_up()
101 | with pytest.raises(DecoderException):
102 | decoder.channel_down()
103 |
104 |
105 |
--------------------------------------------------------------------------------
/550 - Testing/test_example.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 |
4 | class TestStringMethods(unittest.TestCase):
5 |
6 | def setUp(self) -> None:
7 | print("Run before each test")
8 |
9 | def tearDown(self) -> None:
10 | print("Run after each test")
11 |
12 | @classmethod
13 | def setUpClass(cls) -> None:
14 | print("Run once before all")
15 |
16 | @classmethod
17 | def tearDownClass(cls) -> None:
18 | print("Run once after all")
19 |
20 | def test_upper(self):
21 | self.assertEqual('foo'.upper(), 'FOO')
22 |
23 | def test_isupper(self):
24 | self.assertTrue('FOO'.isupper())
25 | self.assertFalse('Foo'.isupper())
26 |
27 | def test_split(self):
28 | s = 'hello world'
29 | self.assertEqual(s.split(), ['hello', 'world'])
30 | # check that s.split fails when the separator is not a string
31 | with self.assertRaises(TypeError):
32 | s.split(2)
33 |
--------------------------------------------------------------------------------
/600 - Functions/README.md:
--------------------------------------------------------------------------------
1 | # Functions
2 |
3 | ## Passing arguments by name
4 | ```python
5 | def greetings(from_, to):
6 | print(f"{from_} greets {to}")
7 |
8 |
9 | greetings("Alice", "Bob")
10 | greetings("Alice", to="Bob")
11 | greetings(from_="Alice", to="Bob")
12 | greetings(to="Bob", from_="Alice")
13 | ```
14 | [greetings.py](greetings.py)
15 |
16 | ## args and kwargs
17 | ```python
18 | def fun(*args, **kwargs):
19 | print(args)
20 | print(kwargs)
21 |
22 |
23 | fun(1, 1, 2, 3, 5, foo="bar")
24 | """
25 | (1, 1, 2, 3, 5)
26 | {'foo': 'bar'}
27 | """
28 | ```
29 | [function_args.py](function_args.py)
30 |
31 | ## Passing args and kwargs
32 | ```python
33 | def greetings(from_, to):
34 | print(f"{from_} greets {to}")
35 |
36 |
37 | tuple_parameters = ("Alice", "Bob")
38 | greetings(*tuple_parameters)
39 |
40 | dict_parameters = {
41 | "from_": "Alice",
42 | "to": "Bob"
43 | }
44 | greetings(**dict_parameters)
45 | ```
46 | [greetings_passing_args_and_kwargs.py](greetings_passing_args_and_kwargs.py)
47 |
48 | ## Default arguments
49 | ```python
50 | def repeat(text, times=1):
51 | return text * times
52 |
53 |
54 | print(repeat("foo", 2))
55 | print(repeat("foo"))
56 | ```
57 | [default_arguments.py](default_arguments.py)
58 |
59 | Watch out for default mutable arguments: [default_mutable.py](default_mutable.py)
60 |
61 | ## Functions as first class citizens
62 | > In Python, functions are first class objects which means that functions in Python can be used or passed as arguments.
63 | Properties of first class functions:
64 | > * A function is an instance of the Object type.
65 | > * You can store the function in a variable.
66 | > * You can pass the function as a parameter to another function.
67 | > * You can return the function from a function.
68 | > * You can store them in data structures such as hash tables, lists, …
69 |
70 |
71 | ```python
72 | # Python program to illustrate functions
73 | # can be treated as objects
74 | def shout(text):
75 | return text.upper()
76 |
77 | print(shout('Hello'))
78 |
79 | yell = shout
80 |
81 | print(yell('Hello'))
82 | ```
83 |
84 | ```python
85 | # Python program to illustrate functions
86 | # can be passed as arguments to other functions
87 | def shout(text):
88 | return text.upper()
89 |
90 | def whisper(text):
91 | return text.lower()
92 |
93 | def greet(func):
94 | # storing the function in a variable
95 | greeting = func("""Hi, I am created by a function passed as an argument.""")
96 | print (greeting)
97 |
98 | greet(shout)
99 | greet(whisper)
100 | ```
101 |
102 | ```python
103 | # Python program to illustrate functions
104 | # Functions can return another function
105 |
106 | def create_adder(x):
107 | def adder(y):
108 | return x+y
109 |
110 | return adder
111 |
112 | add_15 = create_adder(15)
113 |
114 | print(add_15(10))
115 | ```
116 |
117 | ## Nonlocal and global variables
118 | * [nonlocal_variables.py](nonlocal_variables.py)
119 | * [global_variables.py](global_variables.py)
120 |
121 |
122 | ## Python decorators
123 | > Decorators allow us to wrap another function in order to extend the behaviour of the wrapped function,
124 | > without permanently modifying it.
125 |
126 | Example: [decorator_example.py](decorator_example.py)
127 |
128 | Exercise: [decorator_exercise.py](decorator_exercise.py)
129 |
130 |
131 | [Decorators in Python Geeks for Geeks]: https://www.geeksforgeeks.org/decorators-in-python/
132 |
--------------------------------------------------------------------------------
/600 - Functions/decorator_example.py:
--------------------------------------------------------------------------------
1 | from functools import wraps
2 |
3 |
4 | def deco(f):
5 | print('Decorator starts')
6 |
7 | # @wraps(f)
8 | def inner_func():
9 | print(f'Inner starts')
10 | f()
11 | print(f'Inner ends')
12 |
13 | print('Decorator ends')
14 | return inner_func
15 |
16 |
17 | # @deco
18 | def main_function():
19 | print("Inner fu")
20 |
21 |
22 | decorated_main_function = deco(main_function)
23 |
24 |
25 | if __name__ == '__main__':
26 | decorated_main_function()
27 |
28 | # our_main_function('arg1_val')
29 |
30 | # print(decorated_main_function.__name__)
31 |
--------------------------------------------------------------------------------
/600 - Functions/decorator_exercise.py:
--------------------------------------------------------------------------------
1 | from functools import wraps
2 |
3 |
4 | def duplicate_result_deco(f):
5 | pass
6 |
7 |
8 | def multiply_result_deco(n):
9 | pass
10 |
11 |
12 | @duplicate_result_deco
13 | def duplicated_square(x):
14 | return x ** 2
15 |
16 |
17 | @multiply_result_deco(2)
18 | def multiply2_square(x):
19 | return x ** 2
20 |
21 |
22 | @multiply_result_deco(3)
23 | def multiply3_square(x):
24 | return x ** 2
25 |
26 |
27 | class CustomException(Exception):
28 | pass
29 |
30 |
31 | def retry_n_times(n):
32 | """Decorator catches `CustomException` nad retries running function ntimes
33 | every time it displays `attempt=.
34 | After n times it rerises exception."""
35 |
36 |
37 | @retry_n_times(3)
38 | def raise_custom_exc():
39 | raise CustomException()
40 |
41 |
42 | if __name__ == '__main__':
43 | assert duplicated_square(3) == 18
44 | assert duplicated_square.__name__ == 'duplicated_square'
45 | assert multiply2_square(3) == duplicated_square(3)
46 | assert multiply2_square.__name__ == 'multiply2_square'
47 | assert multiply3_square(3) == 27
48 | assert multiply3_square.__name__ == 'multiply3_square'
49 |
50 | raise_custom_exc()
51 |
--------------------------------------------------------------------------------
/600 - Functions/decorator_exercise_solution.py:
--------------------------------------------------------------------------------
1 | from functools import wraps
2 |
3 |
4 | def duplicate_result_deco(f):
5 | @wraps(f)
6 | def inner_func(*args, **kwargs):
7 | return f(*args, **kwargs) * 2
8 |
9 | return inner_func
10 |
11 |
12 | def multiply_result_deco(n):
13 | def inner_deco(f):
14 | @wraps(f)
15 | def inner_func(*args, **kwargs):
16 | return f(*args, **kwargs) * n
17 |
18 | return inner_func
19 |
20 | return inner_deco
21 |
22 |
23 | @duplicate_result_deco
24 | def duplicated_square(x):
25 | return x ** 2
26 |
27 |
28 | @multiply_result_deco(2)
29 | def multiply2_square(x):
30 | return x ** 2
31 |
32 |
33 | @multiply_result_deco(3)
34 | def multiply3_square(x):
35 | return x ** 2
36 |
37 |
38 | class CustomException(Exception):
39 | pass
40 |
41 |
42 | def retry_n_times(n):
43 | """Decorator catches `CustomException` nad retries running function ntimes
44 | every time it displays `attempt=.
45 | After n times it rerises exception."""
46 | def inner_deco(f):
47 | @wraps(f)
48 | def inner_func(*args, **kwargs):
49 | attempt = 1
50 | while True:
51 | try:
52 | print(f'{attempt=}')
53 | return f(*args, **kwargs)
54 | except CustomException:
55 | if attempt >= n:
56 | raise
57 | attempt += 1
58 |
59 | return inner_func
60 |
61 | return inner_deco
62 |
63 |
64 | @retry_n_times(3)
65 | def raise_custom_exc():
66 | raise CustomException()
67 |
68 |
69 | if __name__ == '__main__':
70 | assert duplicated_square(3) == 18
71 | assert duplicated_square.__name__ == 'duplicated_square'
72 | assert multiply2_square(3) == duplicated_square(3)
73 | assert multiply2_square.__name__ == 'multiply2_square'
74 | assert multiply3_square(3) == 27
75 | assert multiply3_square.__name__ == 'multiply3_square'
76 |
77 | raise_custom_exc()
78 |
--------------------------------------------------------------------------------
/600 - Functions/default_arguments.py:
--------------------------------------------------------------------------------
1 | def repeat(text, times=1):
2 | return text * times
3 |
4 |
5 | print(repeat("foo", 2))
6 | print(repeat("foo"))
7 |
--------------------------------------------------------------------------------
/600 - Functions/default_mutable.py:
--------------------------------------------------------------------------------
1 | print('start loading code')
2 |
3 |
4 | def append_1_wrong(l=list()):
5 | print(f'Wrong Before {l}')
6 | l.append(1)
7 | print(f'Wrong After {l}')
8 |
9 |
10 | def append_1_ok(l=None):
11 | l = list() if l is None else l
12 | print(f'Good Before {l}')
13 | l.append(1)
14 | print(f'Good After {l}')
15 |
16 |
17 | if __name__ == '__main__':
18 | append_1_wrong()
19 | append_1_wrong()
20 |
21 | append_1_ok()
22 | append_1_ok()
23 |
--------------------------------------------------------------------------------
/600 - Functions/function_args.py:
--------------------------------------------------------------------------------
1 | def get_all_args(*args):
2 | print(args)
3 |
4 |
5 | def get_all_kwargs(**kwargs):
6 | print(kwargs)
7 |
8 |
9 | def mixed_model(var1, *remaining_args, kwarg1, **remining_kwargs):
10 | print(var1)
11 | print(remaining_args)
12 | print(kwarg1)
13 | print(remining_kwargs)
14 |
15 |
16 | def real_life_example(arg1, arg2, *, confusing_parameter=False):
17 | """confusing parameter has to be ALWAYS passed explicitly"""
18 |
19 |
20 | if __name__ == '__main__':
21 | get_all_args('val1', 'val2')
22 | # get_all_args('val1', some_fun_var='val2')
23 | # get_all_kwargs('val1')
24 | get_all_kwargs(some_fun_var='val1', some_fun_var2='val2')
25 |
26 | mixed_model('val1', 'val2', kwarg1=1)
27 |
28 | # real_life_example('val1', 'val2', True)
29 | real_life_example('val1', 'val2', confusing_parameter=True)
30 |
--------------------------------------------------------------------------------
/600 - Functions/global_variables.py:
--------------------------------------------------------------------------------
1 | var = 'global value'
2 |
3 |
4 | def fun_with_local_var():
5 | var = 'local value'
6 | print(var)
7 |
8 |
9 | def fun_with_global_var():
10 | # global var
11 | print(var)
12 | # var = 'local value'
13 |
14 |
15 | if __name__ == '__main__':
16 | fun_with_global_var()
17 |
18 | fun_with_local_var()
19 |
--------------------------------------------------------------------------------
/600 - Functions/greetings.py:
--------------------------------------------------------------------------------
1 | def greetings(from_, to):
2 | print(f"{from_} greets {to}")
3 |
4 |
5 | greetings("Alice", "Bob")
6 | greetings("Alice", to="Bob")
7 | greetings(from_="Alice", to="Bob")
8 | greetings(to="Bob", from_="Alice")
9 |
10 | tuple_parameters = ("Alice", "Bob")
11 | greetings(*tuple_parameters)
12 |
13 | dict_parameters = {
14 | "from_": "Alice",
15 | "to": "Bob"
16 | }
--------------------------------------------------------------------------------
/600 - Functions/greetings_passing_args_and_kwargs.py:
--------------------------------------------------------------------------------
1 | def greetings(from_, to):
2 | print(f"{from_} greets {to}")
3 |
4 |
5 | tuple_parameters = ("Alice", "Bob")
6 | greetings(*tuple_parameters)
7 |
8 | dict_parameters = {
9 | "from_": "Alice",
10 | "to": "Bob"
11 | }
12 | greetings(**dict_parameters)
13 |
--------------------------------------------------------------------------------
/600 - Functions/nonlocal_variables.py:
--------------------------------------------------------------------------------
1 | def function_factory():
2 | x = 1
3 |
4 | def inner_function():
5 | # nonlocal x
6 | print(x)
7 | # x += 1
8 |
9 | return inner_function
10 |
11 |
12 | if __name__ == '__main__':
13 | f = function_factory()
14 | f()
15 | f()
16 |
--------------------------------------------------------------------------------
/700 - Generators and Itertools/README.md:
--------------------------------------------------------------------------------
1 | #Generators and Itertools
2 |
3 | ## Generators
4 |
5 | ```python
6 | def generate_consecutive_n(n):
7 | for i in range(n):
8 | yield i
9 |
10 | type(generate_consecutive_n)
11 | #
12 | gen = generate_consecutive_n(3)
13 | type(gen)
14 | #
15 | next(gen)
16 | # 0
17 | next(gen)
18 | # 1
19 | next(gen)
20 | # 2
21 | type(gen)
22 | #
23 | next(gen)
24 | # StopIteration
25 | next(gen)
26 | # StopIteration
27 | gen = generate_consecutive_n(3)
28 | next(gen)
29 | # 0
30 | list(gen)
31 | # [1, 2]
32 | list(gen)
33 | # []
34 | next(gen)
35 | # StopIteration
36 | ```
37 |
38 | Exercise: [generators.py](generators.py)
39 |
40 | ## [Itertools](https://docs.python.org/3/library/itertools.html)
41 | ```python
42 | numbers = list(range(10))
43 | even_numbers = numbers[::2]
44 | odd_numbers = numbers[1::2]
45 |
46 | assert all(not n % 2 for n in even_numbers)
47 | assert all(n % 2 for n in odd_numbers)
48 | assert not any(n % 2 for n in even_numbers)
49 |
50 | # alphabet dict
51 | d = dict(zip('abcd', range(1, 5)))
52 | print(d)
53 | # {'a': 1, 'b': 2, 'c': 3, 'd': 4}
54 |
55 | list(zip('abcd', range(1000)))
56 | from itertools import zip_longest
57 | list(zip_longest('abcd', range(1000), fillvalue='-'))
58 | ```
59 |
60 | Exercise: [generate_alphabet.py](generate_alphabet.py)
61 |
62 | ## Coroutines → AsyncIO
63 | Example: [two_way_generators.py](two_way_generators.py)
64 |
65 | Preparation to understand AsyncIO basics:
66 | * [YT: How Do Python Coroutines Work?]
67 | * [YT: Build Your Own Async]
68 |
69 |
70 |
71 | [How to Use Generators and yield in Python]: https://realpython.com/introduction-to-python-generators/
72 | [YT: How Do Python Coroutines Work?]: https://www.youtube.com/watch?v=7sCu4gEjH5I
73 | [YT: Build Your Own Async]: https://www.youtube.com/watch?v=Y4Gt3Xjd7G8
74 | [Coroutines and Tasks]: https://docs.python.org/3/library/asyncio-task.html
75 | [Async IO in Python: A Complete Walkthrough]: https://realpython.com/async-io-python/
76 |
--------------------------------------------------------------------------------
/700 - Generators and Itertools/generate_alphabet.py:
--------------------------------------------------------------------------------
1 | def get_alphabet_dict():
2 | """
3 | Tip:
4 | ord('a') == 97
5 | chr(97) == 'a'
6 | :return: dict(a=1, b=2, ..., z=26)
7 | """
8 |
9 |
10 | if __name__ == '__main__':
11 |
12 | # Exercise: make it work
13 | alphabet_dict = get_alphabet_dict()
14 | assert alphabet_dict['a'] == 1
15 | assert alphabet_dict['z'] == 26
16 | assert list(alphabet_dict.values()) == list(range(1, 27))
17 |
--------------------------------------------------------------------------------
/700 - Generators and Itertools/generate_alphabet_solution.py:
--------------------------------------------------------------------------------
1 | def get_alphabet_dict():
2 | """
3 | Tip:
4 | ord('a') == 97
5 | chr(97) == 'a'
6 | :return: dict(a=1, b=2, ..., z=26)
7 | """
8 | return dict(
9 | (chr(number), number - ord('a') + 1)
10 | for number in range(ord('a'), ord('z') + 1)
11 | )
12 |
13 |
14 | if __name__ == '__main__':
15 |
16 | # Exercise: make it work
17 | alphabet_dict = get_alphabet_dict()
18 | assert alphabet_dict['a'] == 1
19 | assert alphabet_dict['z'] == 26
20 | assert list(alphabet_dict.values()) == list(range(1, 27))
21 |
--------------------------------------------------------------------------------
/700 - Generators and Itertools/generators.py:
--------------------------------------------------------------------------------
1 | from typing import Generator
2 |
3 |
4 | def identify_generator(elements) -> Generator:
5 | yield from elements
6 |
7 |
8 | def multiply_generator(elements, factor=1) -> Generator:
9 | """Return all values multiplied by factor"""
10 |
11 |
12 | def custom_chain(*lists) -> Generator:
13 | """Merge all lists form tuple to one long generator"""
14 |
15 |
16 | def limit(l, *, n) -> Generator:
17 | """Return first n elements from l"""
18 |
19 |
20 | def cycle(l) -> Generator:
21 | """Generate infinite series out of given list"""
22 |
23 |
24 | if __name__ == '__main__':
25 | short_list = [1, 2, 10]
26 |
27 | id_gen = identify_generator(short_list)
28 | assert list(id_gen) == short_list
29 |
30 | # Exercise1: make it works
31 | multiplied_gen = multiply_generator(short_list, 10)
32 | assert list(multiplied_gen) == [10, 20, 100]
33 | assert type(multiplied_gen) is type(id_gen) # make sure you've created generator, not just returned list
34 |
35 | # Exercise2: make it works
36 | chained = custom_chain(short_list, [20], [30, 42])
37 | assert list(chained) == [1, 2, 10, 20, 30, 42]
38 | assert type(chained) is type(id_gen) # make sure you've created generator, not just returned list
39 |
40 | # Exercise3: make it works
41 | limited = limit(short_list, n=2)
42 | assert list(limited) == [1, 2]
43 | assert type(limited) is type(id_gen) # make sure you've created generator, not just returned list
44 |
45 | # Exercise4: make it works
46 | cycled = list(limit(cycle(short_list), n=900))
47 | assert cycled[:3] == [1, 2, 10]
48 | assert cycled[-3:] == [1, 2, 10]
49 | assert len(cycled) == 900
50 |
--------------------------------------------------------------------------------
/700 - Generators and Itertools/generators_solution.py:
--------------------------------------------------------------------------------
1 | from typing import Generator
2 |
3 |
4 | def identify_generator(elements) -> Generator:
5 | yield from elements
6 |
7 |
8 | def multiply_generator(elements, factor=1) -> Generator:
9 | """Return all values multiplied by factor"""
10 | yield from (e * factor for e in elements)
11 |
12 |
13 | def custom_chain(*lists) -> Generator:
14 | """Merge all lists form tuple to one long generator"""
15 | for l in lists:
16 | yield from l
17 |
18 |
19 | def limit(l, *, n) -> Generator:
20 | """Return first n elements from l"""
21 | g = (i for i in l)
22 | for _ in range(n):
23 | yield next(g)
24 |
25 |
26 | def cycle(l) -> Generator:
27 | """Generate inifinite serie out of given list"""
28 | while True:
29 | yield from l
30 |
31 |
32 | if __name__ == '__main__':
33 | short_list = [1, 2, 10]
34 |
35 | id_gen = identify_generator(short_list)
36 | assert list(id_gen) == short_list
37 |
38 | # Exercise1: make it works
39 | multiplied_gen = multiply_generator(short_list, 10)
40 | assert list(multiplied_gen) == [10, 20, 100]
41 | assert type(multiplied_gen) is type(id_gen) # make sure you've created generator, not just returned list
42 |
43 | # Exercise2: make it works
44 | chained = custom_chain(short_list, [20], [30, 42])
45 | assert list(chained) == [1, 2, 10, 20, 30, 42]
46 | assert type(chained) is type(id_gen) # make sure you've created generator, not just returned list
47 |
48 | # Exercise3: make it works
49 | limited = limit(short_list, n=2)
50 | assert list(limited) == [1, 2]
51 | assert type(limited) is type(id_gen) # make sure you've created generator, not just returned list
52 |
53 | # Exercise4: make it works
54 | cycled = list(limit(cycle(short_list), n=900))
55 | assert cycled[:3] == [1, 2, 10]
56 | assert cycled[-3:] == [1, 2, 10]
57 | assert len(cycled) == 900
58 |
--------------------------------------------------------------------------------
/700 - Generators and Itertools/two_way_generators.py:
--------------------------------------------------------------------------------
1 | from random import random
2 |
3 |
4 | def random_numbers():
5 | print('start generator')
6 | while True:
7 | val = random()
8 | print(f'will yield {val}')
9 | yield val
10 |
11 |
12 | def run_random_numbers():
13 | print(f'{random_numbers=}')
14 | rnd_gen = random_numbers()
15 | print(f'{rnd_gen=}')
16 | print(f'{next(rnd_gen)=}')
17 | print(f'{next(rnd_gen)=}')
18 |
19 | # but we can have two-way communication
20 | print(f'{rnd_gen.send(None)=}')
21 | print(f'{rnd_gen.send(42)=}')
22 | # rnd_gen.throw(Exception)
23 | # rnd_gen.close()
24 | # next(rnd_gen)
25 |
26 |
27 | def inout_gen():
28 | print('init')
29 | ret_val = None
30 | while True:
31 | x = yield ret_val
32 | if x is not None:
33 | ret_val = x
34 |
35 |
36 | def run_input_gen():
37 | inout_g = inout_gen()
38 | next(inout_g)
39 |
40 | print(f'{next(inout_g)}')
41 | print(f'{inout_g.send(22)}')
42 | print(f'{next(inout_g)}')
43 |
44 |
45 | def exercise_gen(ret_val, times):
46 | """Return `ret_value` `times` times.
47 | If generator will receive some value from outside, update `ret_value`"""
48 |
49 |
50 | def exercise1():
51 | """Make it pass"""
52 | g1 = exercise_gen(42, 3)
53 | assert next(g1) == 42
54 | assert g1.send('new val') == 'new val'
55 | assert next(g1) == 'new val'
56 | try:
57 | next(g1)
58 | except StopIteration:
59 | # ok
60 | pass
61 | else:
62 | raise Exception('Generator should be invalid')
63 |
64 |
65 | def exercise2():
66 | """Update `exercise_gen`, so it will ignore all exceptions"""
67 | g1 = exercise_gen("I'll ignore errors", 300)
68 | assert next(g1) == "I'll ignore errors"
69 | assert g1.send('new val') == 'new val'
70 | assert g1.throw(Exception) == 'new val'
71 | assert next(g1) == 'new val'
72 |
73 |
74 | if __name__ == '__main__':
75 | run_random_numbers()
76 | run_input_gen()
77 | exercise1()
78 | exercise2()
79 |
--------------------------------------------------------------------------------
/700 - Generators and Itertools/two_way_generators_solution.py:
--------------------------------------------------------------------------------
1 | from random import random
2 |
3 |
4 | def random_numbers():
5 | print('start generator')
6 | while True:
7 | val = random()
8 | print(f'will yield {val}')
9 | yield val
10 |
11 |
12 | def run_random_numbers():
13 | print(f'{random_numbers=}')
14 | rnd_gen = random_numbers()
15 | print(f'{rnd_gen=}')
16 | print(f'{next(rnd_gen)=}')
17 | print(f'{next(rnd_gen)=}')
18 |
19 | # but we can have two way communication
20 | print(f'{rnd_gen.send(None)=}')
21 | print(f'{rnd_gen.send(42)=}')
22 | # rnd_gen.throw(Exception)
23 | # rnd_gen.close()
24 | # next(rnd_gen)
25 |
26 |
27 | def inout_gen():
28 | print('init')
29 | ret_val = None
30 | while True:
31 | x = yield ret_val
32 | if x is not None:
33 | ret_val = x
34 |
35 |
36 | def run_input_gen():
37 | inout_g = inout_gen()
38 | next(inout_g)
39 |
40 | print(f'{next(inout_g)}')
41 | print(f'{inout_g.send(22)}')
42 | print(f'{next(inout_g)}')
43 |
44 |
45 | def exercise_gen(ret_val, times):
46 | """Return `ret_value` `times` times.
47 | If generator will receive some value from outside, update `ret_value`"""
48 | x = None
49 | for _ in range(times):
50 | try:
51 | x = yield ret_val
52 | except Exception:
53 | pass
54 | if x is not None:
55 | ret_val = x
56 |
57 |
58 | def exercise1():
59 | """Make it pass"""
60 | g1 = exercise_gen(42, 3)
61 | assert next(g1) == 42
62 | assert g1.send('new val') == 'new val'
63 | assert next(g1) == 'new val'
64 | try:
65 | next(g1)
66 | except StopIteration:
67 | # ok
68 | pass
69 | else:
70 | raise Exception('Generator should be invalid')
71 |
72 |
73 | def exercise2():
74 | """Update `exercise_gen`, so it will ignore all exceptions"""
75 | g1 = exercise_gen("I'll ignore errors", 300)
76 | assert next(g1) == "I'll ignore errors"
77 | assert g1.send('new val') == 'new val'
78 | assert g1.throw(Exception) == 'new val'
79 | assert next(g1) == 'new val'
80 |
81 |
82 | if __name__ == '__main__':
83 | run_random_numbers()
84 | run_input_gen()
85 | exercise1()
86 | exercise2()
87 |
--------------------------------------------------------------------------------
/800 - Classes 202/README.md:
--------------------------------------------------------------------------------
1 | # Classes
2 |
3 | ## [descriptors.py](./descriptors.py)
4 |
5 | ## Problem with class size
6 | [heavy_instances.py](heavy_instances.py)
7 |
8 | ## Inheretance and duck typing
9 | * [Supercharge Your Classes With Python super()]
10 | * [Implementing an Interface in Python]
11 |
12 | ### Examples
13 | * [super_example.py](super_example.py)
14 |
15 | ## Overriding dunder methods
16 | * [dunders.py](dunders.py)
17 | * [dag.py](dag.py)
18 | * [duck_typing.py](duck_typing.py)
19 |
20 |
21 | [Supercharge Your Classes With Python super()]: https://realpython.com/python-super/
22 | [Implementing an Interface in Python]: https://realpython.com/python-interface/
23 |
--------------------------------------------------------------------------------
/800 - Classes 202/dag.py:
--------------------------------------------------------------------------------
1 | class Dag:
2 | """https://en.wikipedia.org/wiki/Directed_acyclic_graph
3 | not https://www.youtube.com/watch?v=zH64dlgyydM
4 | """
5 | def __init__(self):
6 | self._allowed_pairs = set()
7 | self._recently_added_node = None
8 |
9 | def __call__(self, entry_point):
10 | self._allowed_pairs.add((None, entry_point))
11 | self._recently_added_node = entry_point
12 | return self
13 |
14 | def __rshift__(self, node):
15 | """There is direct edge from self._recently_added_node to node
16 | """
17 |
18 | def is_direct_path(self, deriving, to):
19 | return (deriving, to) in self._allowed_pairs
20 |
21 | def is_start_node(self, node):
22 | return self.is_direct_path(None, node)
23 |
24 |
25 | if __name__ == '__main__':
26 | dag = Dag()
27 |
28 | # only one node (1)
29 | dag(1)
30 | assert dag.is_start_node(1)
31 |
32 | # Exercise1: add edge 1 -> 2
33 | dag(1) >> 2
34 | assert dag.is_direct_path(1, 2)
35 | assert not dag.is_direct_path(2, 1)
36 | assert not dag.is_start_node(2)
37 |
38 | dag(0) >> 1
39 | assert dag.is_start_node(0)
40 | assert dag.is_start_node(1)
41 | assert dag.is_direct_path(0, 1)
42 |
43 | # Exercise2: Make sure that >> operator can be chained
44 | dag(0) >> 1.1 >> 2
45 | dag(0) >> 1.2 >> 2 >> 3 >> 4
46 | assert dag.is_direct_path(0, 1.1)
47 | assert dag.is_direct_path(0, 1.2)
48 | assert dag.is_direct_path(1.1, 2)
49 | assert dag.is_direct_path(1.2, 2)
50 |
51 | # Exercise3: reimplement `is_direct_path` so it will be able to detect path of unlimited length
52 | assert dag.is_direct_path(0, 1, 2, 3, 4)
53 |
54 | # *Exercise4: Bonus (1 -> 2.1), (1 -> 2.2), (2.1 -> 3), (2.2 -> 3)
55 | dag(1) >> (2.1, 2.2) >> 3
56 | assert dag.is_direct_path(1, 2.1, 3)
57 | assert dag.is_direct_path(1, 2.2, 3)
58 |
59 | # *Exercise5:
60 | assert dag.is_direct_path(1, (2.1, 2.2), 3)
61 |
--------------------------------------------------------------------------------
/800 - Classes 202/dag_solution.py:
--------------------------------------------------------------------------------
1 | class Dag:
2 | """https://en.wikipedia.org/wiki/Directed_acyclic_graph
3 | not https://www.youtube.com/watch?v=zH64dlgyydM
4 | """
5 | def __init__(self):
6 | self._allowed_pairs = set()
7 | self._recently_added_node = None
8 |
9 | def __call__(self, entry_point):
10 | self._allowed_pairs.add((None, entry_point))
11 | self._recently_added_node = entry_point
12 | return self
13 |
14 | def __rshift__(self, node):
15 | """There is direct edge from self._recently_added_node to node
16 | """
17 | self._allowed_pairs.add((self._recently_added_node, node))
18 | self._recently_added_node = node
19 | return self
20 |
21 | # def is_direct_path(self, deriving, to):
22 | def is_direct_path(self, *path):
23 | for deriving, to in zip(path, path[1:]):
24 | if not (deriving, to) in self._allowed_pairs:
25 | return False
26 | return True
27 | # return (deriving, to) in self._allowed_pairs
28 |
29 | def is_start_node(self, node):
30 | return self.is_direct_path(None, node)
31 |
32 |
33 | if __name__ == '__main__':
34 | dag = Dag()
35 |
36 | # only one node (1)
37 | dag(1)
38 | assert dag.is_start_node(1)
39 |
40 | # Exercise1: add edge 1 -> 2
41 | dag(1) >> 2
42 | assert dag.is_direct_path(1, 2)
43 | assert not dag.is_direct_path(2, 1)
44 | assert not dag.is_start_node(2)
45 |
46 | dag(0) >> 1
47 | assert dag.is_start_node(0)
48 | assert dag.is_start_node(1)
49 | assert dag.is_direct_path(0, 1)
50 |
51 | # Exercise2: Make sure that >> operator can be chained
52 | dag(0) >> 1.1 >> 2
53 | dag(0) >> 1.2 >> 2 >> 3 >> 4
54 | assert dag.is_direct_path(0, 1.1)
55 | assert dag.is_direct_path(0, 1.2)
56 | assert dag.is_direct_path(1.1, 2)
57 | assert dag.is_direct_path(1.2, 2)
58 |
59 | # Exercise3: reimplement `is_direct_path` so it will be able to detect path of unlimited length
60 | assert dag.is_direct_path(0, 1, 2, 3, 4)
61 |
62 | # *Exercise4: Bonus (1 -> 2.1), (1 -> 2.2), (2.1 -> 3), (2.2 -> 3)
63 | dag(1) >> (2.1, 2.2) >> 3
64 | assert dag.is_direct_path(1, 2.1, 3)
65 | assert dag.is_direct_path(1, 2.2, 3)
66 |
67 | # *Exercise5:
68 | assert dag.is_direct_path(1, (2.1, 2.2), 3)
69 |
--------------------------------------------------------------------------------
/800 - Classes 202/descriptors.py:
--------------------------------------------------------------------------------
1 | """https://realpython.com/python-descriptors/
2 | """
3 |
4 |
5 | class NonNegativeValue:
6 | def __set_name__(self, owner, name):
7 | self.name = f'_{name}'
8 |
9 | def __get__(self, obj, type=None) -> object:
10 | return obj.__dict__.get(self.name) or 0
11 |
12 | def __set__(self, obj, value) -> None:
13 | obj.__dict__[self.name] = value
14 |
15 |
16 | class Ball:
17 | def __init__(self, weight, diameter):
18 | self._weight = weight
19 | self._diameter = diameter
20 |
21 | weight = NonNegativeValue()
22 | diameter = NonNegativeValue()
23 |
24 |
25 | if __name__ == '__main__':
26 | a = Ball(weight=10, diameter=5)
27 | print(a.weight)
28 | print(a.diameter)
29 | a.weight = 11
30 | print(a.weight)
31 |
--------------------------------------------------------------------------------
/800 - Classes 202/duck_typing.py:
--------------------------------------------------------------------------------
1 | import abc
2 | from collections import abc as cabc
3 |
4 |
5 | class DummyIterable:
6 | def __iter__(self):
7 | yield 1
8 | yield 2
9 | yield 3
10 |
11 |
12 | class GenericAirplane(metaclass=abc.ABCMeta):
13 | @classmethod
14 | def __subclasshook__(cls, subclass):
15 | return (hasattr(subclass, 'flight') and callable(subclass.flight))
16 |
17 |
18 | class A10:
19 | def flight(self):
20 | # https://www.youtube.com/watch?v=NvIJvPj_pjE
21 | return 'brrrrrrrrrrrrrt'
22 |
23 |
24 | if __name__ == '__main__':
25 | l = [1, 2, 3]
26 | assert isinstance(l, cabc.Collection)
27 | assert isinstance(l, cabc.Iterable)
28 |
29 | dummy_iterable = DummyIterable()
30 | print(list(dummy_iterable))
31 | for e in dummy_iterable:
32 | print(e)
33 | assert isinstance(dummy_iterable, cabc.Iterable)
34 |
35 | # GenericAirplane
36 | a10 = A10()
37 | assert isinstance(a10, GenericAirplane)
38 |
--------------------------------------------------------------------------------
/800 - Classes 202/dunders.py:
--------------------------------------------------------------------------------
1 | class Strange:
2 | def __init__(self, idx, text):
3 | self.idx = idx
4 | self.text = text
5 |
6 | def __str__(self):
7 | return f'{self.text[:self.idx]}({self.text[self.idx]}){self.text[self.idx + 1:]}'
8 |
9 | def __add__(self, other):
10 | if isinstance(other, int):
11 | return Strange(
12 | idx=self.idx + other,
13 | text=self.text,
14 | )
15 | raise TypeError(f"unsupported operand type(s) for +: '{type(self).__name__}' and '{type(other).__name__}'")
16 |
17 | def __len__(self):
18 | return len(self.text)
19 |
20 | def __iter__(self):
21 | yield self.idx
22 | yield from self.text
23 |
24 | def __next__(self):
25 | if self.idx < len(self.text):
26 | val = self.text[self.idx]
27 | self.idx += 1
28 | return val
29 | raise StopIteration()
30 |
31 | # def __getattribute__(self, item):
32 | # pass
33 |
34 | def __getattr__(self, item):
35 | return f'Fake {item} attribute'
36 |
37 | def __getitem__(self, item):
38 | print(f'item {item} accessed by []')
39 |
40 | def __sub__(self, other):
41 | print('hehe')
42 |
43 |
44 | if __name__ == '__main__':
45 | strange = Strange(2, 'what now?')
46 | print(strange)
47 | # print(strange + Strange(3, 'and then?'))
48 | print(strange + 3)
49 | print(hash(strange))
50 | print(len(strange))
51 | print(list(strange))
52 |
53 | print(next(strange))
54 | strange += 5
55 | print(strange)
56 | print(next(strange))
57 | try:
58 | print(next(strange))
59 | except StopIteration:
60 | print('Iterator exhausted')
61 |
62 | print(strange.idx, strange.missing_attr)
63 |
64 | print(strange[1])
65 | print(strange[1:-1:3])
66 |
--------------------------------------------------------------------------------
/800 - Classes 202/heavy_instances.py:
--------------------------------------------------------------------------------
1 | """
2 | Result (macOS, python3.9):
3 | Class dict used 268.587008 MB.
4 | Class EventSlots used 107.298816 MB.
5 | Class EventTuple used 123.6992 MB.
6 | Class Event used 218.939392 MB.
7 | Class EventDataclass used 219.15648 MB.
8 | """
9 | import os
10 | import random
11 | from collections import namedtuple
12 | from multiprocessing import Pool
13 | from dataclasses import dataclass
14 |
15 | import psutil
16 |
17 | DNA_BASES = 'ATCG'
18 |
19 |
20 | class Event:
21 | def __init__(self, dna_base, value):
22 | self.dna_base = dna_base
23 | self.value = value
24 |
25 |
26 | @dataclass
27 | class EventDataclass:
28 | # https://docs.python.org/3/library/dataclasses.html
29 | dna_base: str
30 | value: int
31 |
32 |
33 | class EventSlots:
34 | # https://book.pythontips.com/en/latest/__slots__magic.html
35 | # https://docs.python.org/3/reference/datamodel.html?highlight=__slots__#object.__slots__
36 | __slots__ = ['dna_base', 'value']
37 |
38 | def __init__(self, dna_base, value):
39 | self.dna_base = dna_base
40 | self.value = value
41 |
42 |
43 | # https://www.geeksforgeeks.org/namedtuple-in-python/
44 | EventTuple = namedtuple('EventTuple', ['dna_base', 'value'])
45 |
46 |
47 | def generate_events(event_cls: type, events_no: int = 10 ** 6):
48 | events = [
49 | event_cls(dna_base=random.choice(DNA_BASES), value=i)
50 | for i in range(events_no)
51 | ]
52 | process = psutil.Process(os.getpid())
53 | process_size = process.memory_info().rss / 10 ** 6 # in MB
54 | print(f"Class {event_cls.__name__} used {process_size} MB.")
55 |
56 |
57 | if __name__ == '__main__':
58 | with Pool(5) as p:
59 | p.map(generate_events, [dict, Event, EventSlots, EventTuple, EventDataclass])
60 |
--------------------------------------------------------------------------------
/800 - Classes 202/super_example.py:
--------------------------------------------------------------------------------
1 | class Rectangle:
2 | def __init__(self, length, width):
3 | self.length = length
4 | self.width = width
5 |
6 | def area(self):
7 | return self.length * self.width
8 |
9 | def perimeter(self):
10 | return 2 * self.length + 2 * self.width
11 |
12 |
13 | class Square(Rectangle):
14 | def __init__(self, length):
15 | super().__init__(length, length)
16 |
17 |
18 | class Cube(Square):
19 | def surface_area(self):
20 | # face_area = super().area()
21 | face_area = self.area()
22 | return face_area * 6
23 |
24 | def volume(self):
25 | # face_area = super().area()
26 | face_area = self.area()
27 | return face_area * self.length
28 |
29 |
30 | class Triangle:
31 | def __init__(self, base, height):
32 | self.base = base
33 | self.height = height
34 |
35 | def area(self):
36 | return 0.5 * self.base * self.height
37 |
38 |
39 | class RightPyramid(Square, Triangle):
40 | def __init__(self, base, slant_height):
41 | self.base = base
42 | self.slant_height = slant_height
43 | super().__init__(self.base)
44 |
45 | def area(self):
46 | base_area = super().area()
47 | perimeter = super().perimeter()
48 | return 0.5 * perimeter * self.slant_height + base_area
49 |
50 |
51 | if __name__ == '__main__':
52 | print(RightPyramid.__mro__)
53 | pyramid = RightPyramid(2, 4)
54 | pyramid.area()
55 |
--------------------------------------------------------------------------------
/850 - Metaprogramming/README.md:
--------------------------------------------------------------------------------
1 | # Metaprogramming
2 |
3 | > Metaprogramming is a programming technique in which computer programs have the ability to treat other programs
4 | > as their data. It means that a program can be designed to read, generate, analyze or transform other programs,
5 | > and even modify itself while running.
6 | >
7 | [Metaprogramming - wiki]
8 |
9 |
10 | [Metaprogramming - wiki]: https://en.wikipedia.org/wiki/Metaprogramming
11 |
12 | ## Example
13 | [meta_decorator.py](./meta_decorator.py)
14 |
--------------------------------------------------------------------------------
/850 - Metaprogramming/meta_decorator.py:
--------------------------------------------------------------------------------
1 | def deco(f):
2 | def inner_func(self, *args, **kwargs):
3 | print("will be run with deco")
4 | return f(self, *args, **kwargs)
5 |
6 | return inner_func
7 |
8 |
9 | class Meta(type):
10 | def __init__(cls, name, bases, dct):
11 | for attr_name in dir(cls):
12 | if not attr_name.startswith('__') and callable(getattr(cls, attr_name)):
13 | setattr(cls, attr_name, deco(getattr(cls, attr_name)))
14 |
15 |
16 | class A:
17 | def a(self):
18 | print("a")
19 |
20 |
21 | class B(A, metaclass=Meta):
22 | def b(self):
23 | print("b")
24 |
25 |
26 | if __name__ == '__main__':
27 | b = B()
28 | b.a()
29 | b.b()
30 |
--------------------------------------------------------------------------------
/850 - os module/python_path.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 |
4 | if __name__ == '__main__':
5 | print('== base ==')
6 | print(__file__)
7 | current_dir = os.path.dirname(__file__)
8 | print(current_dir)
9 |
10 | # on Windows it mixes slashes and backslashes
11 | print('== raw modified ==')
12 | print(os.path.join(current_dir, '..'))
13 | print(os.path.join(current_dir, 'subdir1', 'subdir2'))
14 |
15 | # `os.path.abspath` will do the job
16 | print('== abspath wrapped ==')
17 | print(os.path.abspath(os.path.join(current_dir, '..')))
18 | print(os.path.abspath(os.path.join(current_dir, 'subdir1', 'subdir2')))
19 |
--------------------------------------------------------------------------------
/900 - app/README.md:
--------------------------------------------------------------------------------
1 | # SQLAlchemy
2 |
3 | ## New model `Post` (blog post)
4 | `Post` should have
5 | * `id`
6 | * `content`
7 | * `creation_date` (default now)
8 |
9 | What type should we use for blog post?
10 | [Available SQLAlchemy field types](https://docs.sqlalchemy.org/en/13/core/type_basics.html?highlight=datetime#generic-types)
11 |
12 | ```python
13 | from datetime import datetime, timezone
14 | datetime.now()
15 | datetime.now(tz=timezone.utc)
16 | ```
17 |
18 | **Always use utc timezone in backend code**
19 |
20 | But remember, [datetime handling is mess](https://zachholman.com/talk/utc-is-enough-for-everyone-right)
21 | > So you’ve got a bunch of scientist types around 1960 who are like, hey, time is all screwy we should totes make a standard. And some of them spoke English, and some of them spoke French, which, of course, is the cause of so much conflict over so many generations. (In hindsight, maybe we should have split all those troublemakers up from the start.)
22 |
23 | > The English-speaking folk were like yo, this definitely sounds like Coordinated Universal Time, boom, ship it. And the French speakers were like yeah that makes total sense! Temps Universel Coordonné DOES work out well in our language, too, ship it! Then they both looked up and realized cool, they’ve created both CUT and TUC for acronyms. Shit.
24 |
25 | > When your standard — that is expressly meant to standardize time — doesn’t even standardize on a standard acronym, well, damn, that probably doesn’t bode well for your standard.
26 |
27 | Let's see `default` datetime for `creation_date` `Column`: `datetime.now`
28 |
29 | # `User` has written the `Post` - one to many
30 |
31 | ## Look at our `User` and `Post` models in dBeaver
32 | ```
33 | test.db -> Tables -> ER Diagram
34 | ```
35 |
36 | And compare with [this](https://fmhelp.filemaker.com/help/18/fmp/en/index.html#page/FMP_Help/one-to-many-relationships.html)
37 | explanation.
38 |
39 | ## One to many in `Flask-SQLAlchemy`
40 |
41 | [One to many](https://docs.sqlalchemy.org/en/13/orm/basic_relationships.html#one-to-many)
42 |
43 | ```python
44 | class User:
45 | ...
46 | posts = relationship('Post', backref='user', lazy=True)
47 |
48 | class Post:
49 | ...
50 | user_id = Column(Integer, ForeignKey('users.id'), nullable=True)
51 | ```
52 |
53 | We edited only `post` **database table** - we've added `user_id` column.
54 |
--------------------------------------------------------------------------------
/900 - app/app/__init__.py:
--------------------------------------------------------------------------------
1 | from app.database import Session, metadata
2 | from app.posts.models import Post
3 | from app.users.models import User
4 | from app.users.accessors import get_user_by_id, get_users_by_name, dummy_func
5 |
6 |
7 | if __name__ == '__main__':
8 | session = Session()
9 |
10 | print(session.query(Post).all())
11 | print(session.query(User).all())
12 |
13 | print(get_user_by_id(1, session))
14 | print(get_users_by_name('Gary', session))
15 |
--------------------------------------------------------------------------------
/900 - app/app/config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 |
4 | class Config:
5 | db_connection_string = os.getenv('DB_CONNECTION_STRING')
6 |
--------------------------------------------------------------------------------
/900 - app/app/database/__init__.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine, MetaData
2 | from sqlalchemy.ext.declarative import declarative_base
3 | from sqlalchemy.orm import sessionmaker
4 |
5 | from app.config import Config
6 |
7 | metadata = MetaData()
8 | engine = create_engine(Config.db_connection_string)
9 | Base = declarative_base(bind=engine, metadata=metadata)
10 | Session = sessionmaker(bind=engine)
11 |
--------------------------------------------------------------------------------
/900 - app/app/database/crete_models.py:
--------------------------------------------------------------------------------
1 | from faker import Faker
2 |
3 | from app.database import Session, metadata
4 | from app.posts.models import Post
5 | from app.users.models import User
6 |
7 | fake = Faker()
8 |
9 |
10 | def create_users(users_number=10, *, session):
11 | users_to_add = list()
12 | for _ in range(users_number):
13 | new_user = User(username=fake.unique.first_name())
14 | session.add(new_user)
15 | users_to_add.append(new_user)
16 | # we don't want to call `db.session.commit()` here,
17 | # to prevent many db connections
18 | # at the same time we can't add `Posts` here,
19 | # because `User.id` will be set in database after calling `db.session.commit()`
20 | session.commit()
21 | # now user instances have assigned `User.id`
22 |
23 |
24 | def delete_all(*, session):
25 | session.query(User).delete()
26 | # TODO: delete posts here
27 | session.commit()
28 |
29 |
30 | def create_posts(n=10, *, user_id=None, session):
31 | """Add posts here"""
32 |
33 |
34 | if __name__ == '__main__':
35 | session = Session()
36 | metadata.create_all()
37 | delete_all(session=session)
38 | create_users(20, session=session)
39 |
--------------------------------------------------------------------------------
/900 - app/app/database/crete_models_extended.py:
--------------------------------------------------------------------------------
1 | from faker import Faker
2 |
3 | from app.database import Session, metadata
4 | from app.posts.models import Post
5 | from app.users.models import User
6 |
7 | fake = Faker()
8 |
9 |
10 | def create_users(users_number=10, *, posts_per_user=0, session):
11 | users_to_add = list()
12 | for _ in range(users_number):
13 | new_user = User(username=fake.unique.first_name())
14 | session.add(new_user)
15 | users_to_add.append(new_user)
16 | # we don't want to call `db.session.commit()` here,
17 | # to prevent many db connections
18 | # at the same time we can't add `Posts` here,
19 | # because `User.id` will be set in database after calling `db.session.commit()`
20 | session.commit()
21 | # now user instances have assigned `User.id`
22 |
23 | for new_user in users_to_add:
24 | create_posts(posts_per_user, user_id=new_user.id, session=session)
25 |
26 |
27 | def delete_all(*, session):
28 | session.query(User).delete()
29 | session.query(Post).delete()
30 | session.commit()
31 |
32 |
33 | def create_posts(n=10, *, user_id=None, session):
34 | for _ in range(n):
35 | p = Post(content=fake.unique.text(), user_id=user_id)
36 | session.add(p)
37 | session.commit()
38 |
39 |
40 | if __name__ == '__main__':
41 | session = Session()
42 | metadata.create_all()
43 | delete_all(session=session)
44 | create_users(20, posts_per_user=10, session=session)
45 |
--------------------------------------------------------------------------------
/900 - app/app/posts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shnela/python_course/842f94c2b0f8d73428919a3c9f397720554c30a7/900 - app/app/posts/__init__.py
--------------------------------------------------------------------------------
/900 - app/app/posts/models.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime, timezone
2 |
3 | from sqlalchemy import Column, Integer, Text, DateTime, ForeignKey
4 |
5 | from app.database import Base
6 |
7 |
8 | class Post(Base):
9 | __tablename__ = 'posts'
10 | id = Column(Integer, primary_key=True)
11 | content = Column(Text(), nullable=False)
12 | creation_date = Column(DateTime(timezone=True), nullable=False,
13 | default=lambda: datetime.now(tz=timezone.utc))
14 | # user_id = Column(Integer, ForeignKey('users.id'), nullable=True)
15 |
--------------------------------------------------------------------------------
/900 - app/app/users/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shnela/python_course/842f94c2b0f8d73428919a3c9f397720554c30a7/900 - app/app/users/__init__.py
--------------------------------------------------------------------------------
/900 - app/app/users/accessors.py:
--------------------------------------------------------------------------------
1 | __all__ = (
2 | 'get_user_by_id',
3 | 'get_users_by_name',
4 | )
5 |
6 | from app.users.models import User
7 |
8 |
9 | def get_user_by_id(user_id, session):
10 | return session.query(User).get(user_id)
11 |
12 |
13 | def get_users_by_name(username, session):
14 | return session.query(User).filter(User.username == username).all()
15 |
16 |
17 | def dummy_func():
18 | """Please don't use me"""
19 |
--------------------------------------------------------------------------------
/900 - app/app/users/models.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer, String
2 | from sqlalchemy.orm import relationship
3 |
4 | from app.database import Base
5 |
6 |
7 | class User(Base):
8 | __tablename__ = 'users'
9 | id = Column(Integer, primary_key=True)
10 | username = Column(String(80), unique=True, nullable=False)
11 | # posts = relationship('Post', backref='user', lazy=True)
12 |
--------------------------------------------------------------------------------
/900 - app/requirements.txt:
--------------------------------------------------------------------------------
1 | Faker==6.1.1
2 | psutil==5.8.0
3 | PyMySQL==1.0.2
4 | python-dateutil==2.8.1
5 | six==1.15.0
6 | SQLAlchemy==1.3.23
7 | text-unidecode==1.3
8 |
--------------------------------------------------------------------------------
/900 - app/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shnela/python_course/842f94c2b0f8d73428919a3c9f397720554c30a7/900 - app/tests/__init__.py
--------------------------------------------------------------------------------
/900 - app/tests/test_app.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from unittest import mock
4 |
5 | from app.database import Session
6 | from app.users.models import User
7 |
8 |
9 | @mock.patch('app.users.accessors.get_user_by_id')
10 | class TestApp(unittest.TestCase):
11 | @classmethod
12 | def setUpClass(cls) -> None:
13 | session = Session()
14 | session.query(User).delete()
15 | session.commit()
16 |
17 | def setUp(self) -> None:
18 | self.session = Session()
19 |
20 | def tearDown(self) -> None:
21 | pass
22 |
23 | def test_creating_user(self, get_user_mock):
24 | new_user = User(username='Frank')
25 | self.session.add(new_user)
26 | self.session.commit()
27 |
28 | user_id = new_user.id
29 |
30 | self.assertEqual('Frank', self.session.query(User).get(user_id).username)
31 |
32 |
33 | def test_creating_user2(self, get_user_mock):
34 | self.session.query(User).delete()
35 | new_user = User(username='Alex')
36 | self.session.add(new_user)
37 | self.session.commit()
38 |
39 | user_id = new_user.id
40 |
41 | self.assertEqual('Alex', self.session.query(User).get(user_id).username)
42 |
43 |
--------------------------------------------------------------------------------
/900 - app/tests/tmp.py:
--------------------------------------------------------------------------------
1 | class A:
2 | number = 1
3 |
4 | a = A()
5 | print(A.number)
6 | print(a.number)
7 | a.number = 2
8 | print(a.number)
9 | print(A.number)
10 | a.x = 555
11 | print(a.x)
12 |
--------------------------------------------------------------------------------
/950 - Personal recomendations/README.md:
--------------------------------------------------------------------------------
1 | # Recommendations
2 |
3 | ## Python
4 | * [Effective Python](https://effectivepython.com/) _(Maybe first edition was better)_
5 | * [Real Python](https://realpython.com/)
6 | * [David Beazley](https://dabeaz.com/) - excellent talks, most of them is outdated, but still worth seeing...
7 |
8 | ## General backend
9 | * [What Every Programmer Should Know About Memory](https://www.asc.tuwien.ac.at/~schoeberl/wiki/lva/seminar11/cpumemory.pdf)
10 | * [Designing Data-Intensive Applications (DDIA)](https://dataintensive.net/)
11 | * [Hussein Nasser (videos)](https://www.husseinnasser.com/p/software-engineering-videos.html)
12 |
13 | ## UX
14 | * [Don't make me think](https://www.amazon.com/Dont-Make-Think-Revisited-Usability/dp/0321965515)
15 |
16 | ## Databases
17 | * [SQL Performance Explained](https://sql-performance-explained.com/) and [meme](https://www.reddit.com/r/SQL/comments/hpjij2/sql_performance_explained_expectations_vs_reality/)
18 | * [The Internals of PostgreSQL](http://www.interdb.jp/pg/index.html)
19 |
20 | ## Not really technical
21 | * [Computer Science: Just the Useful Bits](http://justtheusefulbits.com/)
22 |
23 |
24 | ## Other tools
25 | * [Krisp (invitation link)](https://ref.krisp.ai/u/u6d3ecfb07) - Noise Cancellation Technology
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Jakub Kuszneruk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------