Декораторите на 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, ще илюстрирам това с прост пример. В този пример ще създадем функция декоратор на регистратор, която ще регистрира името на функцията, която декорира всеки път, когато тази функция се изпълнява.
За да започнем, създадохме функцията декоратор. Декораторът приема 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
Този декоратор се използва, за да посочи, че методът, който декорира, е метод на клас. Методите на класа са подобни на статичните методи по това, че и двата не изискват класът да бъде инстанциран, преди да могат да бъдат извикани.
Основната разлика обаче е, че методите на класа имат достъп до атрибутите на класа, докато статичните методи нямат. Това е така, защото 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.