определяющее ключевое слово

  • Ключевое слово 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

Теперь мы можем снять два важных определения:

  1. Функции первого класса: функции, с которыми можно обращаться как с любыми другими объектами, например с передачей в качестве аргумента, возвратом из другой функции или назначением переменной.
  2. Функции высшего порядка: функции, которые принимают другие функции в качестве аргументов или возвращают другие функции в качестве результатов.

Это приводит нас к замыканиям. Что такое замыкания?

Закрытия

Замыкание — это запись, в которой хранится функция и ее окружение (где она создается, т. е. область охвата функции).

Прежде чем двигаться дальше, давайте попробуем понять, что именно я имею в виду под средой.

Среда – это сопоставление, связывающее каждую свободную переменную в функции со значением или местом хранения, к которому эта переменная была привязана при создании замыкания.

Замыкание, в отличие от простой функции, имеет доступ к этим свободным переменным даже при вызове из-за пределов их области видимости.

Давайте посмотрим на пример:

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 внешней функции.

Как получить доступ к глобальным и окружающим переменным области видимости внутри локальной функции?

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

Декораторы функций

Декораторы — это способ изменения или улучшения существующих функций ненавязчивым и удобным для сопровождения способом.

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

Ниже приведена диаграмма, показывающая, как работает декоратор:

Что нужно помнить о декораторе:

  1. Decorator заменяет, улучшает или модифицирует существующую функцию.
  2. Исходное определение функции остается неизменным
  3. 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