3.3. Arbitrary Number of Arguments

3.3.1. Recap

  • argument - Value/variable/reference being passed to the function

  • positional argument - Value passed to function - order is important

  • keyword arguments - Value passed to function resolved by name - order is not important

  • keyword arguments must be on the right side

  • order of keyword arguments doesn't matter

echo(1)          # positional argument
echo(a=1)        # keyword argument
echo(1, 2)       # positional arguments
echo(2, 1)       # positional arguments
echo(a=1, b=2)   # keyword arguments
echo(b=2, a=1)   # keyword arguments, order doesn't matter
echo(1, b=2)     # positional and keyword arguments
echo(a=1, 2)     # SyntaxError: positional argument follows keyword argument

3.3.2. Rationale

../../_images/function-unpacking,args,kwargs.png

Figure 3.15. Unpacking and Arbitrary Number of Parameters and Arguments

3.3.3. Positional Arguments

  • * is used for positional arguments

  • args is a convention, but you can use any name

  • *args unpacks from tuple, list or set

Listing 3.123. Positional arguments passed directly
def echo(a, b, c=0):
    print(a)    # 1
    print(b)    # 2
    print(c)    # 0

echo(1, 2)
Listing 3.124. Positional arguments passed from sequence
def echo(a, b, c=0):
    print(a)    # 1
    print(b)    # 2
    print(c)    # 0

args = (1, 2)
echo(*args)
Listing 3.125. Positional arguments passed from sequence
def echo(a, b, c=0):
    print(a)    # 1
    print(b)    # 2
    print(c)    # 0

args = (1, 2)
echo(args)
# Traceback (most recent call last):
#     ...
# TypeError: echo() missing 1 required positional argument: 'b'

3.3.4. Keyword Arguments

  • ** is used for keyword arguments

  • kwargs is a convention, but you can use any name

  • **kwargs unpacks from dict

Listing 3.126. Keyword arguments passed directly
def echo(a, b, c=0):
    print(a)    # 1
    print(b)    # 2
    print(c)    # 0

echo(a=1, b=2)
Listing 3.127. Keyword arguments passed from dict
def echo(a, b, c=0):
    print(a)    # 1
    print(b)    # 2
    print(c)    # 0

kwargs = {'a': 1, 'b': 2}
echo(**kwargs)

3.3.5. Positional and Keyword Arguments

Listing 3.128. Positional and keyword arguments passed directly
def echo(a, b, c=0):
    print(a)    # 1
    print(b)    # 2
    print(c)    # 0

echo(1, b=2)
Listing 3.129. Positional and keyword arguments passed from sequence and dict
def echo(a, b, c=0):
    print(a)    # 1
    print(b)    # 2
    print(c)    # 0

args = (1,)
kwargs = {'b': 2}

echo(*args, **kwargs)

3.3.6. Objects From Sequence

DATA = (6.0, 3.4, 4.5, 1.6, 'versicolor')

class Iris:
    def __init__(self, sepal_length, sepal_width, petal_length, petal_width, species):
        self.sepal_length = sepal_length
        self.sepal_width = sepal_width
        self.petal_length = petal_length
        self.petal_width = petal_width
        self.species = species

iris = Iris(*DATA)
iris.species
# 'versicolor'
DATA = [
    (5.8, 2.7, 5.1, 1.9, 'virginica'),
    (5.1, 3.5, 1.4, 0.2, 'setosa'),
    (5.7, 2.8, 4.1, 1.3, 'versicolor'),
    (6.3, 2.9, 5.6, 1.8, 'virginica'),
    (6.4, 3.2, 4.5, 1.5, 'versicolor'),
    (4.7, 3.2, 1.3, 0.2, 'setosa'),
]

class Iris:
    def __init__(self, sepal_length, sepal_width, petal_length, petal_width, species):
        self.sepal_length = sepal_length
        self.sepal_width = sepal_width
        self.petal_length = petal_length
        self.petal_width = petal_width
        self.species = species

    def __repr__(self):
        return f'{self.species}'

result = [Iris(*row) for row in DATA]
print(result)
# [virginica, setosa, versicolor,
#  virginica, versicolor, setosa]
from dataclasses import dataclass

MOVEMENT = [
    (0, 0),
    (1, 0),
    (2, 1, 1),
    (3, 2),
    (3, 3, -1),
    (2, 3),
]

@dataclass
class Point:
    x: int
    y: int
    z: int = 0

movement = [Point(x,y) for x,y in MOVEMENT]
# Traceback (most recent call last):
#     ...
# ValueError: too many values to unpack (expected 2)

movement = [Point(*coordinates) for coordinates in MOVEMENT]

movement
# [Point(x=0, y=0, z=0),
#  Point(x=1, y=0, z=0),
#  Point(x=2, y=1, z=1),
#  Point(x=3, y=2, z=0),
#  Point(x=3, y=3, z=-1),
#  Point(x=2, y=3, z=0)]

3.3.7. Objects From Mappings

DATA = {"sepalLength":5.8,"sepalWidth":2.7,"petalLength":5.1,"petalWidth":1.9,"species":"virginica"}

class Iris:
    def __init__(self, sepalLength, sepalWidth, petalLength, petalWidth, species):
        self.sepal_length = sepalLength
        self.sepal_width = sepalWidth
        self.petal_length = petalLength
        self.petal_width = petalWidth
        self.species = species

iris = Iris(**DATA)
iris.species
# 'virginica'
DATA = [{"sepalLength":5.8,"sepalWidth":2.7,"petalLength":5.1,"petalWidth":1.9,"species":"virginica"},
        {"sepalLength":5.1,"sepalWidth":3.5,"petalLength":1.4,"petalWidth":0.2,"species":"setosa"},
        {"sepalLength":5.7,"sepalWidth":2.8,"petalLength":4.1,"petalWidth":1.3,"species":"versicolor"},
        {"sepalLength":6.3,"sepalWidth":2.9,"petalLength":5.6,"petalWidth":1.8,"species":"virginica"},
        {"sepalLength":6.4,"sepalWidth":3.2,"petalLength":4.5,"petalWidth":1.5,"species":"versicolor"},
        {"sepalLength":4.7,"sepalWidth":3.2,"petalLength":1.3,"petalWidth":0.2,"species":"setosa"}]


class Iris:
    def __init__(self, sepalLength, sepalWidth, petalLength, petalWidth, species):
        self.sepal_length = sepalLength
        self.sepal_width = sepalWidth
        self.petal_length = petalLength
        self.petal_width = petalWidth
        self.species = species

    def __repr__(self):
        return f'{self.species}'


result = [Iris(**row) for row in DATA]
print(result)
# [virginica, setosa, versicolor,
#  virginica, versicolor, setosa]

3.3.8. Examples

Listing 3.130. Defining complex number by passing keyword arguments directly
complex(real=3, imag=5)
# (3+5j)


number = {'real': 3, 'imag': 5}
complex(**number)
# (3+5j)
Listing 3.131. Passing vector to the function
def cartesian_coordinates(x, y, z):
    print(x)    # 1
    print(y)    # 0
    print(z)    # 1


vector = (1, 0, 1)
cartesian_coordinates(*vector)
Listing 3.132. Passing point to the function
def cartesian_coordinates(x, y, z):
    print(x)    # 1
    print(y)    # 0
    print(z)    # 1


point = {'x': 1, 'y': 0, 'z': 1}
cartesian_coordinates(**point)
Listing 3.133. str.format() expects keyword arguments, which keys are used in string. It is cumbersome to pass format(name=name, agency=agency) for every variable in the code. Since Python 3.6 f-string formatting are preferred.
firstname = 'Jan'
lastname = 'Twardowski'
location = 'Moon'

result = 'Astronaut {firstname} {lastname} on the {location}'.format(**locals())
print(result)
# Astronaut Jan Twardowski on the Moon
Listing 3.134. Calling a function which has similar parameters. Passing configuration to the function, which sets parameters from the config
def draw_line(x, y, color, type, width, markers):
    ...


draw_line(x=1, y=2, color='red', type='dashed', width='2px', markers='disc')
draw_line(x=3, y=4, color='red', type='dashed', width='2px', markers='disc')
draw_line(x=5, y=6, color='red', type='dashed', width='2px', markers='disc')


style = {'color': 'red',
         'type': 'dashed',
         'width': '2px',
         'markers': 'disc'}

draw_line(x=1, y=2, **style)
draw_line(x=3, y=4, **style)
draw_line(x=5, y=6, **style)
Listing 3.135. Database connection configuration read from config file
config = {
    'host': 'localhost',
    'port': 5432,
    'username': 'my_username',
    'password': 'my_password',
    'database': 'my_database'}


def database_connect(host, port, username, password, database):
    return ...


connection = database_connect(**config)
Listing 3.136. Calling function with all variables from higher order function. locals() will return a dict with all the variables in local scope of the function.
def template(template, **user_data):
    print('Template:', template)
    print('Data:', user_data)


def controller(firstname, lastname, uid=0):
    groups = ['admins', 'astronauts']
    permission = ['all', 'everywhere']
    return template('user_details.html', **locals())

    # template('user_details.html',
    #    firstname='Jan',
    #    lastname='Twardowski',
    #    uid=0,
    #    groups=['admins', 'astronauts'],
    #    permission=['all', 'everywhere'])


controller('Jan', 'Twardowski')
# Template: user_details.html
# Data: {'firstname': 'Jan',
#        'lastname': 'Twardowski',
#        'uid': 0,
#        'groups': ['admins', 'astronauts'],
#        'permission': ['all', 'everywhere']}
Listing 3.137. Proxy functions. One of the most common use of *args, **kwargs.
def read_csv(filepath_or_buffer, sep=', ', delimiter=None, header='infer',
             names=None, index_col=None, usecols=None, squeeze=False, prefix=None,
             mangle_dupe_cols=True, dtype=None, engine=None, converters=None,
             true_values=None, false_values=None, skipinitialspace=False,
             skiprows=None, nrows=None, na_values=None, keep_default_na=True,
             na_filter=True, verbose=False, skip_blank_lines=True, parse_dates=False,
             infer_datetime_format=False, keep_date_col=False, date_parser=None,
             dayfirst=False, iterator=False, chunksize=None, compression='infer',
             thousands=None, decimal=b'.', lineterminator=None, quotechar='"',
             quoting=0, escapechar=None, comment=None, encoding=None, dialect=None,
             tupleize_cols=None, error_bad_lines=True, warn_bad_lines=True,
             skipfooter=0, doublequote=True, delim_whitespace=False, low_memory=True,
             memory_map=False, float_precision=None):
    """
    Definition of pandas.read_csv() function
    https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html
    """


def mycsv(file, encoding='utf-8', decimal=b',',
          lineterminator='\n', *args, **kwargs):

    return read_csv(file, encoding=encoding, decimal=decimal,
                    lineterminator=lineterminator, *args, **kwargs)


mycsv('iris1.csv')
mycsv('iris2.csv', encoding='iso-8859-2')
mycsv('iris3.csv', encoding='cp1250', verbose=True)
mycsv('iris4.csv', verbose=True, usecols=['Sepal Length', 'Species'])
Listing 3.138. Decorators. Decorators are functions, which get pointer to the decorated function as it's argument, and has closure which gets original function arguments as positional and keyword arguments.
def login_required(func):
    def wrapper(request, *args, **kwargs):
        if not request.user.is_authenticated():
            raise PermissionError
        return func(*args, **kwargs)
    return wrapper


@login_required
def edit_profile(request):
    ...

3.3.9. Assignments

3.3.9.1. Function Args/Kwargs Arguments Define

English
  1. Use data from "Input" section (see below)

  2. Create function mean(*args), which calculates arithmetic mean for args

  3. Do not import any libraries and modules

  4. Define result: list[tuple[str, float]]

  5. Iterate over DATA separating features from label

  6. To result append label and arithmetic mean of features

  7. Compare result with "Output" section (see below)

Polish
  1. Użyj kodu z sekcji "Input" (patrz poniżej)

  2. Stwórz funkcję mean(*args), która liczy średnią arytmetyczną dla args

  3. Nie importuj żadnych bibliotek i modułów

  4. Zdefiniuj result: list[tuple[str, float]]

  5. Iteruj po DATA separując features od label

  6. Do result dodawaj label oraz wynik średniej arytmetycznej features

  7. Porównaj wyniki z sekcją "Output" (patrz poniżej)

Input
DATA = [
    ('Sepal length', 'Sepal width', 'Petal length', 'Petal width', 'Species'),
    (5.8, 2.7, 5.1, 1.9, 'virginica'),
    (5.1, 0.2, 'setosa'),
    (5.7, 2.8, 4.1, 1.3, 'versicolor'),
    (6.3, 5.7, 'virginica'),
    (6.4, 1.5, 'versicolor'),
    (4.7,  'setosa'),
]
Output
>>> mean(1)
1.0
>>> mean(1, 3)
2.0
>>> mean(1, 2, 3)
2.0
>>> mean()
Traceback (most recent call last):
    ...
ValueError: At least one argument is required

>>> assert type(result) is list
>>> assert all(type(row) is tuple for row in result)

>>> result  # doctest: +NORMALIZE_WHITESPACE
[('virginica', 3.875),
 ('setosa', 2.65),
 ('versicolor', 3.475),
 ('virginica', 6.0),
 ('versicolor', 3.95),
 ('setosa', 4.7)]