├── 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 | --------------------------------------------------------------------------------