├── cover.png
├── markdowns
└── article.md
├── python-project
├── collections_counter.py
├── context_manager.py
├── context_manager_decorator.py
├── decorators.py
├── defaultdict.py
├── defaultdict_tree.py
├── generator.py
├── itertools_chain.py
├── itertools_combinations.py
├── itertools_permutations.py
├── keyword_arguments.py
├── lru_cache.py
├── unpacking.py
├── unpacking_arguments.py
└── yield.py
└── techio.yml
/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodinGame/advanced-python-features-playground/9d2a476cefbf83a677368368fccf6bcc2ad682d6/cover.png
--------------------------------------------------------------------------------
/markdowns/article.md:
--------------------------------------------------------------------------------
1 | Python is full of awesome features and tricks, that make you think "Wow! Python is so cool!".
2 |
3 | We've done a selection of features we particularly like. We hope you'll learn something that will make you say "Neat! I didn't know that".
4 |
5 | - [Generators](#generators)
6 | - [Collections Module](#collections)
7 | - [Itertools Module](#itertools)
8 | - [Packing / Unpacking](#unpacking)
9 | - [Decorators](#decorators)
10 | - [Context Managers](#context-managers)
11 |
12 | The source code is on [GitHub](https://github.com/CodinGame/advanced-python-features-playground), please feel free to come up with ideas to improve it.
13 |
14 | # Generators
15 |
16 | A **generator** is an object that produces a sequence of values. It can be used as an iterator, which means that you can use it with a `for` statement, or use the `next` function to get the next value. However, you can iterate over the values only once.
17 |
18 | A generator can be created using a function that uses the `yield` keyword to generate a value. When a generator function is called, a generator object is created.
19 |
20 | @[yield operator]({"stubs": ["yield.py"], "command": "python3 yield.py"})
21 |
22 | For simple cases, it is possible to create a generator using a **generator expression**. As opposed to a list, the values will be computed on the fly instead of being computed once and stored in memory.
23 |
24 | @[generator expressions]({"stubs": ["generator.py"], "command": "python3 generator.py"})
25 |
26 | # Collections Module
27 |
28 | [`collections`](https://docs.python.org/3/library/collections.html) is a module in the standard library that implements alternative container datatypes.
29 |
30 | For example, a [`Counter`](https://docs.python.org/3/library/collections.html#collections.Counter) is a collection where elements are stored as dictionary keys and their counts are stored as dictionary values:
31 |
32 | @[Counter]({ "stubs": ["collections_counter.py"], "command": "python3 collections_counter.py" })
33 |
34 | A [`defaultdict`](https://docs.python.org/3/library/collections.html#collections.defaultdict) is a subclass of `dict`, which allows to pass a factory used to create automatically a new value when a key is missing.
35 |
36 | @[defaultdict]({"stubs": ["defaultdict.py"], "command": "python3 defaultdict.py"})
37 |
38 | The `defaultdict` can be used to create a tree data structure!
39 |
40 | @[Tree]({"stubs": ["defaultdict_tree.py"], "command": "python3 defaultdict_tree.py"})
41 |
42 | # Itertools Module
43 |
44 | [`itertools`](https://docs.python.org/3/library/itertools.html) is a module in the standard library that allows you to create iterators for efficient looping.
45 |
46 | For example, [`permutations`](https://docs.python.org/3/library/itertools.html#itertools.permutations) allows you to generate all the possible ways of ordering a set of things:
47 |
48 | @[permutations]({ "stubs": ["itertools_permutations.py"], "command": "python3 itertools_permutations.py" })
49 |
50 | Similarly, [`combinations` ](https://docs.python.org/3/library/itertools.html#itertools.combinations) generates all the possible ways of selecting items from a collection, such that (unlike permutations) the order does not matter:
51 |
52 | @[combinations]({ "stubs": ["itertools_combinations.py"], "command": "python3 itertools_combinations.py" })
53 |
54 | `itertools` also contains utility functions such as [`chain`](https://docs.python.org/3/library/itertools.html#itertools.chain), which takes iterables and creates a new iterator that returns elements from the given iterables sequentially, as a single sequence:
55 |
56 | @[chain]({ "stubs": ["itertools_chain.py"], "command": "python3 itertools_chain.py" })
57 |
58 | # Packing / Unpacking
59 |
60 | The `*` operator, known as the unpack or splat operator allows very convenient transformations, going from lists or tuples to separate variables or arguments and conversely.
61 |
62 | @[Extended Iterable Unpacking]({"stubs": ["unpacking.py"], "command": "python3 unpacking.py"})
63 |
64 | When the arguments for your function are already in a list or in a tuple, you can unpack them using `*args` if it's a `list`, or `**kwargs` if that's a `dict`.
65 |
66 | @[unpacking arguments]({"stubs": ["unpacking_arguments.py"], "command": "python3 unpacking_arguments.py"})
67 |
68 | The opposite is also possible, you can define a function that will pack all the arguments in a single `tuple` and all the keyword arguments in a single `dict`.
69 |
70 | @[keyword arguments]({"stubs": ["keyword_arguments.py"], "command": "python3 keyword_arguments.py"})
71 |
72 | # Decorators
73 |
74 | A **decorator** is simply a function which takes a function as a parameter and returns a function.
75 |
76 | For example, in the following code, the `cache` function is used as a decorator to remember the Fibonacci numbers that have already been computed:
77 |
78 | @[decorators]({ "stubs": ["decorators.py"], "command": "python3 decorators.py" })
79 |
80 | The [`functools`](https://docs.python.org/3/library/functools.html) module provides a few decorators, such as [`lru_cache`](https://docs.python.org/3/library/functools.html#functools.lru_cache) which can do what we just did: memoization. It saves recent calls to save time when a given function is called with the same arguments:
81 |
82 | @[lru_cache]({ "stubs": ["lru_cache.py"], "command": "python3 lru_cache.py" })
83 |
84 | # Context Managers
85 |
86 | Context managers are mainly used to properly manage resources. The most common use of a context manager is the opening of a file: `with open('workfile', 'r') as f:`. However most developers have no idea how that really works underneath nor how they can create their own.
87 |
88 | Actually, a [context manager](https://docs.python.org/3/library/stdtypes.html#typecontextmanager) is just a class that implements the methods `__enter__` and `__exit__`.
89 |
90 | @[Context Manager]({"stubs": ["context_manager.py"], "command": "python3 context_manager.py"})
91 |
92 | For simple use cases, it's also possible to use a generator function with a single `yield`, using the [`@contextmanager`](https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager) decorator.
93 |
94 | @[Context Manager Using @contextmanager]({"stubs": ["context_manager_decorator.py"], "command": "python3 context_manager_decorator.py"})
95 |
96 | Voilà! We hoped you enjoyed our selection of best features in Python 3, feel free to share your feedback on the forum or on our [Github](https://github.com/CodinGame/advanced-python-features-playground) :)
97 |
--------------------------------------------------------------------------------
/python-project/collections_counter.py:
--------------------------------------------------------------------------------
1 | from collections import Counter
2 |
3 | a = Counter('blue')
4 | b = Counter('yellow')
5 |
6 | print(a)
7 | print(b)
8 | print((a + b).most_common(3))
9 |
--------------------------------------------------------------------------------
/python-project/context_manager.py:
--------------------------------------------------------------------------------
1 | from time import time
2 |
3 |
4 | class Timer():
5 | def __init__(self, message):
6 | self.message = message
7 |
8 | def __enter__(self):
9 | self.start = time()
10 | return None # could return anything, to be used like this: with Timer("Message") as value:
11 |
12 | def __exit__(self, type, value, traceback):
13 | elapsed_time = (time() - self.start) * 1000
14 | print(self.message.format(elapsed_time))
15 |
16 |
17 | with Timer("Elapsed time to compute some prime numbers: {}ms"):
18 | primes = []
19 | for x in range(2, 500):
20 | if not any(x % p == 0 for p in primes):
21 | primes.append(x)
22 | print("Primes: {}".format(primes))
23 |
24 |
--------------------------------------------------------------------------------
/python-project/context_manager_decorator.py:
--------------------------------------------------------------------------------
1 | from contextlib import contextmanager
2 |
3 |
4 | @contextmanager
5 | def colored_output(color):
6 | print("\033[%sm" % color, end="")
7 | yield
8 | print("\033[0m", end="")
9 |
10 |
11 | print("Hello, World!")
12 | with colored_output(31):
13 | print("Now in color!")
14 | print("So cool.")
15 |
--------------------------------------------------------------------------------
/python-project/decorators.py:
--------------------------------------------------------------------------------
1 | def cache(function):
2 | cached_values = {} # Contains already computed values
3 | def wrapping_function(*args):
4 | if args not in cached_values:
5 | # Call the function only if we haven't already done it for those parameters
6 | cached_values[args] = function(*args)
7 | return cached_values[args]
8 | return wrapping_function
9 |
10 | @cache
11 | def fibonacci(n):
12 | print('calling fibonacci(%d)' % n)
13 | if n < 2:
14 | return n
15 | return fibonacci(n-1) + fibonacci(n-2)
16 |
17 | print([fibonacci(n) for n in range(1, 9)])
18 |
--------------------------------------------------------------------------------
/python-project/defaultdict.py:
--------------------------------------------------------------------------------
1 | from collections import defaultdict
2 |
3 | my_dict = defaultdict(lambda: 'Default Value')
4 | my_dict['a'] = 42
5 |
6 | print(my_dict['a'])
7 | print(my_dict['b'])
8 |
--------------------------------------------------------------------------------
/python-project/defaultdict_tree.py:
--------------------------------------------------------------------------------
1 | from collections import defaultdict
2 | import json
3 |
4 | def tree():
5 | """
6 | Factory that creates a defaultdict that also uses this factory
7 | """
8 | return defaultdict(tree)
9 |
10 | root = tree()
11 | root['Page']['Python']['defaultdict']['Title'] = 'Using defaultdict'
12 | root['Page']['Python']['defaultdict']['Subtitle'] = 'Create a tree'
13 | root['Page']['Java'] = None
14 |
15 | print(json.dumps(root, indent=4))
16 |
--------------------------------------------------------------------------------
/python-project/generator.py:
--------------------------------------------------------------------------------
1 | a = (x * x for x in range(100))
2 |
3 | # a is a generator object
4 | print(type(a))
5 |
6 | # Sum all the numbers of the generator
7 | print(sum(a))
8 |
9 | # There are no elements left in the generator
10 | print(sum(a))
11 |
--------------------------------------------------------------------------------
/python-project/itertools_chain.py:
--------------------------------------------------------------------------------
1 | from itertools import chain
2 |
3 | for c in chain(range(3), range(12, 15)):
4 | print(c)
5 |
--------------------------------------------------------------------------------
/python-project/itertools_combinations.py:
--------------------------------------------------------------------------------
1 | from itertools import combinations
2 |
3 | for c in combinations([1, 2, 3, 4], 2):
4 | print(c)
5 |
--------------------------------------------------------------------------------
/python-project/itertools_permutations.py:
--------------------------------------------------------------------------------
1 | from itertools import permutations
2 |
3 | for p in permutations([1,2,3]):
4 | print(p)
5 |
--------------------------------------------------------------------------------
/python-project/keyword_arguments.py:
--------------------------------------------------------------------------------
1 | def f(*args, **kwargs):
2 | print("Arguments: ", args)
3 | print("Keyword arguments: ", kwargs)
4 |
5 | f(3, 4, 9, foo=42, bar=7)
6 |
--------------------------------------------------------------------------------
/python-project/lru_cache.py:
--------------------------------------------------------------------------------
1 | from functools import lru_cache
2 |
3 | @lru_cache(maxsize=None)
4 | def fibonacci(n):
5 | print('calling fibonacci(%d)' % n)
6 | if n < 2:
7 | return n
8 | return fibonacci(n-1) + fibonacci(n-2)
9 |
10 | print([fibonacci(n) for n in range(1, 9)])
11 |
--------------------------------------------------------------------------------
/python-project/unpacking.py:
--------------------------------------------------------------------------------
1 | a, *b, c = [2, 7, 5, 6, 3, 4, 1]
2 | print(a)
3 | print(b)
4 | print(c)
5 |
--------------------------------------------------------------------------------
/python-project/unpacking_arguments.py:
--------------------------------------------------------------------------------
1 | def repeat(count, name):
2 | for i in range(count):
3 | print(name)
4 |
5 | print("Call function repeat using a list of arguments:")
6 | args = [4, "cats"]
7 | repeat(*args)
8 |
9 | print("Call function repeat using a dictionary of keyword arguments:")
10 | args2 = {'count': 4, 'name': 'cats'}
11 | repeat(**args2)
12 |
--------------------------------------------------------------------------------
/python-project/yield.py:
--------------------------------------------------------------------------------
1 | def fibonacci_generator():
2 | a, b = 0, 1
3 | while True:
4 | yield a
5 | a, b = b, a + b
6 |
7 | # Print all the numbers of the Fibonacci sequence that are lower than 1000
8 | for i in fibonacci_generator():
9 | if i > 1000:
10 | break
11 | print(i)
12 |
--------------------------------------------------------------------------------
/techio.yml:
--------------------------------------------------------------------------------
1 | title : Advanced Python Features
2 |
3 | # Table of contents
4 | plan:
5 | - title: Advanced Python Features # Set the page title
6 | statement: markdowns/article.md # Set the file path to the page content
7 |
8 | # Settings for your programming projects and the associated Docker images
9 | projects:
10 | python:
11 | root: /python-project # Set the root path to your Python project
12 |
13 | # Set the docker image. This image runs tests using this syntax:
14 | # @[Code Editor Title]({"stubs": ["editor_file1.py", "editor_file2.py", ...], "command": "python3 test_file.py"})
15 | runner: python:3
16 |
--------------------------------------------------------------------------------