Files
practical-python/Notes/07_Advanced_Topics/01_Variable_arguments.md
David Beazley b5244b0e61 link experiment
2020-05-26 09:21:19 -05:00

214 lines
4.6 KiB
Markdown

# 7.1 Variable Arguments
### 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):
...
```
Function call.
```python
foo(1,2,3,4,5)
```
The arguments get passed as a tuple.
```python
def foo(x, *args):
# x -> 1
# args -> (2,3,4,5)
```
### Keyword variable arguments (**kwargs)
A function can also accept any number of keyword arguments.
For example:
```python
def foo(x, y, **kwargs):
...
```
Function call.
```python
foo(2,3,flag=True,mode='fast',header='debug')
```
The extra keywords are passed in a dictionary.
```python
def foo(x, y, **kwargs):
# x -> 2
# y -> 3
# kwargs -> { 'flat': True, 'mode': 'fast', 'header': 'debug' }
```
### Combining both
A function can also combine any number of variable keyword and non-keyword arguments.
Function definition.
```python
def foo(*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.
### Passing Tuples and Dicts
Tuples can be expanded into variable arguments.
```python
numbers = (2,3,4)
foo(1, *numbers) # Same as f(1,2,3,4)
```
Dictionaries can also be expaded into keyword arguments.
```python
options = {
'color' : 'red',
'delimiter' : ',',
'width' : 400
}
foo(data, **options)
# Same as foo(data, color='red', delimiter=',', width=400)
```
These are not commonly used except when writing library functions.
## Exercises
### (a) A simple example of variable arguments
Try defining the following function:
```python
>>> def avg(x,*more):
return float(x+sum(more))/(1+len(more))
>>> avg(10,11)
10.5
>>> avg(3,4,5)
4.0
>>> avg(1,2,3,4,5,6)
3.5
>>>
```
Notice how the parameter `*more` collects all of the extra arguments.
### (b) Passing tuple and dicts as arguments
Suppose you read some data from a file and obtained a tuple such as
this:
```
>>> data = ('GOOG', 100, 490.1)
>>>
```
Now, suppose you wanted to create a `Stock` object from this
data. If you try to pass `data` directly, it doesn't work:
```
>>> from stock import Stock
>>> s = Stock(data)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() takes exactly 4 arguments (2 given)
>>>
```
This is easily fixed using `*data` instead. Try this:
``python
>>> s = Stock(*data)
>>> s
Stock('GOOG', 100, 490.1)
>>>
```
If you have a dictionary, you can use `**` instead. For example:
```python
>>> data = { 'name': 'GOOG', 'shares': 100, 'price': 490.1 }
>>> s = Stock(**data)
Stock('GOOG', 100, 490.1)
>>>
```
### (c) Creating a list of instances
In your `report.py` program, you created a list of instances
using code like this:
```python
def read_portfolio(filename):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float])
portfolio = [ Stock(d['name'], d['shares'], d['price'])
for d in portdicts ]
return Portfolio(portfolio)
```
You can simplify that code using `Stock(**d)` instead. Make that change.
### (d) Argument pass-through
The `fileparse.parse_csv()` function has some options for changing the
file delimiter and for error reporting. Maybe you'd like to expose those
options to the `read_portfolio()` function above. Make this change:
```
def read_portfolio(filename, **opts):
'''
Read a stock portfolio file into a list of dictionaries with keys
name, shares, and price.
'''
with open(filename) as lines:
portdicts = fileparse.parse_csv(lines,
select=['name','shares','price'],
types=[str,int,float],
**opts)
portfolio = [ Stock(**d) for d in portdicts ]
return Portfolio(portfolio)
```
Once you've made the change, trying reading a file with some errors:
```python
>>> import report
>>> port = report.read_portfolio('Data/missing.csv')
Row 4: Couldn't convert ['MSFT', '', '51.23']
Row 4: Reason invalid literal for int() with base 10: ''
Row 7: Couldn't convert ['IBM', '', '70.44']
Row 7: Reason invalid literal for int() with base 10: ''
>>>
```
Now, try silencing the errors:
```python
>>> import report
>>> port = report.read_portfolio('Data/missing.csv', silence_errors=True)
>>>
```
[Next](02_Anonymous_function)