Many edits

This commit is contained in:
David Beazley
2020-05-28 17:41:59 -05:00
parent 9572f707b2
commit a83a9cf064
23 changed files with 467 additions and 210 deletions

View File

@@ -1,25 +1,31 @@
[Contents](../Contents) \| [Previous (6.4 Generator Expressions)](../06_Generators/04_More_generators) \| [Next (7.2 Anonymous Functions)](02_Anonymous_function)
# 7.1 Variable Arguments
This section covers variadic function arguments, sometimes described as
`*args` and `**kwargs`.
### Positional variable arguments (*args)
A function that accepts *any number* of arguments is said to use variable arguments.
For example:
```python
def foo(x, *args):
def f(x, *args):
...
```
Function call.
```python
foo(1,2,3,4,5)
f(1,2,3,4,5)
```
The arguments get passed as a tuple.
The extra arguments get passed as a tuple.
```python
def foo(x, *args):
def f(x, *args):
# x -> 1
# args -> (2,3,4,5)
```
@@ -30,37 +36,52 @@ A function can also accept any number of keyword arguments.
For example:
```python
def foo(x, y, **kwargs):
def f(x, y, **kwargs):
...
```
Function call.
```python
foo(2,3,flag=True,mode='fast',header='debug')
f(2, 3, flag=True, mode='fast', header='debug')
```
The extra keywords are passed in a dictionary.
```python
def foo(x, y, **kwargs):
def f(x, y, **kwargs):
# x -> 2
# y -> 3
# kwargs -> { 'flat': True, 'mode': 'fast', 'header': 'debug' }
# kwargs -> { 'flag': True, 'mode': 'fast', 'header': 'debug' }
```
### Combining both
A function can also combine any number of variable keyword and non-keyword arguments.
Function definition.
A function can also accept any number of variable keyword and non-keyword arguments.
```python
def foo(*args, **kwargs):
def f(*args, **kwargs):
...
```
This function takes any combination of positional or keyword arguments.
It is sometimes used when writing wrappers or when you want to pass arguments through to another function.
Function call.
```python
f(2, 3, flag=True, mode='fast', header='debug')
```
The arguments are separated into positional and keyword components
```python
def f(*args, **kwargs):
# args = (2, 3)
# kwargs -> { 'flag': True, 'mode': 'fast', 'header': 'debug' }
...
```
This function takes any combination of positional or keyword
arguments. It is sometimes used when writing wrappers or when you
want to pass arguments through to another function.
### Passing Tuples and Dicts
@@ -68,7 +89,7 @@ Tuples can be expanded into variable arguments.
```python
numbers = (2,3,4)
foo(1, *numbers) # Same as f(1,2,3,4)
f(1, *numbers) # Same as f(1,2,3,4)
```
Dictionaries can also be expaded into keyword arguments.
@@ -79,12 +100,10 @@ options = {
'delimiter' : ',',
'width' : 400
}
foo(data, **options)
# Same as foo(data, color='red', delimiter=',', width=400)
f(data, **options)
# Same as f(data, color='red', delimiter=',', width=400)
```
These are not commonly used except when writing library functions.
## Exercises
### Exercise 7.1: A simple example of variable arguments

View File

@@ -1,3 +1,5 @@
[Contents](../Contents) \| [Previous (7.1 Variable Arguments)](01_Variable_arguments) \| [Next (7.3 Returning Functions)](03_Returning_functions)
# 7.2 Anonymous Functions and Lambda
### List Sorting Revisited
@@ -30,7 +32,9 @@ It seems simple enough. However, how do we sort a list of dicts?
By what criteria?
You can guide the sorting by using a *key function*. The *key function* is a function that receives the dictionary and returns the value in a specific key.
You can guide the sorting by using a *key function*. The *key
function* is a function that receives the dictionary and returns the
value of interest for sorting.
```python
def stock_name(s):
@@ -39,7 +43,7 @@ def stock_name(s):
portfolio.sort(key=stock_name)
```
The value returned by the *key function* determines the sorting.
Here's the result.
```python
# Check how the dictionaries are sorted by the `name` key
@@ -56,13 +60,16 @@ The value returned by the *key function* determines the sorting.
### Callback Functions
Callback functions are often short one-line functions that are only used for that one operation. For example of previous sorting example.
Programmers often ask for a short-cut, so is there a shorter way to specify custom processing for `sort()`?
In the above example, the key function is an example of a callback
function. The `sort()` method "calls back" to a function you supply.
Callback functions are often short one-line functions that are only
used for that one operation. Programmers often ask for a short-cut
for specifying this extra processing.
### Lambda: Anonymous Functions
Use a lambda instead of creating the function.
In our previous sorting example.
Use a lambda instead of creating the function. In our previous
sorting example.
```python
portfolio.sort(key=lambda s: s['name'])
@@ -156,6 +163,6 @@ Try sorting the portfolio according to the price of each stock
Note: `lambda` is a useful shortcut because it allows you to
define a special processing function directly in the call to `sort()` as
opposed to having to define a separate function first (as in part a).
opposed to having to define a separate function first.
[Contents](../Contents) \| [Previous (7.1 Variable Arguments)](01_Variable_arguments) \| [Next (7.3 Returning Functions)](03_Returning_functions)

View File

@@ -1,6 +1,8 @@
[Contents](../Contents) \| [Previous (7.2 Anonymous Functions)](02_Anonymous_function) \| [Next (7.4 Decorators)](04_Function_decorators)
# 7.3 Returning Functions
This section introduces the idea of closures.
This section introduces the idea of using functions to create other functions.
### Introduction
@@ -27,7 +29,8 @@ Adding 3 4
### Local Variables
Observe how to inner function refers to variables defined by the outer function.
Observe how to inner function refers to variables defined by the outer
function.
```python
def add(x, y):
@@ -38,7 +41,8 @@ def add(x, y):
return do_add
```
Further observe that those variables are somehow kept alive after `add()` has finished.
Further observe that those variables are somehow kept alive after
`add()` has finished.
```python
>>> a = add(3,4)
@@ -51,7 +55,7 @@ Adding 3 4 # Where are these values coming from?
### Closures
When an inner function is returned as a result, the inner function is known as a *closure*.
When an inner function is returned as a result, that inner function is known as a *closure*.
```python
def add(x, y):
@@ -62,7 +66,10 @@ def add(x, y):
return do_add
```
*Essential feature: A closure retains the values of all variables needed for the function to run properly later on.*
*Essential feature: A closure retains the values of all variables
needed for the function to run properly later on.* Think of a
closure as a function plus an extra environment that holds the values
of variables that it depends on.
### Using Closures
@@ -99,7 +106,7 @@ Closures carry extra information around.
```python
def add(x, y):
def do_add():
print('Adding %s + %s -> %s' % (x, y, x + y))
print(f'Adding {x} + {y} -> {x+y}')
return do_add
def after(seconds, func):
@@ -110,8 +117,6 @@ after(30, add(2, 3))
# `do_add` has the references x -> 2 and y -> 3
```
A function can have its own little environment.
### Code Repetition
Closures can also be used as technique for avoiding excessive code repetition.
@@ -122,11 +127,12 @@ You can write functions that make code.
### Exercise 7.7: Using Closures to Avoid Repetition
One of the more powerful features of closures is their use in
generating repetitive code. If you refer back to exercise 5.2
recall the code for defining a property with type checking.
generating repetitive code. If you refer back to [Exercise
5.7](../05_Object_model/02_Classes_encapsulation), recall the code for
defining a property with type checking.
```python
class Stock(object):
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
@@ -173,7 +179,7 @@ Now, try it out by defining a class like this:
```python
from typedproperty import typedproperty
class Stock(object):
class Stock:
name = typedproperty('name', str)
shares = typedproperty('shares', int)
price = typedproperty('price', float)
@@ -211,7 +217,7 @@ Float = lambda name: typedproperty(name, float)
Now, rewrite the `Stock` class to use these functions instead:
```python
class Stock(object):
class Stock:
name = String('name')
shares = Integer('shares')
price = Float('price')

View File

@@ -1,3 +1,5 @@
[Contents](../Contents) \| [Previous (7.3 Returning Functions)](03_Returning_functions) \| [Next (7.5 Decorated Methods)](05_Decorated_methods)
# 7.4 Function Decorators
This section introduces the concept of a decorator. This is an advanced
@@ -12,7 +14,7 @@ def add(x, y):
return x + y
```
Now, consider the function with some logging.
Now, consider the function with some logging added to it.
```python
def add(x, y):
@@ -32,13 +34,15 @@ def sub(x, y):
*Observation: It's kind of repetitive.*
Writing programs where there is a lot of code replication is often really annoying.
They are tedious to write and hard to maintain.
Especially if you decide that you want to change how it works (i.e., a different kind of logging perhaps).
Writing programs where there is a lot of code replication is often
really annoying. They are tedious to write and hard to maintain.
Especially if you decide that you want to change how it works (i.e., a
different kind of logging perhaps).
### Example continuation
### Code that makes logging
Perhaps you can make *logging wrappers*.
Perhaps you can make a function that makes functions with logging
added to them. A wrapper.
```python
def logged(func):
@@ -65,7 +69,9 @@ logged_add(3, 4) # You see the logging message appear
This example illustrates the process of creating a so-called *wrapper function*.
**A wrapper is a function that wraps another function with some extra bits of processing.**
A wrapper is a function that wraps around another function with some
extra bits of processing, but otherwise works in the exact same way
as the original function.
```python
>>> logged_add(3, 4)
@@ -100,6 +106,8 @@ It is said to *decorate* the function.
There are many more subtle details to decorators than what has been presented here.
For example, using them in classes. Or using multiple decorators with a function.
However, the previous example is a good illustration of how their use tends to arise.
Usually, it's in response to repetitive code appearing across a wide range of
function definitions. A decorator can move that code to a central definition.
## Exercises

View File

@@ -1,6 +1,8 @@
[Contents](../Contents) \| [Previous (7.4 Decorators)](04_Function_decorators) \| [Next (8 Testing and Debugging)](../08_Testing_debugging/00_Overview)
# 7.5 Decorated Methods
This section discusses a few common decorators that are used in
This section discusses a few built-in decorators that are used in
combination with method definitions.
### Predefined Decorators
@@ -8,7 +10,7 @@ combination with method definitions.
There are predefined decorators used to specify special kinds of methods in class definitions.
```python
class Foo(object):
class Foo:
def bar(self,a):
...
@@ -29,8 +31,9 @@ Let's go one by one.
### Static Methods
`@staticmethod` is used to define a so-called *static* class methods (from C++/Java).
A static method is a function that is part of the class, but which does *not* operate on instances.
`@staticmethod` is used to define a so-called *static* class methods
(from C++/Java). A static method is a function that is part of the
class, but which does *not* operate on instances.
```python
class Foo(object):
@@ -42,17 +45,19 @@ class Foo(object):
>>>
```
Static methods are sometimes used to implement internal supporting code for a class.
For example, code to help manage created instances (memory management, system resources, persistence, locking, etc).
Static methods are sometimes used to implement internal supporting
code for a class. For example, code to help manage created instances
(memory management, system resources, persistence, locking, etc).
They're also used by certain design patterns (not discussed here).
### Class Methods
`@classmethod` is used to define class methods.
A class method is a method that receives the *class* object as the first parameter instead of the instance.
`@classmethod` is used to define class methods. A class method is a
method that receives the *class* object as the first parameter instead
of the instance.
```python
class Foo(object):
class Foo:
def bar(self):
print(self)
@@ -71,7 +76,7 @@ class Foo(object):
Class methods are most often used as a tool for defining alternate constructors.
```python
class Date(object):
class Date:
def __init__(self,year,month,day):
self.year = year
self.month = month
@@ -90,7 +95,7 @@ d = Date.today()
Class methods solve some tricky problems with features like inheritance.
```python
class Date(object):
class Date:
...
@classmethod
def today(cls):
@@ -106,62 +111,7 @@ d = NewDate.today()
## Exercises
Start this exercise by defining a `Date` class. For example:
```
>>> class Date(object):
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
>>> d = Date(2010, 4, 13)
>>> d.year, d.month, d.day
(2010, 4, 13)
>>>
```
### Exercise 7.11: Class Methods
A common use of class methods is to provide alternate constructors
(epecially since Python doesn't support overloaded methods). Modify
the `Date` class to have a class method `today()` that creates a date
from today's date.
```python
>>> import time
>>> class Date(object):
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
@classmethod
def today(cls):
t = time.localtime()
return cls(t.tm_year, t.tm_mon, t.tm_mday)
>>> d = Date.today()
>>> d.year, d.month, d.day
... output varies. Should be today ...
>>>
```
One reason you should use class methods for this is that they work
with inheritance. For example, try this:
```python
>>> class CustomDate(Date):
def yow(self):
print('Yow!')
>>> d = CustomDate.today()
<__main__.CustomDate object at 0x10923d400>
>>> d.yow()
Yow!
>>>
```
### Exercise 7.12: Class Methods in Practice
### Exercise 7.11: Class Methods in Practice
In your `report.py` and `portfolio.py` files, the creation of a `Portfolio`
object is a bit muddled. For example, the `report.py` program has code like this:
@@ -186,7 +136,7 @@ and the `portfolio.py` file defines `Portfolio()` with an odd initializer
like this:
```python
class Portfolio(object):
class Portfolio:
def __init__(self, holdings):
self.holdings = holdings
...
@@ -202,7 +152,7 @@ Like this:
import stock
class Portfolio(object):
class Portfolio:
def __init__(self):
self.holdings = []
@@ -222,7 +172,7 @@ class method for it:
import fileparse
import stock
class Portfolio(object):
class Portfolio:
def __init__(self):
self.holdings = []