A bag of tricks regarding namespace/scope, code blocks, argument un/packing, lambdas (anonymous function), closures, and maybe more.
Today is going to get a little theoretical and you'll find interesting applications for these rules that supplement your other work.
-
Today's deep dive: Clarifying Scope.
- Nesting Functions - How does Python resolve names?
- LEGB? Local, Enclosing, Global, Built-in. Lets use an example:
""" Lets see what z is in each scope. What will we see for each value of z? After this, remove the assignment of z in each function and see what happens. Finally, add the assignment back and uncomment the BEFORE_ASSIGNMENT print lines. """ # z = 15 def cat(): """ The cat function is written in main. """ #if you use z in cat, you cannot call it before using it... #print("z in cat BEFORE ASSIGNMENT {}".format(z)) z = 10 print("z in cat AFTER ASSIGNMENT {}".format(z)) def cat_helper(): """ The cat_helper function is inside of cat only. """ #if you use z in cat_helper, you cannot call it before using it... #print("z in cat_helper BEFORE ASSIGNMENT {}".format(z)) z = 5 print("z in cat_helper AFTER ASSIGNMENT {}".format(z)) return # this is inside of cat cat_helper() return # now we are back in main print("z in main {}".format(z)) # lets run cat()... it will run meow() from inside cat() cat()
- LEGB? Local, Enclosing, Global, Built-in. Lets use an example:
- Nesting Functions - How does Python resolve names?
-
Using Argument packing and unpacking:
(*args, **kwargs)
- Two operations:
- The * operator will pack or unpack a tuple.
- The ** operator will pack or unpack a dict.
- Lets try unpacking some stuff:
my_list = [1,2,3] # can also be a tuple my_dict = {"first" : "hello", "second" : "world"} def my_function(a, b, c, first, second): print a, b, c print first, second my_function(*my_list, **my_dict)
- Lets try a second way:
def my_function(*args, **kwargs): print a, b, c print first, second my_function(1, 2, 3, first="hello", second="world")
- One rule: don't get sloppy. Explicit is better than implicit.
- You will see code where people stop defining variables in their function.
- These people are failing. Code is written to be read by humans, and that's a key principle of python.
- Two operations:
-
Functions are objects.
- What does this really mean? It means we can give them names and pass them around.
def addx(b, x): """ takes b,x and returns b + x """ return b + x def multx(b, x): """ takes b,x and returns b * x """ return b * x # we can assign the function to a new name, it has 2 names now. my_addx = addx my_b = 1 my_x = 2 print(my_addx(my_b, my_x))
- Ok.. that's nice. Lets do more. Lets put our functions in a list.
# continue the last code section my_functions = [addx, multx] print(my_functions[0](1, 2)) print(my_functions[1](1, 2))
- Ok lets get weird. These anonymous functions don't need names.
- Now lets open up a python terminal.
# we can write a square function without even giving it a name b = 2 x = 4 print(lambda b, x: b ** x) # or it can still have a name... addx = lambda b, x: b + x my_functions = [addx, lambda b, x: b * x] # lets multiply without the word multx print(my_functions[1](b,x))
- Why do we need these? Simply put, we don't.
- Now lets open up a python terminal.
- What does this really mean? It means we can give them names and pass them around.
- How to get a nested function to close. Also known as a closure.
- A what? A closure - Simple definition: A function that binds a set of 'private' variables.
- How does this help me write useful code?
- Control flow
- Private functions for specific environments
- Passing variables to a function based on the context of the function
- Why not just put the environment in the global scope? You could.
- Security
- compartmentalization
- memory efficiency, code reusability
- Closures are a sophisticated, optional tool
- Decorators - Forget everything about the @ symbol for now.
- A decorator is a callable object that wraps around another callable object. Often a function that wraps around a function.
- The decorator must return a callable object, usually a function.
- A decorator is used to add functionality to the original function. Also called enhancement.
- If you have used chaining in other functions, it is a very similar idea. Chains of functions run on each others output from the inside out. Decorators are also wrapped and executed from the inside out.
- Example time. Head over to decorator1.py and continue through all the decorator examples.
- Functools - What's a partial?
- A partial is part of a callable object (generally a function) with a fixed keyword argument or positional argument. This reduces the number of arguments that need to be provided to the function, essentially 'fixing' one argument.
- Review the partial example.
- Functools also has an update_wrapper function for making a decorator's wrapper look like the wrapped object. Invoked using the @wraps decorator on the function wrapper.
- Review the wraps example.
- the functools.total_ordering decorator will autogenerate rich comparisons if you specify a single one.
- Finally, reduce is located here in Python 3.
Preparing for this lesson:
This won't guarantee you will be ready for the lesson, but it will serve as a good warm up.
- Read Python Scopes and Namespaces
- Optional: Pep 227 - Statically Nested Scopes
- If you know nothing else, know that python resolves scopes in LEGB order: local, enclosing, global, built-in.
- Closure and Decorator Blog Post by Simeon Franklin of SF Python.