2.3. Arbitrary Number of Arguments

2.3.1. Positional Arguments

  • * in this context, is not multiplication in mathematical sense

  • * is used for positional arguments

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

  • *args unpacks from tuple, list or set

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

echo(1, 2)
Listing 2.99. 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)

2.3.2. Keyword Arguments

  • ** in this context, is not power in mathematical sense

  • ** is used for keyword arguments

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

  • **kwargs unpacks from dict

Listing 2.100. 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 2.101. 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)

2.3.3. Positional and Keyword Arguments

Listing 2.102. 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 2.103. 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)

2.3.4. 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 = [
    (6.0, 3.4, 4.5, 1.6, 'versicolor'),
    (4.9, 3.1, 1.5, 0.1, "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)
# [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]
# 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)]

2.3.5. Objects From Mappings

DATA = {"sepal_length": 6.0, "sepal_width": 3.4, "petal_length": 4.5, "petal_width": 1.6, "species": "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 = [
    {"sepal_length": 6.0, "sepal_width": 3.4, "petal_length": 4.5, "petal_width": 1.6, "species": "versicolor"},
    {"sepal_length": 4.9, "sepal_width": 3.1, "petal_length": 1.5, "petal_width": 0.1, "species": "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)
# ['versicolor', 'setosa']

2.3.6. Examples

Listing 2.104. 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 2.105. 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 2.106. 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 2.107. 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 2.108. 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 2.109. 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 2.110. 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 2.111. 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 my_csv(file, encoding='utf-8', decimal=b',', lineterminator='\n', *args, **kwargs):
    return read_csv(file, encoding=encoding, decimal=decimal,
                    lineterminator=lineterminator, *args, **kwargs)


my_csv('iris1.csv')
my_csv('iris2.csv', encoding='iso-8859-2')
my_csv('iris3.csv', encoding='cp1250', verbose=True)
my_csv('iris4.csv', verbose=True, usecols=['Sepal Length', 'Species'])
Listing 2.112. 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(original_function):

    def wrapper(*args, **kwargs):
        user = kwargs['request'].user

        if user.is_authenticated():
            return original_function(*args, **kwargs)
        else:
            print('Permission denied')

    return wrapper


@login_required
def edit_profile(request):
    ...

2.3.7. Assignments

2.3.7.1. Function Args/Kwargs Arguments Define

English
  1. Mind the non-functional requirements (see below)

  2. Download data/iris.csv and save as iris.csv

  3. Remove species column

  4. Separate header from measurements

  5. For each line extract values by splitting lines by coma ,

  6. Create result: List[dict] by zipping header and measurements:

    • key: column name from the header

    • value: measurement at the position

  7. Create function mean(*values), function

  8. Iterate over result and call mean() by passing arguments positionally

  9. Print mean for each row

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

  11. Non-functional requirements:

    • Use only str.split() method

    • Don't use pandas, numpy or csv etc.

Polish
  1. Pobierz plik data/iris.csv i zapisz jako iris.csv

  2. Usuń kolumnę species

  3. Odseparuj nagłówek od pomiarów

  4. Wyciągnij wartości z każdej linii przez podział jej po przecinku ,

  5. Stwórz result: List[dict] poprzez scalenie nagłówka i pomiarów z każdego wiersza

    • klucz: nazwa kolumny z nagłówka

    • wartość: pomiar z odpowiedniej kolumny

  6. Stwórz funkcję mean(*values)

  7. Iterując po result wywołuj mean() podając argumenty pozycyjnie

  8. Wypisz średnią dla każdego wiersza

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

  10. Wymagania niefunkcjonalne:

    • Użyj tylko metody str.split()

    • Nie używaj pandas, numpy, csv itp.

Output
header: list
# ['sepal_length', 'sepal_width' ,'petal_length', 'petal_width']

result: List[Dict[str, float]] = [
    {'sepal_length': 5.4, 'sepal_width': 3.9, 'petal_length': 1.3, 'petal_width': 0.4},
    {'sepal_length': 5.9, 'sepal_width': 3.0, 'petal_length': 5.1, 'petal_width': 1.8},
    {'sepal_length': 6.0, 'sepal_width': 3.4, 'petal_length': 4.5, 'petal_width': 1.6},
    ...
]
Hint
  • map(float, measurements)