27 January 2009

Основы Python — кратко. Часть 6. Расширенное определение функций.

Python
Продолжение, начало см. тут.

В этом разделе мы поговорим более подробно про определение функций, и раскроем некоторые Python-специфичные особенности данного процесса. Так как информации много, то постараюсь излагать все достаточно кратко.

Параметры по-умолчанию


Для всех параметров функций можно указывать значения по-умолчанию, это дает возможность вызвать функцию с меньшим числом параметров. Например, у нас есть функция для авторизации пользователя на сайте:
def login(username="anonymous", password=None):
    """Тут какие-то действия"""
    pass

# вызвать эу функцию мы можем одним 
# из нижеприведенных способов
login("root", "ujdyzysqgfhjkm")
login("guest")
login()
# мы можем указать какой из параметров мы передаем, 
# указав его имя в явном виде
login(password="nobody@mail.com") 


В общем, те параметры что есть – сопоставляются слева направо (если имя не указано конкретно), остальные заменяются значениями по-умолчанию (если они конечно заданы).
Важной особенностью в данном случае является то, что значения по-умолчанию вычисляются и ассоциируются только один раз – в момент объявления функции. Все вытекающие из этого недостатки наглядно продемонстрирует пример:
def_val = 2
def our_func(val = def_val):
    print val

our_func(4)    # выведет 4
our_func()     # выведет 2 – значение по-умолчанию
def_val = 12
our_func()     # все равно 2, так как def_val было равно 2 на момент объявления

Более неприятное следствие из этого. Допустим, мы хотим объявить функцию, принимающую на вход список, что-то с ним делающую и печатающую его. Причем если список не задан, то по умолчанию он равен пустому.
Попытка сделать это «в лоб» будет работать не совсем так как хотелось бы:
In [1]: def lister(lst = []):
   ...:     lst.append([1, 2, 3])
   ...:     print lst
   ...:

In [2]: lister()
[[1, 2, 3]]

In [3]: lister()
[[1, 2, 3], [1, 2, 3]]

In [4]: lister()
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]

Собственно, проблема тут в том, что переменная lst будет ассоциирована с пустым списком один раз, и между вызовами будет сохранять свое значение.
В данном случае, правильно будет описать нашу функцию следующим образом (как рекомендуют все учебники):
In [5]: def lister2(lst=None):
   ...:     if lst is None:
   ...:         lst=[]
   ...:     lst.append([1, 2, 3])
   ...:     print lst
   ...:

In [6]: lister2()
[[1, 2, 3]]

In [7]: lister2()
[[1, 2, 3]]

In [8]: lister2([4, 5, 6])
[4, 5, 6, [1, 2, 3]]

Данная функция как раз будет работать так как хотелось бы изначально.

Position и keyword аргументы.


Зачастую случается необходимость сделать функцию, которая обрабатывает неопределенное число параметров. Например функция расчета суммы элементов списка.
Мы конечно можем передавать все аргументы как один параметр типа list, но это выглядит некрасиво. Потому в Пайтоне был придуман специальный механизм, называемый position-arguments. Вот пример, демонстрирующий использование.
In [9]: def list_sum(*args):
   ...:     smm = 0
   ...:     for arg in args:
   ...:         smm += arg
   ...:     return smm
   ...:

In [10]: list_sum(1, 2, 3)
Out[10]: 6

In [11]: list_sum(1)
Out[11]: 1

In [12]: list_sum()
Out[12]: 0

В данном случае, все наши параметры «упаковываются» в список args в соответствии с их «порядковым номером» при передаче.
Возможна и обратная операция, допустим у нас есть список значений, и мы хотим передать их как список параметров функции:
In [14]: lst = [1, 10, 2]

In [15]: list(range(*lst))
Out[15]: [1, 3, 5, 7, 9]

В этом примере список lst был «распакован» и подставлен на место параметров функции range, то есть вызов был аналогичен:
In [16]: list(range(1, 10, 2))
Out[16]: [1, 3, 5, 7, 9]

Кроме position, можно использовать и т.н. keyword аргументы. Они отличаются тем что для них надо явно задавать имя. Вот пример – функция, генерирующая insert выражение для базы данных (NB: максимальная оптимизация не ставилась в данном случае за самоцель).
def enquote1(in_str):
    """Quotes input string with single-quote"""
    in_str = in_str.replace("'", r"\'")
    return "'%s'" % in_str

def enquote2(in_str):
    """Quotes input string with double-quote"""
    in_str = in_str.replace('"', r'\"')
    return '"%s"' % in_str

def gen_insert(table, **kwargs):
    """Generates DB insert statement"""
    cols = []
    vals = []
    for col, val in kwargs.items():
        cols.append(enquote2(col))
        vals.append(enquote1(str(val)))
    cols = ", ".join(cols)
    vals = ", ".join(vals)

    return 'INSERT INTO "%s"(%s) VALUES(%s);' % (
            table, cols, vals)

print gen_insert("workers", name="John", age=21, salary=1500.0)
params = {"name": "Mary", "age": 19, "salary": 1200.0}
print gen_insert("workers", **params)

На выходе мы получим то что и ожидалось:
INSERT INTO "workers"("salary", "age", "name") VALUES('1500.0', '21', 'John');
INSERT INTO "workers"("salary", "age", "name") VALUES('1200.0', '19', 'Mary');

Обратите внимание на второй вызов функции gen_insert – так мы можем вызвать функцию имея только словарь параметров. Это применимо к любой функции. Так же возможны различные сочетания positional и keyword аргументов.

В качестве завершающего примера рассмотрим такую функцию:
def logging(func, *args, **kwargs):
    res = func(*args, **kwargs)
    print 'calling %s, returned %r' % (func.__name__, res)
    return res

def double(x):
    "Doubles a number"
    return 2*x

print logging(double, 155)

Это – простейший способ отладки функции, мы как бы «оборачиваем» вызов одной функции другой что бы вывести промежуточную информацию.
Забегая вперед – скажу что в Пайтоне есть очень мощный инструмент для более удобного использования подобного рода «оборачивающих» функций, называемый декораторами, но про это позже.

На этом на сегодня все. Продолжение следует (либо мое, либо уважаемого Gerlion), оставайтесь с нами.

«Домашнее задание»


Доработайте функцию logging, добавив в нее вывод параметров с которыми вызывается дочерняя функция, и обработку возможных исключительных ситуаций в дочерней функции.
Tags:pythonосновыфункциипараметры
Hubs: Python
+38
65.5k 187
Comments 77
Top of the last 24 hours