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

4.6 KiB

7.1 Variable Arguments

Positional variable arguments (*args)

A function that accepts any number of arguments is said to use variable arguments. For example:

def foo(x, *args):
    ...

Function call.

foo(1,2,3,4,5)

The arguments get passed as a tuple.

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:

def foo(x, y, **kwargs):
    ...

Function call.

foo(2,3,flag=True,mode='fast',header='debug')

The extra keywords are passed in a dictionary.

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.

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.

numbers = (2,3,4)
foo(1, *numbers)      # Same as f(1,2,3,4)

Dictionaries can also be expaded into keyword arguments.

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:

>>> 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:

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:

>>> 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:

>>> import report
>>> port = report.read_portfolio('Data/missing.csv', silence_errors=True)
>>>

Next