├── .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 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
Python versionMaintenance statusFirst releasedEnd of supportRelease schedule
3.10bugfix2020-10-052025-10PEP 619
3.9bugfix2020-10-052025-10PEP 596
3.8security2019-10-142024-10PEP 569
3.7security2018-06-272023-06-27PEP 537
2.7end-of-life2010-07-032020-01-01PEP 373
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 | --------------------------------------------------------------------------------