Обяснено (с примери и случаи на употреба)

Декораторите на Python са невероятно полезна конструкция в Python. Използвайки декоратори в Python, можем да променим поведението на функция, като я обвием в друга функция. Декораторите ни позволяват да пишем по-чист код и да споделяме функционалност. Тази статия е урок не само как да използвате декоратори, но и как да ги създавате.

Предварителни знания

Темата за декораторите в Python изисква някои основни познания. По-долу съм изброил някои концепции, с които вече трябва да сте запознати, за да разберете смисъла на този урок. Свързах също ресурси, където можете да освежите концепциите, ако е необходимо.

Основен Python

Тази тема е за по-среден/напреднал. В резултат на това, преди да се опитате да научите, трябва вече да сте запознати с основите на Python, като типове данни, функции, обекти и класове.

Трябва също така да разбирате някои обектно-ориентирани концепции като гетери, сетери и конструктори. Ако не сте запознати с езика за програмиране Python, ето някои ресурси, за да започнете.

Функции са първокласни граждани

В допълнение към основния Python, трябва да сте наясно и с тази по-разширена концепция в Python. Функциите и почти всичко останало в Python са обекти като int или string. Тъй като те са обекти, можете да правите няколко неща с тях, а именно:

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

Всъщност единствената разлика между функционалните обекти и другите обекти е, че функционалните обекти съдържат магическия метод __call__().

Надяваме се, че на този етап се чувствате комфортно с необходимите познания. Можем да започнем да обсъждаме основната тема.

Какво е декоратор на Python?

Декораторът на Python е просто функция, която приема функция като аргумент и връща модифицирана версия на функцията, която е била предадена. С други думи, функцията foo е декоратор, ако приема като аргумент лентата на функциите и връща друга функция baz.

Функцията baz е модификация на bar в смисъл, че в тялото на baz има извикване на лентата на функциите. Въпреки това, преди и след обаждането до бара, baz може да направи всичко. Това беше хапка; ето код за илюстрация на ситуацията:

# Foo is a decorator, it takes in another function, bar as an argument
def foo(bar):

    # Here we create baz, a modified version of bar
    # baz will call bar but can do anything before and after the function call
    def baz():

        # Before calling bar, we print something
        print("Something")

        # Then we run bar by making a function call
        bar()

        # Then we print something else after running bar
        print("Something else")

    # Lastly, foo returns baz, a modified version of bar
    return baz

Как да създадете декоратор в Python?

За да илюстрирам как се създават и използват декоратори в Python, ще илюстрирам това с прост пример. В този пример ще създадем функция декоратор на регистратор, която ще регистрира името на функцията, която декорира всеки път, когато тази функция се изпълнява.

  4 начина за бързо създаване на бележка на iPhone или iPad

За да започнем, създадохме функцията декоратор. Декораторът приема func като аргумент. func е функцията, която декорираме.

def create_logger(func):
    # The function body goes here

Вътре във функцията декоратор ще създадем нашата модифицирана функция, която ще регистрира името на func, преди да стартира func.

# Inside create_logger
def modified_func():
    print("Calling: ", func.__name__)
    func()

След това функцията create_logger ще върне модифицираната функция. В резултат на това цялата функция create_logger ще изглежда така:

def create_logger(func):
    def modified_func():
        print("Calling: ", func.__name__)
        func()

    return modified_function

Приключихме със създаването на декоратора. Функцията create_logger е прост пример за функция декоратор. Той приема func, която е функцията, която декорираме, и връща друга функция, modified_func. modified_func първо регистрира името на func, преди да стартира func.

Как да използвате декоратори в Python

За да използваме нашия декоратор, използваме синтаксиса @ така:

@create_logger
def say_hello():
    print("Hello, World!")

Сега можем да извикаме say_hello() в нашия скрипт и изходът трябва да бъде следният текст:

Calling:  say_hello
"Hello, World"

Но какво прави @create_logger? Е, той прилага декоратора към нашата функция say_hello. За да разберете по-добре какво се прави, кодът непосредствено под този параграф ще постигне същия резултат като поставянето на @create_logger преди say_hello.

def say_hello():
    print("Hello, World!")

say_hello = create_logger(say_hello)

С други думи, един от начините за използване на декоратори в Python е изрично извикване на декоратора, предаване на функцията, както направихме в кода по-горе. Другият и по-сбит начин е да използвате синтаксиса @.

В този раздел разгледахме как да създаваме декоратори на Python.

Малко по-сложни примери

Горният пример беше прост случай. Има малко по-сложни примери, като например когато функцията, която декорираме, приема аргументи. Друга по-сложна ситуация е, когато искате да украсите цял клас. Тук ще разгледам и двете ситуации.

Когато функцията приема аргументи

Когато функцията, която декорирате, приема аргументи, модифицираната функция трябва да получи аргументите и да ги предаде, когато евентуално извика немодифицираната функция. Ако това звучи объркващо, позволете ми да обясня с термините на foo-bar.

Спомнете си, че foo е функцията декоратор, bar е функцията, която декорираме, а baz е декорираната лента. В този случай bar ще приеме аргументите и ще ги предаде на baz по време на извикването на baz. Ето пример за код за затвърждаване на концепцията:

def foo(bar):
    def baz(*args, **kwargs):
        # You can do something here
        ___
        # Then we make the call to bar, passing in args and kwargs
        bar(*args, **kwargs)
        # You can also do something here
        ___

    return baz

Ако *args и **kwargs изглеждат непознати; те са просто указатели съответно към позиционните и ключовите аргументи.

Важно е да се отбележи, че baz има достъп до аргументите и следователно може да извърши известно валидиране на аргументите, преди да извика bar.

Пример би бил, ако имаме функция декоратор, secure_string, която ще гарантира, че аргументът, предаден на функция, която декорира, е низ; ще го приложим така:

def ensure_string(func):
    def decorated_func(text):
        if type(text) is not str:
             raise TypeError('argument to ' + func.__name__ + ' must be a string.')
        else:
             func(text)

    return decorated_func

Можем да украсим функцията say_hello така:

@ensure_string
def say_hello(name):
    print('Hello', name)

След това можем да тестваме кода, използвайки това:

say_hello('John') # Should run just fine
say_hello(3) # Should throw an exception

И трябва да произведе следния изход:

Hello John
Traceback (most recent call last):
   File "/home/anesu/Documents/python-tutorial/./decorators.py", line 20, in <module> say hello(3) # should throw an exception
   File "/home/anesu/Documents/python-tu$ ./decorators.pytorial/./decorators.py", line 7, in decorated_func raise TypeError('argument to + func._name_ + must be a string.')
TypeError: argument to say hello must be a string. $0

Както се очакваше, скриптът успя да отпечата „Hello John“, защото „John“ е низ. Той хвърли изключение при опит за отпечатване на „Hello 3“, защото „3“ не беше низ. Декораторът secure_string може да се използва за валидиране на аргументите на всяка функция, която изисква низ.

  Как работят сгъваемите телефони и кога ще получа такъв?

Декориране на клас

В допълнение към функциите за декориране, ние можем да декорираме и класове. Когато добавите декоратор към клас, декорираният метод замества метода на конструктора/инициатора на класа (__init__).

Връщайки се към foo-bar, да предположим, че foo е нашият декоратор и Bar е класът, който декорираме, тогава foo ще украси Bar.__init__. Това ще бъде полезно, ако искаме да направим нещо, преди да бъдат инстанцирани обекти от типа Bar.

Това означава, че следният код

def foo(func):
    def new_func(*args, **kwargs):
        print('Doing some stuff before instantiation')
        func(*args, **kwargs)

    return new_func

@foo
class Bar:
    def __init__(self):
        print("In initiator")

Е еквивалентно на

def foo(func):
    def new_func(*args, **kwargs):
        print('Doing some stuff before instantiation')
        func(*args, **kwargs)

    return new_func

class Bar:
    def __init__(self):
        print("In initiator")


Bar.__init__ = foo(Bar.__init__)

Всъщност инстанцирането на обект от клас Bar, дефиниран чрез някой от двата метода, трябва да ви даде същия изход:

Doing some stuff before instantiation
In initiator

Примерни декоратори в Python

Въпреки че можете да дефинирате свои собствени декоратори, има някои, които вече са вградени в Python. Ето някои от обичайните декоратори, които може да срещнете в Python:

@статичен метод

Статичният метод се използва в клас, за да покаже, че методът, който декорира, е статичен метод. Статичните методи са методи, които могат да се изпълняват без необходимост от инстанциране на класа. В следващия пример на код създаваме клас Dog със статичен метод bark.

class Dog:
    @staticmethod
    def bark():
        print('Woof, woof!')

Сега методът на кората може да бъде достъпен така:

Dog.bark()

И изпълнението на кода ще доведе до следния резултат:

Woof, woof!

Както споменах в раздела Как да използваме декоратори, декораторите могат да се използват по два начина. Синтаксисът @ е по-сбит и е един от двата. Другият метод е да извикаме функцията декоратор, като подаваме функцията, която искаме да декорираме като аргумент. Това означава, че кодът по-горе постига същото като кода по-долу:

class Dog:
    def bark():
        print('Woof, woof!')

Dog.bark = staticmethod(Dog.bark)

И все още можем да използваме метода на кората по същия начин

Dog.bark()

И ще произведе същия резултат

Woof, woof!

Както можете да видите, първият метод е по-чист и е по-очевидно, че функцията е статична функция, преди дори да сте започнали да четете кода. В резултат на това за останалите примери ще използвам първия метод. Но не забравяйте, че вторият метод е алтернативен.

@classmethod

Този декоратор се използва, за да посочи, че методът, който декорира, е метод на клас. Методите на класа са подобни на статичните методи по това, че и двата не изискват класът да бъде инстанциран, преди да могат да бъдат извикани.

  11 най-добър софтуер за управление на продукти за модерни приложения

Основната разлика обаче е, че методите на класа имат достъп до атрибутите на класа, докато статичните методи нямат. Това е така, защото Python автоматично предава класа като първи аргумент на метод на клас, когато той бъде извикан. За да създадем клас метод в Python, можем да използваме декоратора на classmethod.

class Dog:
    @classmethod
    def what_are_you(cls):
        print("I am a " + cls.__name__ + "!")

За да изпълним кода, просто извикваме метода, без да инстанцираме класа:

Dog.what_are_you()

И изходът е:

I am a Dog!

@Имот

Декораторът на свойства се използва за етикетиране на метод като настройка на свойства. Връщайки се към нашия пример с куче, нека създадем метод, който извлича името на кучето.

class Dog:
    # Creating a constructor method that takes in the dog's name
    def __init__(self, name):

         # Creating a private property name
         # The double underscores make the attribute private
         self.__name = name

    
    @property
    def name(self):
        return self.__name

Сега имаме достъп до името на кучето като нормална собственост,

# Creating an instance of the class
foo = Dog('foo')

# Accessing the name property
print("The dog's name is:", foo.name)

И резултатът от изпълнението на кода ще бъде

The dog's name is: foo

@property.setter

Декораторът property.setter се използва за създаване на метод за настройка за нашите свойства. За да използвате декоратора @property.setter, замествате свойството с името на свойството, за което създавате сетер. Например, ако създавате сетер за метода за свойството foo, вашият декоратор ще бъде @foo.setter. Ето пример за куче за илюстрация:

class Dog:
    # Creating a constructor method that takes in the dog's name
    def __init__(self, name):

         # Creating a private property name
         # The double underscores make the attribute private
         self.__name = name

    
    @property
    def name(self):
        return self.__name

    # Creating a setter for our name property
    @name.setter
    def name(self, new_name):
        self.__name = new_name

За да тестваме сетера, можем да използваме следния код:

# Creating a new dog
foo = Dog('foo')

# Changing the dog's name
foo.name="bar"

# Printing the dog's name to the screen
print("The dog's new name is:", foo.name)

Изпълнението на кода ще доведе до следния резултат:

The dogs's new name is: bar

Значение на декораторите в Python

Сега, след като разгледахме какво представляват декораторите и вие видяхте някои примери за декоратори, можем да обсъдим защо декораторите са важни в Python. Декораторите са важни по няколко причини. Някои от тях съм изброил по-долу:

  • Те позволяват повторно използване на кода: В примера за регистриране, даден по-горе, можем да използваме @create_logger за всяка функция, която искаме. Това ни позволява да добавим функция за регистриране към всички наши функции, без да я записваме ръчно за всяка функция.
  • Те ви позволяват да пишете модулен код: Отново, връщайки се към примера за регистриране, с декораторите можете да отделите основната функция, в този случай say_hello, от другата функционалност, от която се нуждаете, в този случай регистриране.
  • Те подобряват рамките и библиотеките: Декораторите се използват широко в рамките и библиотеките на Python, за да предоставят допълнителна функционалност. Например в уеб рамки като Flask или Django декораторите се използват за дефиниране на маршрути, обработка на удостоверяване или прилагане на междинен софтуер към конкретни изгледи.

Заключителни думи

Декораторите са невероятно полезни; можете да ги използвате за разширяване на функции, без да променяте функционалността им. Това е полезно, когато искате да замерите изпълнението на функциите, да регистрирате всеки път, когато дадена функция е извикана, да потвърдите аргументите, преди да извикате функция, или да проверите разрешенията, преди дадена функция да бъде изпълнена. След като разберете декораторите, ще можете да пишете код по по-чист начин.

След това може да искате да прочетете нашите статии за кортежи и използване на cURL в Python.