# 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¶

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,
'database': 'my_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):
permission = ['all', 'everywhere']
return template('user_details.html', **locals())

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

controller('Jan', 'Twardowski')
# Template: user_details.html
# Data: {'firstname': 'Jan',
#        'lastname': 'Twardowski',
#        'uid': 0,
#        '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,
skipfooter=0, doublequote=True, delim_whitespace=False, low_memory=True,
memory_map=False, float_precision=None):
"""
"""

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

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

def edit_profile(request):
...


## 3.3.9. Assignments¶

### 3.3.9.1. Function Args/Kwargs Arguments Define¶

• Assignment name: Function Args/Kwargs Arguments Define

• Last update: 2020-10-12

• Complexity level: medium

• Lines of code to write: 15 lines

• Estimated time of completion: 13 min

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)]