Рубрики
Без рубрики

Закрытия и декораторы в Python

В этом руководстве учит вас два продвинутых навыка Python: закрытия и декораторы. Освоение их сделает вас лучшим кодировщиком сегодня – так и давайте погрузимся прямо в них! Застегивание Каждая функция в Python является первым классом, потому что их можно пропустить, как любой другой объект. Обычно, когда язык программирования создает функцию, как другие данные … Закрытия и декораторы в Python Подробнее »

Автор оригинала: Francisco R Porrata.

В этом руководстве учит вас два продвинутых навыка Python: закрытия и декораторы. Освоение их сделает вам лучший кодер сегодня – так что давайте погрузимся прямо в них!

Закрытие

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

Закрытие – это вложенная функция. Он определен в пределах внешней функции.

def outer_hello_fn():
    def hello():
        print("Hello Finxter!")
        
    hello()

Здесь у нас есть внешняя функция под названием OUTER_ HELLO_ FN он не имеет входных аргументов. Функция Привет это вложенная функция, определенная в пределах внешней функции. Привет Функция – это закрытие.

Попробуй сам:

Упражнение : Что такое вывод этого кода фрагмент? Запустите код, чтобы проверить, если вы правильны.

Когда внешняя функция называется, Привет Функция внутри этого будет определена, а затем вызывается. Вот функция вызова и вывод:

outer_hello_fn()

Выход:

Hello Finxter!

Здравствуйте, был определен в пределах wrible_hello_fn , что означает, что если вы попытаетесь вызовите Привет Функция, это не будет работать.

hello()

Выход:

NameError: name 'hello' is not defined

Если вы хотите получить доступ к функции, определенной в другой функции, верните саму объект функции. Вот как.

def get_hello_fn():
    def hello():
        print("Hello Finxter!")

    return hello

Внешняя функция называется get_hello_fn Отказ Привет , это внутренняя функция или закрытие. Вместо того, чтобы вызвать эту функцию Hello, просто верните Привет Функция для тех, кто называет get_hello_fn Отказ Например:

hello_fn = get_hello_fn()

Вызов get_hello_fn хранит объект обратной функции в hello_fn Переменная. Если вы изучите содержимое этого hello_fn Переменная, вы увидите, что это функциональный объект.

hello_fn

Выход:

.hello>

Как вы можете видеть в структуре, это локально определена функция внутри get_hello_fn То есть функция определена в другой функции, то есть закрытие. Теперь это закрытие может быть вызвано с использованием переменной Hello_FN.

hello_fn()

Выход:

Hello Finxter!

Вызывать hello_fn () распечатает Привет Finxter! на экран. Закрытие – это нечто большее, чем просто внутренняя функция, определенная в наружной функции. Еще больше. Вот еще один пример:

def hello_by_name(name):
    
    def hello():
        print("Hello!", name)
        
    hello()
    
    return hello

Здесь внешняя функция называется hello_by_name , который принимает один входной аргумент, имя человека. В этой внешней функции есть Привет Внутренняя функция. Он печатает на экран Привет! и значение имени.

Переменная имени является входным аргументом для внешней функции. Это также доступно в рамках внутренней функции Hello. Переменная имени здесь можно рассматривать как переменную, которая является локальной для внешней функции. Локальные переменные во внешней функции могут быть доступны закрытиями. Вот пример передачи аргумента на внешнюю функцию:

greet_hello_fn = hello_by_name("Chris")

Функция Hello возвращается, и она хранится в greet_hello_fn Переменная.

Выполнение этой отпечатки Привет! Крис на экран. Это потому, что мы вызывали закрытие изнутри внешней функции. У нас есть ссылка на закрытие, которое было определено внешней функцией.

greet_hello_fn()

Выход:

Hello! Chris

Обратите внимание на что-то интересное здесь. Крис доступен в имени переменной, который является локальным для hello_by_name функция.

Теперь мы уже вызвали и вышли hello_by_name Но значение в переменной имени все еще доступна для нашего закрытия. И это еще одна важная концепция о закрытиях в Python. Они проводят ссылку на локальное состояние даже после внешней функции, которая определила локальное состояние, и больше не существует. Вот еще один слегка другой пример, иллюстрирующий эту концепцию.

def greet_by_name(name):
    
    greeting_msg = "Hi there!"

    def greeting():
        print(greeting_msg, name)
        
    return greeting

Внешняя функция, green_by_name , принимает один входной аргумент, имя. Внутри внешней функции локальная переменная называется greeting_msg определяется, что говорит, "Всем привет!" . Закрытие, называемое приветствие, определяется в пределах внешней функции. Доступ к локальной переменной greeting_msg а также имя аргумента ввода. Ссылка на это приветствие возвращается из внешнего green_by_name функция.

Давайте пойдем вперед и придумайте HELT_BY_NAME и храните объект функции, который он возвращается в переменную HELE_FN. Мы будем использовать этот объект функции для приветствия Ray по имени. Идите вперед и вызовите Green_fn (), указав скобки. И это должно сказать, привет там! Рэй. Соблюдайте о том, как закрытие имеет доступ не только к названию Ray, но и к приветствию, даже после того, как мы выполнили и вызовели внешнюю функцию.

greet_fn = greet_by_name("Ray")
greet_fn()

Выход:

Hi there! Ray

Закрытие переносится вокруг информации о местном государстве. Давайте посмотрим, что произойдет, когда функция Green_by_name удаляется, поэтому у вас больше нет доступа к внешней функции.

del greet_by_name

Теперь помните, что название и сообщение приветствия являются оба переменными, которые были определены во внешней функции. Что происходит с ними? Теперь, если вы попытаетесь призвать приветствовать по имени.

greet_by_name("Ray")

Выход:

NameError: name 'greet_by_name' is not defined

Как насчет greet_fn?

Помните, что greet_fn – это ссылка на наше закрытие. Это все еще работает?

greet_fn()

Выход:

Hi there! Ray

Он не только работает, но он все еще имеет доступ к локальным переменным, которые были определены во внешней функции. Внешняя функция больше не существует в памяти Python, но локальные переменные все еще доступны вместе с нашим закрытием.

Декораторы – модификация кода

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

def print_message():
    print("Decorators are cool!")

Вот простая функция, которая печатает сообщение на экран.

print_message()

Выход:

Decorators are cool!

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

import random

def highlight():
    
    annotations = ['-', '*', '+', ':', '^']
    annotate = random.choice(annotations)
    
    print(annotate * 50)
    
    print_message()
    
    print(annotate * 50)

Внешняя функция выделения не имеет входных аргументов. В рамках функции Highlight случайного выбора аннотаций используется для украшения исходного сообщения. Сообщение будет выделено случайным выбором между тире, звездочкой, плюсом, толстой кишкой и калесой. Вывод будет иметь аннотацию 50 символов до и после сообщения, которое находится внутри функции print_message.

Попробуй сам:

Упражнение : Что такое вывод этого кода фрагмент? Запустите код, чтобы проверить ваше понимание!

highlight()

Выход:

::::::::::::::::::::::::::::::::::::::::::::::::::
Decorators are cool!
::::::::::::::::::::::::::::::::::::::::::::::::::

Вот еще одна функция с другим сообщением, print_another_message.

def print_another_message():
    print("Decorators use closures.")

Теперь, если я хочу также выделить это сообщение, существующая функция Highlight не будет работать, потому что она была жестко настроена, чтобы вызвать функцию print_message. Итак, как я могу изменить эту функцию выделения, чтобы она способна подчеркнуть любое сообщение, которое я хочу распечатать на экране? Помните, что функции являются частными гражданами в Python, что означает, что у вас есть функция печати, вы можете пройти ее в качестве входного аргумента к функции выделения. Вот переопределенная функция выделения, make_highlighted.

def make_highlighted(func):
    
    annotations = ['-', '*', '+', ':', '^']
    annotate = random.choice(annotations)
    
    def highlight():
        print(annotate * 50)
        func()
        print(annotate * 50)            
    
    return highlight

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

print_message()
print_another_message()

И теперь с помощью функции make_highlighted любое напечатанное сообщение может быть выделено. Например:

highlight_and_print_message = make_highlighted(print_message)

highlight_and_print_message()

Выход:

++++++++++++++++++++++++++++++++++++++++++++++++++
Decorators are cool!
++++++++++++++++++++++++++++++++++++++++++++++++++

Чтобы распечатать другое сообщение и выделить его, просто пропустите другой функциональный объект к функции make_highlighted.

highlight_and_print_another_message = make_highlighted(print_another_message)

highlight_and_print_another_message()

Выход:

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Decorators use closures.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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

Почему это декоратор? Ну, это требуется в функциональном объекте и украшает его и меняет его. В этом примере он подчеркивает функцию со случайными символами. Декораторы – это стандартный рисунок дизайна, а в Python вы можете легко использовать декораторы. Вместо того, чтобы проходить в функциональном объекте для make_highlighted, доступа к закрытию, а затем, вызывая замыкание, вы можете просто украсить любую функцию, используя @ и размещение декоратора перед функцией для украшения.

@make_highlighted
def print_a_third_message():
    print("This is how decorators are used")

Использование декоратора @make_highlighted будет автоматически передавать функцию print_a_third_message в качестве ввода для make_highlighted и выделить сообщение.

print_a_third_message()

Выход:

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is how decorators are used
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Теперь вы можете использовать декоратор, чтобы выделить любые сообщения.

@make_highlighted
def print_any_message():
    print("This message is highlighted!")

И теперь, если вы вызываете print_ane_message, вы обнаружите, что результат, который отображается на экран, выделен.

print_any_message()

Выход:

++++++++++++++++++++++++++++++++++++++++++++++++++
This message is highlighted!
++++++++++++++++++++++++++++++++++++++++++++++++++

Декораторы – настройка

Давайте посмотрим другой пример декоратора, который будет делать некоторую работу. Это сделает некоторую проверку ошибок для нас.

Вот две функции, которые будут входным в наш декоратор

def square_area(length):
    
    return length**2

def square_perimeter(length):
    
    return 4 * length

Мы предполагаем, что значение радиуса, передаваемого положительным и правильным.

square_area(5)

Выход:

25

Что, если я вызовую квадрату_арею и пропустите -1?

square_area(-1)

Выход:

-4

Вход -1 не имеет смысла в качестве значения для длины. Функция должна была выбросить ошибку или сказать нам каким-то образом, что отрицательные значения длины не действительны. Теперь, если вы должны были выполнить проверку ошибки для каждой из этих функций, нам придется сделать это индивидуально. Мы должны были бы иметь заявление о если в рамках функции области, а также функции периметра. Вместо этого давайте напишем декоратор, который выполнит эту проверку этой ошибки для нас. Декоратор Safe_Calculate принимает один входной аргумент, который является функциональным объектом.

def safe_calculate(func):
    
    def calculate(length):
        if length <= 0:
            raise ValueError("Length cannot be negative or zero")
        
        return func(length)
    
    return calculate

Это функциональный объект, который выполнит расчет. Внутри внешней функции Safe_Calculate внутренняя функция называется рассчитанным, – это закрытие. Рассчитать принимает один входной аргумент, длина. Он проверяет, стоит ли длина меньше или равна 0. Если да, это бросает ошибку. И так, как он бросает ошибку, просто вызывая Rosing ValueError, «длина не может быть отрицательной или ноль». Как только мы поднимем эту ошибку, Python остановит выполнение. Но если длина положительна, она будет вызывать функцию функции и пройти длину в качестве входного аргумента. Safe_Calculate – это наш декоратор, который принимает в качестве входной функции объекта и возвращает замыкание, которое будет выполнять безопасный расчет.

square_area_safe = safe_calculate(square_area)

Давайте проверим это первым:

square_area_safe(5)

Это безопасно, и я получаю результат здесь на экране.

25

Призывая его с отрицательным числом поднимет ошибку

square_area_safe(-1)

Выход:

ValueError: Length cannot be negative or zero

Давайте украсьте функцию периметра, а также с Safe_calculate.

square_perimeter_safe = safe_calculate(square_perimeter)

square_perimeter(10)

Выход:

40

Но если вы должны были позвонить в Square_Perimeter_Safe с отрицательным значением для длины хорошо, то есть ValueError.

square_perimeter_safe(-10)

Выход:

ValueError: Length cannot be negative or zero

Теперь, когда у вас есть декоратор, вы должны украсить ваши функции, а не использовать способ, которым мы использовали до сих пор.

@safe_calculate
def square_area(length):
    return length**2

@safe_calculate
def square_perimeter(length):
    return 4 * length

Теперь в следующий раз называется Square_area или Square_PeriMeter, проверка безопасности будет выполнена.

square_perimeter(3)

Выход:

12

Если вы попытаетесь рассчитать периметр для отрицательного значения длины, вы получите ValuctionError. Функция Safe_Calculate, которую мы устанавливаем ранее, имеет ограничение, и вы увидите, что это в будущем примере.

square_perimeter(-3)

Выход:

ValueError: Length cannot be negative or zero

Что происходит, когда у вас есть более одного ввода? Вот функция, которая рассчитывает область прямоугольника.

@safe_calculate
def rectangle_area(length, width):
    return length * width

В рамках нашей функции Safe_calculate мы вызвали объект Func, который выполняет расчет только одним входным аргументом, только с переменной длиной. Это будет вызвать проблему, когда мы используем декоратор Safe_Calculate для функции прямоугольника_ареа.

Как только я украсил эту функцию, я собираюсь вызвать его с 4, 5.

rectangle_area(4, 5)

Выход:

TypeError: calculate() takes 1 positional argument but 2 were given

Проблема в том, как мы определили закрытие внутри функции Safe_Calculate.

Закрытие расчета принимает только один входной аргумент. Если функция имеет несколько аргументов ввода, то Safe_Calculate не может быть использован. Функция переопределенного Safe_Calculate_all ниже:

def safe_calculate_all(func):
    
    def calculate(*args):
        
        for arg in args:
            if arg <= 0:
                raise ValueError("Argument cannot be negative or zero")
        
        return func(*args)
    
    return calculate. 

Он принимает один входной аргумент, который является объектом функционала, который должен быть украшен. Основное изменение находится в входных аргументах, которые передаются в рассчитанное закрытие. Рассчитайте функцию теперь принимают аргументы переменной длины, * args.   Функция, иташивающая все аргументы, которые были переданы, и проверяет, будет ли аргумент меньше или равен 0. Если какой-либо из аргументов меньше или равен 0, leteletror будет повышен. Помните, * args распаковывает оригинальные аргументы, чтобы элементы кортеже пропущены индивидуально на объект функции, функции func. Теперь вы можете использовать этот декоратор Safe_Calculate_all с функциями, которые имеют любое количество аргументов.

@safe_calculate_all
def rectangle_area(length, width):
    return length * width
rectangle_area(10, 3)

Выход:

30

Давайте попробуем вызовать ту же функцию, но на этот раз один из аргументов отрицательный. Ширина отрицательная, и это дает мне ValueError, благодаря нашему декоратору Safe_Calculate_all.

rectangle_area(10, -3)

Когда вы вызываете эту функцию, она проверит все аргументы.

ValueError: Argument cannot be negative or zero

Неважно, какой аргумент отрицательный, вы все еще получаете ValueError. Здесь длина отрицательна:

rectangle_area(-10, 3)

Выход:

ValueError: Argument cannot be negative or zero

Сторирование декораторов

Вы можете иметь функцию, украшенную с помощью нескольких декораторов. И эти декораторы будут приковываться вместе.

Вот два декоратора, одна печатающая звездочка и другие знаки плюс

def asterisk_highlight(func):
    
    def highlight():
        print("*" * 50)

        func()

        print("*" * 50)            
    
    return highlight

def plus_highlight(func):
    
    def highlight():
        print("+" * 50)

        func()

        print("+" * 50)            
    
    return highlight

PRINT_MESSAGE_ONE украшена звездочкой_highlight.

@asterisk_highlight
def print_message_one():
    print("Decorators are cool!") 
print_message_one()

Выход:

**************************************************
Decorators are cool!
**************************************************

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

@plus_highlight
@asterisk_highlight
def print_message_one():
    print("Decorators are cool!")

То, что вы видите здесь, является примером цепочки декораторов вместе. Но как они приковываются? Какое украшение начнется первым, Asterisk_Highlight или Pluse_ighlight? Какой декоратор является самым близким к определению функции, является то, что выполняется первым, а затем декоратор, который находится дальше от определения функции. Это означает, что сообщение будет сначала выделено звездочкой, а затем плюс.

print_message_one()

Выход:

++++++++++++++++++++++++++++++++++++++++++++++++++
**************************************************
Decorators are cool!
**************************************************
++++++++++++++++++++++++++++++++++++++++++++++++++

Если вы измените заказ декораторов, порядок украшений также изменится.

@asterisk_highlight
@plus_highlight
def print_message_one():
    print("Decorators are cool!") 

У вас будет та же функция print_message_one, но декоратор, который ближе всего к определению функции, – это Plus_Highlight, а затем asterisk_highlight.

print_message_one()

Выход:

**************************************************
++++++++++++++++++++++++++++++++++++++++++++++++++
Decorators are cool!
++++++++++++++++++++++++++++++++++++++++++++++++++
**************************************************

Использование Kwargs в декораторах

В этом примере мы используем Kwargs для отображения разных сообщений для декоратора, которые времена выполняются выполнение функции

def timeit(func):
        def timed(*args, **kw):
            if 'start_timeit_desc' in kw:
                print(kw.get('start_timeit_desc'))
            ts = time.time()
            result = func(*args, **kw)
            te = time.time()
            if 'end_timeit_desc' in kw:
                print('Running time for {} is {} ms'.format(kw.get('end_timeit_desc'), (te - ts) * 1000))
            return result
        return timed 

Декоратор Timeit используется для функции тестирования. Три параметра передаются к тесту функции: A, B и ** Kwargs. Параметры A и B обрабатываются в декоратере с * args, как мы видели раньше. Параметр ** kwargs используется для передачи описаний для функции. Эти параметры start_timeit_desc и end_timeit_desc. Эти два параметра проверяются внутри закрытого замыкания и будут отображать в них сообщения.

@timeit
def test(a,b, **kwargs):
    return a * b


result = test(10,20, start_timeit_desc = "Start of test(10,20)...", end_timeit_desc = "End of test(10,20)")
print("result of test(10,20) = " + str(result))
Output:
Start of test(10,20)...
Running time for End of test(10,20) is 0.0 ms
result of test(10,20) = 200

Оригинал: “https://blog.finxter.com/closures-and-decorators-in-python/”