This article is composed of some notes from book Effective Python.
Scope Resolution
Python’s scope resolution follows the order below:
- current function
- enclosing scopes: like a outer function enclosing the current function
- global scope
- build-in scope
1 | global_var = 'global value' |
Note that the assignment of enclosing_var_2 in function inner is actually a new variable definition, because enclosing_var_2 is not in the current scope. It’s designed to prevent local variables polluting its outer scopes. So we can see value of enclosing_var_2 doesn’t change.
Generator
It’s a function using yield instead of return.
Return an iterator when it gets called.
Every call of next with that iterator will result in code execution to the next yield and the iterator will return what’s passed to the yield.
1 | def gen(): |
Whenever you want to use a function to compose a list, you can consider using a generator instead, which is a cleaner way.
1 | def create_results(num): |
A pitfall
If an iterator is used up (a StopIteration exception has been thrown), you will get no results for iterating it again. And there will be no exceptions.
1 | def results_gen(number): |
Solutions
- Copy content of the iterator to a list and use the list afterward.
- Pass a lambda instead of
numberswhich returns a new generator on every call. - Implement iterator protocol. That is, implement
__iter__as a generator.
1 | class ResultsContainer: |
Each traversal of the results_container object will cause it to return a new iterator (calling __iter__ every time), so there won’t be this issue.
Single asterisk used in function definition and function call
*numbers is called optional positional arguments. It indicates that this function can take zero or more than one positional arguments starting from that position.
It should be put after all positional arguments in a function definition.
It will pack those arguments into one tuple to use in the function.
* before nums will unpack any iterable nums so that print_numbers(0, 5, *nums) will be print_numbers(0, 5, 1, 2, 3)
1 | def print_numbers(first_num, *numbers): |
Use __call__ special method to turn class instances into functions
If we define __call__ in our class, we can turn the class instances into functions. Each call on the instance will invoke calling of its __call__ .
So, when there is a need for a function to preserve some state, you can consider using a class with __call__ method. It’s more readable than a stateful closure.
1 | from collections import defaultdict |