определяющее ключевое слово
- Ключевое слово def связывает тело функции с именем таким образом, что функции представляют собой такие же объекты, как и все остальное в Python.
- def выполняется во время выполнения => функции создаются во время выполнения
Локальные функции
- Функции, определенные внутри других функций, называются локальными функциями, поскольку они определены локально в области действия конкретной функции.
Локальные функции не являются элементами объемлющих функций, они просто определяются в пределах объема объемлющей функции.
def func(a, b): def local_func(x, y): return x * y return local_func(a, b)
В приведенном выше примере local_func является локальным для области видимости func. Таким образом, мы можем сказать, что local_func — это локальная функция внутри func.
Каждый вызов func приводит к новому определению local_func. Таким образом, local_func привязывается к отдельному телу функции при каждом вызове.
Давайте проверим это:
store = [] def func(a, b): def local_func(x, y): # store the function instances in the store list store.append(local_func) print(local_func) print(x * y) return local_func(a, b) func(2, 3) func(2, 6)
Это результат:
<function func.<locals>.local_func at 0x0596DBB8> 6 <function func.<locals>.local_func at 0x0596DC00> 12
Как мы можем сделать вывод из вывода, адреса двух функций действительно разные. Таким образом, при каждом вызове func создается новая функция.
Локальные функции подчиняются тем же правилам области видимости, что и другие функции:
- Л — местный
- Е — Охватывающий
- G — глобальный
- Б — встроенный
Функции, возвращающие функции
def outer(): def inner(): print('inner') return inner x = outer() x()
Выход:
inner
Теперь мы можем снять два важных определения:
- Функции первого класса: функции, с которыми можно обращаться как с любыми другими объектами, например с передачей в качестве аргумента, возвратом из другой функции или назначением переменной.
- Функции высшего порядка: функции, которые принимают другие функции в качестве аргументов или возвращают другие функции в качестве результатов.
Это приводит нас к замыканиям. Что такое замыкания?
Закрытия
Замыкание — это запись, в которой хранится функция и ее окружение (где она создается, т. е. область охвата функции).
Прежде чем двигаться дальше, давайте попробуем понять, что именно я имею в виду под средой.
Среда – это сопоставление, связывающее каждую свободную переменную в функции со значением или местом хранения, к которому эта переменная была привязана при создании замыкания.
Замыкание, в отличие от простой функции, имеет доступ к этим свободным переменным даже при вызове из-за пределов их области видимости.
Давайте посмотрим на пример:
def outer(): x = 'closure1' y = 'closure2' def inner(): print(x, y) return inner local_func = outer() local_func() print(local_func.__closure__)
Вот результат:
closure1 closure2 (<cell at 0x02A5B3F0: str object at 0x02B2E2A0>, <cell at 0x02A5B470: str object at 0x02B3A520>)
Строка print(local_func.__closure__) приводит к набору, содержащему два объекта. Первое — это x, сопоставленное с «closure1», а второе — это сопоставление y с «closure2».
Теперь, когда мы знаем, что такое замыкания, давайте рассмотрим, что такое фабрика функций.
Фабрика функций
Фабрика функций — это функция, которая возвращает новую специализированную функцию, которая выполняет операцию со своими собственными аргументами, а также с аргументами, переданными в фабрику функций.
def outer(x): def inner(y): return(y + " " + x) return inner # This line assigns local_func to inner # but at the same time assigns argument x of outer to 'Manoj' # Once outer returns inner, all the variables that inner refers # to are stored in an environment local_func = outer('Manoj') print(local_func('Hello')) print(local_func.__closure__)
Выход:
Hello Manoj (<cell at 0x03DD2710: str object at 0x03D8B3E0>,)
Вторая строка вывода представляет собой набор с одним элементом, который представляет собой строковый объект «Manoj», на который ссылается аргумент x внешней функции.
Как получить доступ к глобальным и окружающим переменным области видимости внутри локальной функции?
Мы можем получить доступ и изменить глобальные и объемлющие переменные области внутри локальной функции с помощью глобальных и нелокальных ключевых слов и, таким образом, избежать создания новых переменных с тем же именем, что и у глобальных и объемлющих переменных области видимости.
Декораторы функций
Декораторы — это способ изменения или улучшения существующих функций ненавязчивым и удобным для сопровождения способом.
Проще говоря, декораторы принимают функцию (или вызываемый объект) в качестве аргумента и возвращают функцию (или вызываемый объект).
Ниже приведена диаграмма, показывающая, как работает декоратор:
Что нужно помнить о декораторе:
- Decorator заменяет, улучшает или модифицирует существующую функцию.
- Исходное определение функции остается неизменным
- Decorator использует исходное имя модифицированной функции.
def escape_unicode(f): def wrap(*args, **kwargs): x = f(*args, **kwargs) return ascii(x) return wrap @escape_unicode def unic(): return 'TromŒ' print(unic())
Результат:
'Trom\u0152'
Таким образом, функция escape_unicode модифицирует функцию unicode и возвращает новую оболочку вызываемой функции, которая является телом новой функции для unicode.
Прежде чем мы двинемся дальше, некоторым из вас может быть интересно, что такое *args и **kwargs. Позволь мне объяснить.
def args_func(*args): print(args) print(args_func(['a', 'b'])) # (['a', 'b'],) # Iterates through the elements # of the list print(args_func(*['a', 'b'])) # ('a', 'b') def kwargs_func(**kwargs): print(kwargs) print(kwargs_func(a=1, b=2)) # {'a': 1, 'b': 2} print(kwargs_func(**{'a': 1, 'b': 2})) # {'a': 1, 'b': 2}
args хранит все позиционные аргументы, переданные функции, а kwargs хранит словари и пары ключ-значение.
Декораторы класса
Классы также можно использовать в качестве декораторов. Классы можно вызывать, и при вызове класса будет создан новый экземпляр.
class CallCount: def __init__(self, f): self.f = f self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 return self.f(*args, **kwargs) @CallCount def hello(name): print('Hello {}'.format(name))
В приведенном выше примере, когда hello вызывается say as hello('Michael'), будет создан новый экземпляр CallCount. Объект функции, созданный вызовом hello, будет назначен атрибуту f CallCount. Экземпляр будет немедленно возвращен, что означает, что hello теперь будет ссылаться на возвращенный экземпляр CallCount.
hello('Manoj') Hello Manoj hello('William') Hello William hello.count => 2 hello.f('Manoj') Hello Manoj
Декораторы экземпляров
Декораторы экземпляров — это экземпляры, вызываемые немедленно с функцией, переданной в качестве аргумента функции __call__ класса экземпляра и возвращающей вызываемый объект.
class Trace: def __init__(self): self.enabled = True def __call__(self, f): def wrap(*args, **kwargs): """ A wrapper function """ if self.enabled: print('Calling {}'.format(f)) return f(*args, **kwargs) return wrap tracer = Trace() @tracer def rotate_list(l): """ Rotates a list """ return l[1:] + [l[0]] l = [1,2,3] print(rotate_list(l)) Calling <function rotate_list at 0x7f86fe38a488> => [2, 3, 1] tracer.enabled = False print(rotate_list(l)) => [2, 3, 1]
Это все о декораторах. Мы также можем использовать несколько декораторов, где декораторы выполняются в порядке, обратном применению к функции.
Итак, мы закончили. Ну, еще нет. Важная концепция еще предстоит обсудить. Декораторы помогают нам писать модульный код, но в то же время они изменяют метаданные исходной функции, когда вызываемый объект возвращается из оригинала.
Не верите мне? Проверьте сами.
В приведенном выше примере, когда мы печатаем rotate_list.__name__, вместо rotate_list мы получаем wrap. Точно так же, когда мы печатаем rotate_list.__doc__, вместо «Поворачивает список» мы видим «Функция-обертка». Это не хорошо.
Один из способов решить эту проблему — обновить __doc__ и __name__ функции переноса. Но есть лучший способ.
functools.wrap()
functool.wraps() правильно обновляет метаданные обернутых функций.
import functools class Trace: def __init__(self): self.enabled = True def __call__(self, f): @functools.wraps(f) def wrap(*args, **kwargs): """ Wrap the function """ if self.enabled: print('Calling {}'.format(f)) return f(*args, **kwargs) return wrap tracer = Trace() @tracer def rotate_list(l): """ Rotates a list """ l = l[1:] + [l[0]] return l
Теперь rotate_list.__doc__ и rotate_list.__name__ дадут правильные результаты.
Декораторы для проверки аргументов
Прежде чем мы закончим, давайте посмотрим, как можно использовать декораторы для проверки аргументов функций. Обратите внимание, что функция check_hero возвращает декоратор с именем validator.
def check_hero(name): def validator(f): def wrap(*args): if args[0] == name: raise ValueError( '{} is a villain'.format(name) ) return f(*args) return wrap return validator @check_hero('Thanos') def check(value): return '{} is a hero'.format(value) check('Tony') # Tony is a hero check('Thanos') # Throws ValueError: Thanos is a villain