Треки
/
Python
Python
/
Вправи
/
Офіціант кейтерингу
Офіціант кейтерингу

Офіціант кейтерингу

Навчальна вправа

Вступ

Множина - це змінна та невпорядкована колекція хешованих обʼєктів. Елементи множини мають бути різними, дублікати не допускаються. Вони можуть містити дані різних типів і навіть вкладені структури, як-от tuple з tuples, якщо всі елементи можна хешувати. Множини також бувають у незмінному варіанті, frozensets.

Найчастіше множини використовують, щоб швидко прибрати дублікати з інших структур даних або груп елементів. Їх також застосовують для ефективних порівнянь, коли впорядкування та відстеження дублікатів не потрібні.

Як і інші типи колекцій (словники, масиви, кортежі), sets підтримують:

  • Ітерацію через for item in <set>
  • Перевірку належності через in і not in,
  • Обчислення довжини за допомогою len(), та
  • Поверхневі копії через copy()

sets не підтримують:

  • Індексування будь-якого виду
  • Упорядкування сортуванням чи вставкою
  • Зрізи
  • Конкатенацію через +

Перевірка належності до set має сталу часову складність (у середньому), тоді як перевірка належності до list чи string має складність, що зростає зі збільшенням обсягу даних. Методи на кшталт <set>.union(), <set>.intersection() чи <set>.difference() також мають сталу часову складність (у середньому).

Літерали множин

Множину можна задати безпосередньо як літерал множини за допомогою фігурних дужок {} і ком між елементами. Дублікати мовчки відкидаються:

>>> one_element = {'➕'}
{'➕'}

>>> multiple_elements = {'➕', '🔻', '🔹', '🔆'}
{'➕', '🔻', '🔹', '🔆'}

>>> multiple_duplicates =  {'Hello!', 'Hello!', 'Hello!', 
                            '¡Hola!','Привіт!', 'こんにちは!', 
                            '¡Hola!','Привіт!', 'こんにちは!'}
{'こんにちは!', '¡Hola!', 'Hello!', 'Привіт!'}

Літерали множин використовують ті самі фігурні дужки, що й літерали dict, тому для створення порожньої множини потрібно скористатися set().

Конструктор множини

set() (конструктор класу set) можна використати з будь-яким iterable, переданим як аргумент. Елементи iterable перебираються по черзі й додаються до множини по одному. Порядок елементів не зберігається, а дублікати мовчки відкидаються:

# To create an empty set, the constructor must be used.
>>> no_elements = set()
set()

# The tuple is unpacked & each element is added.  
# Duplicates are removed.
>>> elements_from_tuple = set(("Parrot", "Bird", 
                               334782, "Bird", "Parrot"))
{334782, 'Bird', 'Parrot'}

# The list is unpacked & each element is added.
# Duplicates are removed.
>>> elements_from_list = set([2, 3, 2, 3, 3, 3, 5, 
                              7, 11, 7, 11, 13, 13])
{2, 3, 5, 7, 11, 13}

Пастки під час створення множин

Через поведінку «розпакування» використання set() з рядком може здивувати:

# String elements (Unicode code points) are 
# iterated through and added *individually*.
>>> elements_string = set("Timbuktu")
{'T', 'b', 'i', 'k', 'm', 't', 'u'}

# Unicode separators and positioning code points 
# are also added *individually*.
>>> multiple_code_points_string = set('अभ्यास')
{'अ', 'भ', 'य', 'स', 'ा', '्'}

Множини можуть містити дані різних типів і вкладені типи даних, але всі елементи множини мають бути хешованими:

# Attempting to use a list for a set member throws a TypeError
>>> lists_as_elements = {['🌈','💦'], 
                        ['☁️','⭐️','🌍'], 
                        ['⛵️', '🚲', '🚀']}

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'


# Standard sets are mutable, so they cannot be hashed.
>>> sets_as_elements = {{'🌈','💦'}, 
                        {'☁️','⭐️','🌍'}, 
                        {'⛵️', '🚲', '🚀'}}

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'

Робота з множинами

Множини мають методи, які загалом відтворюють математичні операції над множинами. Більшість (не всі) із цих методів мають відповідний оператор. Методи зазвичай приймають будь-який iterable як аргумент, тоді як оператори вимагають, щоб обидві сторони операції були множинами або frozensets.

Неперетинні множини

Метод <set>.isdisjoint(<other_collection>) перевіряє, чи мають елементи множини якийсь перетин з елементами іншої множини. Метод приймає будь-який iterable або set як аргумент. Він поверне True, якщо дві множини не мають спільних елементів, і False, якщо елементи спільні. Операторного відповідника немає:

# Both mammals and additional_animals are lists.
>>> mammals = ['squirrel','dog','cat','cow', 'tiger', 'elephant']
>>> additional_animals = ['pangolin', 'panda', 'parrot', 
                          'lemur', 'tiger', 'pangolin']

# Animals is a dict.
>>> animals = {'chicken': 'white',
               'sparrow': 'grey',
               'eagle': 'brown and white',
               'albatross': 'grey and white',
               'crow': 'black',
               'elephant': 'grey', 
               'dog': 'rust',
               'cow': 'black and white',
               'tiger': 'orange and black',
               'cat': 'grey',
               'squirrel': 'black'}
               
# Birds is a set.
>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}

# Mammals and birds don't share any elements.
>>> birds.isdisjoint(mammals)
True

# There are also no shared elements between 
# additional_animals and birds.
>>> birds.isdisjoint(additional_animals)
True

# Animals and mammals have shared elements.
# **Note** The first object needs to be a set or converted to a set
# since .isdisjoint() is a set method.
>>> set(animals).isdisjoint(mammals)
False

Підмножини та надмножини

<set>.issubset(<other_collection>) перевіряє, чи кожен елемент <set> також є в <other_collection>. Операторна форма: <set> <= <other_set>:

# Both mammals and additional_animals are lists.
>>> mammals = ['squirrel','dog','cat','cow', 'tiger', 'elephant']
>>> additional_animals = ['pangolin', 'panda', 'parrot', 
                          'lemur', 'tiger', 'pangolin']

# Animals is a dict.
>>> animals = {'chicken': 'white',
               'sparrow': 'grey',
               'eagle': 'brown and white',
               'albatross': 'grey and white',
               'crow': 'black',
               'elephant': 'grey', 
               'dog': 'rust',
               'cow': 'black and white',
               'tiger': 'orange and black',
               'cat': 'grey',
               'squirrel': 'black'}

# Birds is a set.
>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}

# Set methods will take any iterable as an argument.
# All members of birds are also members of animals.
>>> birds.issubset(animals)
True

# All members of mammals also appear in animals.
# **Note** The first object needs to be a set or converted to a set
# since .issubset() is a set method.
>>> set(mammals).issubset(animals)
True

# Both objects need to be sets to use a set operator
>>> birds <= set(mammals)
False

# A set is always a loose subset of itself.
>>> set(additional_animals) <= set(additional_animals)
True

Метод <set>.issuperset(<other_collection>) виконує дію, обернену до .issubset(). Він перевіряє, чи кожен елемент <other_collection> також є в <set>. Операторна форма: <set> >= <other_set>:

# All members of mammals also appear in animals.
# **Note** The first object needs to be a set or converted to a set
# since .issuperset() is a set method.
>>> set(animals).issuperset(mammals)
True

# All members of animals do not show up as members of birds.
>>> birds.issuperset(animals)
False

# Both objects need to be sets to use a set operator
>>> birds >= set(mammals)
False

# A set is always a loose superset of itself.
>>> set(animals) >= set(animals)
True

Перетин множин

<set>.intersection(*<other iterables>) повертає нову множину з елементами, спільними для початкової множини та всіх <others> (іншими словами, множину, де все перетинається). Операторна версія цього методу: <set> & <other set> & <other set 2> & ... <other set n>:

>>> perennials = {'Annatto','Asafetida','Asparagus','Azalea',
                 'Winter Savory', 'Broccoli','Curry Leaf','Fennel', 
                 'Kaffir Lime','Kale','Lavender','Mint','Oranges',
                 'Oregano', 'Tarragon', 'Wild Bergamot'}

>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Marjoram', 
              'Summer Squash', 'Okra','Shallots', 'Basil', 
              'Cilantro', 'Cumin', 'Sunflower', 'Chervil', 
              'Summer Savory'}

>>> herbs = ['Annatto','Asafetida','Basil','Chervil','Cilantro',
            'Curry Leaf','Fennel','Kaffir Lime','Lavender',
            'Marjoram','Mint','Oregano','Summer Savory', 
            'Tarragon','Wild Bergamot','Wild Celery',
            'Winter Savory']


# Methods will take any iterable as an argument.
>>> perennial_herbs = perennials.intersection(herbs)
{'Annatto', 'Asafetida', 'Curry Leaf', 'Fennel', 'Kaffir Lime',
 'Lavender', 'Mint', 'Oregano', 'Wild Bergamot','Winter Savory'}

# Operators require both groups be sets.
>>> annuals & set(herbs)
 {'Basil', 'Chervil', 'Marjoram', 'Cilantro'}

Обʼєднання множин

<set>.union(*<other iterables>) повертає нову множину з елементами з <set> та всіх <other iterables>. Операторна форма цього методу: <set> | <other set 1> | <other set 2> | ... | <other set n>:

>>> perennials = {'Asparagus', 'Broccoli', 'Sweet Potato', 'Kale'}
>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'}
>>> more_perennials = ['Radicchio', 'Rhubarb', 
                      'Spinach', 'Watercress']

# Methods will take any iterable as an argument.
>>> perennials.union(more_perennials)
{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb',
'Spinach','Sweet Potato','Watercress'}

# Operators require sets.
>>> set(more_perennials) | perennials
{'Asparagus',
 'Broccoli',
 'Kale',
 'Radicchio',
 'Rhubarb',
 'Spinach',
 'Sweet Potato',
 'Watercress'}

Різниця множин

<set>.difference(*<other iterables>) повертає нову множину з елементів початкової множини, яких немає в <others>. Операторна версія цього методу: <set> - <other set 1> - <other set 2> - ...<other set n>.

>>> berries_and_veggies = {'Asparagus', 
                          'Broccoli', 
                          'Watercress', 
                          'Goji Berries', 
                          'Goose Berries', 
                          'Ramps', 
                          'Walking Onions', 
                          'Blackberries', 
                          'Strawberries', 
                          'Rhubarb', 
                          'Kale', 
                          'Artichokes', 
                          'Currants'}

>>> veggies = ('Asparagus', 'Broccoli', 'Watercress', 'Ramps',
               'Walking Onions', 'Rhubarb', 'Kale', 'Artichokes')

# Methods will take any iterable as an argument.
>>> berries = berries_and_veggies.difference(veggies)
{'Blackberries','Currants','Goji Berries',
 'Goose Berries', 'Strawberries'}

# Operators require sets.
>>> berries_and_veggies - berries
{'Artichokes','Asparagus','Broccoli','Kale',
'Ramps','Rhubarb','Walking Onions','Watercress'}

<set>.symmetric_difference(<other iterable>) повертає нову множину, яка містить елементи, що є в <set> АБО в <other>, але не в обох одночасно. Операторна версія цього методу: <set> ^ <other set>:

>>> plants_1 = {'🌲','🍈','🌵', '🥑','🌴', '🥭'}
>>> plants_2 = ('🌸','🌴', '🌺', '🌲', '🌻', '🌵')


# Methods will take any iterable as an argument.
>>> fruit_and_flowers = plants_1.symmetric_difference(plants_2)
>>> fruit_and_flowers
{'🌸', '🌺', '🍈', '🥑', '🥭','🌻' }


# Operators require both groups be sets.
>>> fruit_and_flowers ^ plants_1
{'🌲',  '🌸', '🌴', '🌵','🌺', '🌻'}

>>> fruit_and_flowers ^ set(plants_2)
{'🥭', '🌴', '🌵', '🍈', '🌲', '🥑'}
Note

Симетрична різниця більш ніж двох множин дасть множину, яка містить і елементи, унікальні для кожної множини, І елементи, спільні для більш ніж двох множин у наборі (подробиці у статті Вікіпедії про симетричну різницю).

Щоб отримати лише елементи, унікальні для кожної множини в наборі, потрібно на окремому кроці зібрати перетини всіх комбінацій по дві множини й прибрати їх:

>>> one = {'black pepper','breadcrumbs','celeriac','chickpea flour',
           'flour','lemon','parsley','salt','soy sauce',
           'sunflower oil','water'}

>>> two = {'black pepper','cornstarch','garlic','ginger',
           'lemon juice','lemon zest','salt','soy sauce','sugar',
           'tofu','vegetable oil','vegetable stock','water'}

>>> three = {'black pepper','garlic','lemon juice','mixed herbs',
             'nutritional yeast', 'olive oil','salt','silken tofu',
             'smoked tofu','soy sauce','spaghetti','turmeric'}

>>> four = {'barley malt','bell pepper','cashews','flour',
            'fresh basil','garlic','garlic powder', 'honey',
            'mushrooms','nutritional yeast','olive oil','oregano',
            'red onion', 'red pepper flakes','rosemary','salt',
            'sugar','tomatoes','water','yeast'}

>>> intersections = (one & two | one & three | one & four | 
                     two & three | two & four | three & four)
...
{'black pepper','flour','garlic','lemon juice','nutritional yeast', 
'olive oil','salt','soy sauce', 'sugar','water'}

# The ^ operation will include some of the items in intersections, 
# which means it is not a "clean" symmetric difference - there
# are overlapping members.
>>> (one ^ two ^ three ^ four) & intersections
{'black pepper', 'garlic', 'soy sauce', 'water'}

# Overlapping members need to be removed in a separate step
# when there are more than two sets that need symmetric difference.
>>> (one ^ two ^ three ^ four) - intersections
...
{'barley malt','bell pepper','breadcrumbs', 'cashews','celeriac',
  'chickpea flour','cornstarch','fresh basil', 'garlic powder',
  'ginger','honey','lemon','lemon zest','mixed herbs','mushrooms',
  'oregano','parsley','red onion','red pepper flakes','rosemary',
  'silken tofu','smoked tofu','spaghetti','sunflower oil', 'tofu', 
  'tomatoes','turmeric','vegetable oil','vegetable stock','yeast'}

Вказівки

Ми разом зі своїми бізнес-партнерами керуємо невеликою кейтеринговою компанією. Ми щойно погодилися провести захід для місцевого кулінарного клубу, який представить страви, «улюблені клубом». Клуб не має досвіду в організації великих заходів і потребує допомоги з організацією, закупівлями, підготовкою та подачею. Ми вирішили написати кілька невеликих скриптів Python, щоб пришвидшити весь процес планування.

1. Очищення інгредієнтів страви

Рецепти для заходу додали з різних джерел, і їхні інгредієнти, схоже, мають дублікати (або й більше) записів. Ми ж не хочемо купити зайве! Перш ніж почати закупівлі й готування, список інгредієнтів кожної страви потрібно «очистити».

Реалізуймо функцію clean_ingredients(<dish_name>, <dish_ingredients>), яка приймає назву страви та list інгредієнтів. Ця функція повинна повернути tuple, першим елементом якого є назва страви, а далі йде set інгредієнтів без дублікатів.

>>> clean_ingredients('Punjabi-Style Chole', ['onions', 'tomatoes', 'ginger paste', 'garlic paste', 'ginger paste', 'vegetable oil', 'bay leaves', 'cloves', 'cardamom', 'cilantro', 'peppercorns', 'cumin powder', 'chickpeas', 'coriander powder', 'red chili powder', 'ground turmeric', 'garam masala', 'chickpeas', 'ginger', 'cilantro'])

>>> ('Punjabi-Style Chole', {'garam masala', 'bay leaves', 'ground turmeric', 'ginger', 'garlic paste', 'peppercorns', 'ginger paste', 'red chili powder', 'cardamom', 'chickpeas', 'cumin powder', 'vegetable oil', 'tomatoes', 'coriander powder', 'onions', 'cilantro', 'cloves'})

2. Коктейлі та моктейлі

На заході будуть і коктейлі, і «моктейлі», тобто напої без алкоголю. Потрібно переконатися, що «моктейлі» справді безалкогольні, а коктейлі справді містять алкоголь.

Реалізуймо функцію check_drinks(<drink_name>, <drink_ingredients>), яка приймає назву напою та list інгредієнтів. Якщо напій не містить алкоголю, функція повинна повернути назву напою, а далі «Mocktail», а якщо містить, то назву напою, а далі «Cocktail». У цій вправі коктейлі міститимуть алкоголь лише з константи ALCOHOLS у sets_categories_data.py:

>>> from sets_categories_data import ALCOHOLS 

>>> check_drinks('Honeydew Cucumber', ['honeydew', 'coconut water', 'mint leaves', 'lime juice', 'salt', 'english cucumber'])
...
'Honeydew Cucumber Mocktail'

>>> check_drinks('Shirley Tonic', ['cinnamon stick', 'scotch', 'whole cloves', 'ginger', 'pomegranate juice', 'sugar', 'club soda'])
...
'Shirley Tonic Cocktail'

3. Категоризація страв

Серед запрошених є гості з різними дієтичними потребами, тож нашим працівникам потрібно розділити страви на Vegan, Vegetarian, Paleo, Keto та Omnivore. Страва належить до категорії лише тоді, коли всі її інгредієнти входять до множини інгредієнтів цієї категорії.

Реалізуймо функцію categorize_dish(<dish_name>, <dish_ingredients>), яка приймає назву страви та set інгредієнтів цієї страви. Функція повинна повернути рядок тексту (англ. string) вигляду dish name: <CATEGORY> (до якої харчової категорії належить страва). Усі наведені страви «належатимуть» до однієї з категорій, імпортованих із sets_categories_data.py (VEGAN, VEGETARIAN, PALEO, KETO або OMNIVORE).

>>> from sets_categories_data import VEGAN, VEGETARIAN, PALEO, KETO, OMNIVORE


>>> categorize_dish('Sticky Lemon Tofu', {'tofu', 'soy sauce', 'salt', 'black pepper', 'cornstarch', 'vegetable oil', 'garlic', 'ginger', 'water', 'vegetable stock', 'lemon juice', 'lemon zest', 'sugar'})
...
'Sticky Lemon Tofu: VEGAN'

>>> categorize_dish('Shrimp Bacon and Crispy Chickpea Tacos with Salsa de Guacamole', {'shrimp', 'bacon', 'avocado', 'chickpeas', 'fresh tortillas', 'sea salt', 'guajillo chile', 'slivered almonds', 'olive oil', 'butter', 'black pepper', 'garlic', 'onion'})
...
'Shrimp Bacon and Crispy Chickpea Tacos with Salsa de Guacamole: OMNIVORE'

4. Позначення алергенів і заборонених продуктів

У деяких гостей є алергії та додаткові дієтичні обмеження. Ці інгредієнти потрібно позначити для кожної страви, щоб вони не створили проблем.

Реалізуймо функцію tag_special_ingredients(<dish>), яка приймає tuple, де в першій позиції стоїть назва страви, а в другій — list або set інгредієнтів цієї страви. Повернімо назву страви, а далі set інгредієнтів, які потребують спеціальної позначки в описі страви. Інгредієнти страви всередині list можуть мати дублікати, а можуть і не мати. У цій вправі всі алергени та спеціальні інгредієнти, які потрібно позначити, містяться в константі SPECIAL_INGREDIENTS, імпортованій із sets_categories_data.py.

>>> from sets_categories_data import SPECIAL_INGREDIENTS

>>> tag_special_ingredients(('Ginger Glazed Tofu Cutlets', ['tofu', 'soy sauce', 'ginger', 'corn starch', 'garlic', 'brown sugar', 'sesame seeds', 'lemon juice']))
...
('Ginger Glazed Tofu Cutlets', {'garlic','soy sauce','tofu'})

>>> tag_special_ingredients(('Arugula and Roasted Pork Salad', ['pork tenderloin', 'arugula', 'pears', 'blue cheese', 'pine nuts', 'balsamic vinegar', 'onions', 'black pepper']))
...
('Arugula and Roasted Pork Salad', {'pork tenderloin', 'blue cheese', 'pine nuts', 'onions'})

5. Укладання «головного списку» інгредієнтів

Готуючись до замовлення та закупівель, нам потрібно укласти «головний список» інгредієнтів для всього в меню (кількість заповнимо пізніше).

Реалізуймо функцію compile_ingredients(<dishes>), яка приймає list страв і повертає множину всіх інгредієнтів у всіх перелічених стравах. Кожну окрему страву представлено її set інгредієнтів.

dishes = [ {'tofu', 'soy sauce', 'ginger', 'corn starch', 'garlic', 'brown sugar', 'sesame seeds', 'lemon juice'},
           {'pork tenderloin', 'arugula', 'pears', 'blue cheese', 'pine nuts',
           'balsamic vinegar', 'onions', 'black pepper'},
           {'honeydew', 'coconut water', 'mint leaves', 'lime juice', 'salt', 'english cucumber'}]

>>> compile_ingredients(dishes)
...
{'arugula', 'brown sugar', 'honeydew', 'coconut water', 'english cucumber', 'balsamic vinegar', 'mint leaves', 'pears', 'pork tenderloin', 'ginger', 'blue cheese', 'soy sauce', 'sesame seeds', 'black pepper', 'garlic', 'lime juice', 'corn starch', 'pine nuts', 'lemon juice', 'onions', 'salt', 'tofu'}

6. Відбір закусок для подачі на тацях

Організатори дали нам список страв, які хотіли б приготувати як «закуски на один укус» для подачі на тацях. Їх потрібно виокремити з основного списку страв, які готуються як більші порції.

Реалізуймо функцію separate_appetizers(<dishes>, <appetizers>), яка приймає list назв страв і list назв закусок. Функція повинна повернути list назв страв, з якого вилучено назви закусок. І <dishes>, і <appetizers> list можуть містити дублікати, тож може знадобитися позбутися їх.

dishes =    ['Avocado Deviled Eggs','Flank Steak with Chimichurri and Asparagus', 'Kingfish Lettuce Cups',
             'Grilled Flank Steak with Caesar Salad','Vegetarian Khoresh Bademjan','Avocado Deviled Eggs',
             'Barley Risotto','Kingfish Lettuce Cups']
          
appetizers = ['Kingfish Lettuce Cups','Avocado Deviled Eggs','Satay Steak Skewers',
              'Dahi Puri with Black Chickpeas','Avocado Deviled Eggs','Asparagus Puffs',
              'Asparagus Puffs']
              
>>> separate_appetizers(dishes, appetizers)
...
['Vegetarian Khoresh Bademjan', 'Barley Risotto', 'Flank Steak with Chimichurri and Asparagus', 
 'Grilled Flank Steak with Caesar Salad']

7. Пошук інгредієнтів, використаних лише в одному рецепті

У кожній категорії (Vegan, Vegetarian, Paleo, Keto, Omnivore) потрібно виокремити інгредієнти, які трапляються лише в одній страві. Ці «одиничні» інгредієнти закріплять за окремим закупівельником, щоб про них не забули в метушні з рештою справ.

Реалізуймо функцію singleton_ingredients(<dishes>, <INTERSECTIONS>), яка приймає list страв і константу <CATEGORY>_INTERSECTIONS для тієї самої категорії. Кожну страву представлено set її інгредієнтів. Кожна <CATEGORY>_INTERSECTIONS - це set інгредієнтів, які трапляються більш ніж в одній страві категорії. Використовуючи операції над множинами, функція повинна повернути set «одиничних» інгредієнтів (інгредієнтів, що трапляються лише в одній страві категорії).

from sets_categories_data import example_dishes, EXAMPLE_INTERSECTION

>>> singleton_ingredients(example_dishes, EXAMPLE_INTERSECTION)
...
{'garlic powder', 'sunflower oil', 'mixed herbs', 'cornstarch', 'celeriac', 'honey', 'mushrooms', 'bell pepper', 'rosemary', 'parsley', 'lemon', 'yeast', 'vegetable oil', 'vegetable stock', 'silken tofu', 'tofu', 'cashews', 'lemon zest', 'smoked tofu', 'spaghetti', 'ginger', 'breadcrumbs', 'tomatoes', 'barley malt', 'red pepper flakes', 'oregano', 'red onion', 'fresh basil'}
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
Python Exercism

Час розпочати Офіціант кейтерингу?

Зареєструйтеся на Exercism, щоб вивчати й опановувати Python, а також 17 концепцій146 вправ та справжнє наставництво від людей, і все це безкоштовно.