Python 2019

Лекция 2

Функции

  • Ограничения на имя функции в Python типичны: можно использовать буквы, подчеркивание _ и цифры от 0 до 9, но цифра не должна стоять на первом месте.

Объявление функций (1)

  • return использовать не обязательно, по умолчанию функция возвращает None.
>>> def foo():
...     return 42
... 
>>> foo()
42
>>> def foo():
...     42
... 
>>> print(foo())
None
  • Для документации функций используются строковые литералы

Объявление функций (2)

  • После объявления функции документация доступна через специальный атрибут
>>> def foo():
...     """I return 42.""
...     return 42
... 
>>> foo.__doc__
'I return 42.'
  • В интерпретаторе удобнее пользоваться встроенной функцией
>>> help(foo)  # или foo? в IPython.

Пример

Пример: min

>>> def min(x, y):                #    __o
...     return x if x < y else y  #  _`\<,_
...                               # (_)/ (_)
>>> min(-5, 12)                   # http://ascii.bike
-5
>>> min(x=-5, y=12)
-5
>>> min(x=-5, z=12)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: min() got an unexpected keyword argument 'z'
>>> min(y=12, x  =-5)  # порядок не важен
-5
  • Находить минимум произвольного кол-ва аргументом:

Пример: куда дальше?

>>> xs = {-5, 12, 13}
>>> min(???)
-5
  • Находить функцию min для кортежей, списков, множеств и других последовательностей
>>> min(-5, 12, 13)
-5
  • Находить минимум произвольного количества отрезком [lo, hi]:
>>> bounded_min(-5, 12, 13, lo=0, hi=255)
12
>>> bounded_min = make_min(lo=0, hi=255)
>>> bounded_min(-5, 12, 13)
12
  • По заданным lo и hi строить функцию bounded_min

Упаковка и распаковка

Вопрос

Как потребовать, чтобы в args был хотя бы один элемент?

Упаковка аргументов (1)

>>> def min(*args):  # type(args) == tuple.
...     res = float("inf")
...     for arg in args:
...         if arg < res:
...             res = arg
...     return res
... 
>>> min(-5, 12, 13)
-5
>>> min()
inf

Вопрос

Как применить функцию min к коллекции?

Упаковка аргументов (2)

>>> def min(first, *args):
...     res = first
...     # ...
... 
>>> min()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: min() missing 1 required [...] argument: 'first'
>>> xs = {-5, 12, 13}
>>> min(???)

Распаковка аргументов

  • Синтаксис будет работать с любым объектом, поддерживающим протокол итератора.
>>> xs = {-5, 12, 13}
>>> min(*xs)
-5
>>> min(*[-5, 12, 13])
-5
>>> min(*(-5, 12, 13))
-5
  • Об итераторах потом, а пока вспомним про bounded_min
>>> bounded_min(-5, 12, 13, lo=0, hi=255)
12

Вопрос

В какой момент происходит инициализация ключевых аргументов со значениями по умолчанию?

Аргументы по умолчанию

>>> def bounded_min(first, *args, lo=float("-inf"),
...                 hi=float("inf")):
...     res = hi
...     for arg in (first,) + args:
...         if arg < res and lo < arg < hi:
...             res = arg
...     return max(res, lo)
... 
>>> bounded_min(-5, 12, 13, lo=0, hi=255)
12

Подводные камни

>>> def unique(iterable, seen=set()):
...     acc = []
...     for item in iterable:
...         if item not in seen:
...             seen.add(item)
...             acc.append(item)
...     return acc
... 
>>> xs = [1, 1, 2, 3]
>>> unique(xs)
[1, 2, 3]
>>> unique(xs)
[]
>>> unique.__defaults__
({1, 2, 3},)

Правильная инициализация

>>> def unique(iterable, seen=None)):
...     seen = set(seen or [])  # None -- falsy
...     acc = []
...     for item in iterable:
...         if item not in seen:
...             seen.add(item)
...             acc.append(item)
...     return acc
... 
>>> xs = [1, 1, 2, 3]
>>> unique(xs)
[1, 2, 3]
>>> unique(xs)
[]
  • Если функция имеет фиксированную арность, то ключевые аргументы можно передавать без явного указания имени:
>>> def flatten(xs, depth=None):
...     pass
... 
>>> flatten([1, [2], 3], depth=1)
>>> flatten([1, [2], 3], 1)
  • Можно явно потребовать, чтобы часть аргументов всегда передавалась как ключевые:
>>> def flatten(xs, *, depth=None):
...     pass
... 
>>> flatten([1, [2], 3], 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: flatten() takes 1 positional argument [...]

Ключевые аргументы: Упаковка и распаковка

  • Ключевые аргументы, аналогично позиционным, можно упаковывать и распаковывать:
>>> def runner(cmd, **kwargs):
...     if kwargs.get("verbose". True):
...         print("Logging enabled")
... 
>>> runner("mysqld", limit=42)
Logging enabled
>>> runner("mysqld", **{"verbose": False})
>>> options = {"verbose": False}
>>> runner("mysqld", **options)
  • Поговорим о присваивании

Раскаковка и присваивание

>>> acc = []
>>> seen = set()
>>> (acc, seen) = ([], set())
  • В качестве правого аргумента можно использовать любой объект, поддерживающий протокол итератора
>>> x, y, z = [1, 2, 3]
>>> x, y, z = {1, 2, 3}  # unordered!
>>> x, y, z = "xyz"
  • Скобки обычно опускают, но иногда они бывают полезны
>>> rectangle = (0, 0), (4, 4)
>>> (x1, y1), (x2, y2) = rectangle

Расширенный синтаксис

  • В Python 3.0 был реализован расширенный синтаксис распаковки
>>> first, *rest = range(1, 5)
>>> first, rest
(1, [2, 3, 4])
  • * можно использовать в любом месте выражения
>>> first, *rest, last = range(1, 5)
>>> last
4
>>> first, *rest, last = [42]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: need more than 1 values to unpack
>>> *_, (first, *rest) = [range(1, 5)] * 5
>>> first
1

Синтаксис распаковки работает в цикле for, например:

Распаковка и цикл for

>>> for a, *b in [range(4), range(2)]:
...     print(b)
... 
[1, 2, 3]
[1]
  • Распаковка при вызове функций:

Еще больше распаковки

>>> def f(*args, **kwargs):
...     print(args, kwargs)
... 
>>> f(1, 2, *[3, 4], *[5],
...   foo="bar", **{"baz": 42}, boo=24)
(1, 2, 3, 4, 5) {{'baz': 42, 'boo': 24, 'foo': 'bar'}}
  • и при инициализации контейнеров:
>>> defaults = {"host": "0.0.0.0", "port": 8080}
>>> {**defaults, "port": 80}
{'host': '0.0.0.0', 'port': 80}
>>> [*range(5), 6]  # аналогично для множества и кортежа
[0, 1, 2, 3, 4, 6]
  • Функции в Python могут принимать произвольное количество позиционных и ключевых аргументов.
  • Для объявления таких функций используют синтаксис упаковки, а для вызова синтаксис распаковки

Резюме

>>> def f(*args, **kwargs):
...     pass
... 
>>> f(1, 2, 3, **{"foo": 42})
  • Синтаксис распаковки также можно использовать при присваивании нескольких аргументов и в цикле for
>>> first, *rest = range(4)
>>> for first, *rest in [range(4), range(2)]:
...     pass
...

Области видимости  aka scopes

  • В Python функции — объекты первого класса, то есть с ними можно делать все то же самое, что и с другими значениями.
  • Например, можно объявлять функции внутри других функций

Функции (внутри функций)+

>>> def wrapper():
...     def identify(x):
...         return x
...     return identity
... 
>>> f = wrapper()
>>> f(42)
42

make_min

>>> def make_min(*, lo, hi):
...     def min(first, *args):
...         res = hi
...         for arg in (first,) + args:
...             if arg < res and lo < arg < hi:
...                 res = arg
...         return max(res, lo)
...     return min
... 
>>> bounded_min = make_min(lo=0, hi=255)
>>> bounded_min(-5, 12, 13)
0

Правило LEGB

Поиск имени ведется не более, чем в четырех областях видимости: локальной, замет в объемлющей функции (если такая имеется, затем в глобальной и, наконец, во встроенной.

Области видимости: LEGB

>>> min                  # builtin
<built-in function min>
>>> min = 42             # global
>>> def f(*args):
...     min = 2          # enclosing
...     def g():
...         min = 4      # local
...         print(min)
...

Интроспекция

>>> min = 42      # ≡ globals()["min"] = 42
>>> globals()
{..., 'min': 42}
>>> def f():
...     min = 2   # ≡ locals()["min"] = 2
...     print(locals())
...
>>> f()
{'min': 2}
  • Функции в Python могут использовать переменные, определенные во внешних областях видимости.
  • Важно помнить, что поиск переменных осуществляется во время исполнения функции, а не во время ее объявления.

Замыкания

>>> def foo():
...     print(i)
... 
>>> for i in range(4):
...     f()
... 
0
1
2
3
  • Для присваивания правило LEGB не работает

Присваивание

>>> min = 42
>>> def f():
...     min += 1
...     return min
... 
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: local variable 'min' referenced [...]
  • По умолчанию операция присваивания создает локальную переменную.
  • Изменить это поведение можно с помощью операторов global и nonlocal.
  • Позволяет модифицировать значение переменной из глобальной области видимости

Оператор global

>>> min = 42
>>> def f():
...     global min
...     min += 1
...     return min
... 
>>> f()
42
>>> f()
44
  • Использование global порочно, почти всегда лучше заменить global на thread-local объект.

Оператор nonlocal

  • Позволяет модифицировать значение переменной из объемлющей области области видимости
>>> def cell(value=None):
...     def get():
...         return value
...     def set(update):
...         nonlocal value
...         value = update
...     return get, set
... 
>>> get, set = cell()
>>> set(42)
>>> get()
42
  • В Python четыре области видимости: встроенная, глобальная, объемлющая и локальная.
  • Правило LEGB: поиск имени осуществляется от локальной к встроенной.
  • При использовании операции присваивания имя считается локальным. Это поведение можно изменить с помощью операторов global и nonlocal.

Резюме

Функциональное программирование

  • Python не функциональный язык, но в нём есть элементы функционального программирования.

Анонимные функции

  • Анонимные функции имеют вид

и эквивалентны по поведению

  • Всё, сказанное про аргументы именованных функций справедливо и для анонимных
>>> lambda arguments: expression
>>> def <lambda>(arguments):
...     return expression
>>> lambda foo, *args, bar=None, **kwargs: 42
<function <lambda> at 0x100fb9730>
  • Применяет функцию к каждому элементу последовательности

map

>>> map(indentity, range(4))
<map object at 0x100fc4c88>
>>> list(map(indentity, range(4)))
[0, 1, 2, 3]
>>> set(map(lambda x: x % 7, [1, 9, 16, -1, 2, 5]))
{1, 2, 5, 6}
>>> map(lambda s: s.strip(), open("./HBA1.txt"))
<map object at 0x100fc4cc0>
  • или последовательностей, количество элементов в результате определяется длиной наименьшей из последовательностей
>>> list(map(lambda x, n: x ** n,
...          [2, 3], range(1, 8)))
[2, 9]

flter

  • Убирает из последовательности элементы, не удовлетворяющие предикату
>>> filter(lambda x: x % 2 != 0, range(10))
<filter object at 0x1011edfd0>
>>> list(filter(lambda x: x % 2 != 0, range(10)))
[1, 3, 5, 7, 9]
  • Вместо предиката можно предать None, в этом случае в последовательности останутся только truthy значения
>>> xs = [0, None, [], {}, set(), "", 42]
>>> list(filter(None, xs))
[42]
  • Строит последовательность кортежей из элементов нескольких последовательностей

zip

>>> list(zip("abc", range(3), [42j, 42j, 42j]))
[('a', 0, 42j), ('b', 1, 42j), ('c', 2, 42j)]
  • Поведение в случае последовательностей различной длины аналогично map.
>>> list(zip("abc", range(10))
[('a', 0), ('b', 1), ('c', 2)]
  • Пришли в Python из языка ABC, который позаимствовал их из языка SETL

Генераторы списков

>>> [x ** 2 for x in range(10) if x % 2 == 1]
[1, 9, 25, 49, 81]
  • Компактная альтернатива комбинациям map и filter
>>> list(map(lambda x: x ** 2,
...          filter(lambda x: x % 2 == 1,
...                 range(10))))
[1, 9, 25, 49, 81]
  • Могут быть вложенными
>>> nested = [range(5), range(8, 10)]
>>> [x for xs in nested for x in xs]  $ flatten
[0, 1, 2, 3, 4, 8, 9]

Генераторы множеств и словарей

>>> {x % 7 for x in [1, 9, 16, -1, 2, 5]}
{1, 2, 5, 6}
>>> date = {"year": 2014, "month": "September", "day": ""}
>>> {k: v for k, v in date.items() if v}
{'month': 'September', 'year': 2014}
>>> {x: x ** 2 for x in range(4)}
{0: 0, 1: 1, 2: 4, 3: 9}
  •  Наличие элементов функционального программирования позволяет компактно выражать вычисления.
  • В Python есть типичные для функциональных языков:
    • анонимные функции lambda,
    • функции map, filter и zip,
    • генераторы списков.
  • Синтаксис Python также поддерживает генерацию других коллекций: множеств и словарей.

Резюме

Made with Slides.com