- Декораторы в Python
- Декораторы в Python
- Функция с аргументами
- functools.wraps
- Декораторы с аргументами
- Объекты-декораторы
- Декоратор метода
- Декоратор класса на примере Singleton
- Встроенные декораторы
- Python: Decorators in OOP
- A guide on classmethods, staticmethods and the property decorator
- Table of Contents
- An example
- classmethod
- The getter and setter methods
- Adding getter and setter methods
- The deleter method
- use-cases for the property decorator
- Note#1: Where should you define a decorator wrt a class?
- Decorator inside Python class
- Let’s suppose we need a decorator that we gonna use inside one specific class only. Something like this:
- Note#2: Are there options other than constructor overloading in Java to simulate the methods we discussed (like, from_str or from_tuple)?
- Conclusion
Декораторы в Python
Декоратор (Decorator) — структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности. 1
Декораторы в Python
Декоратор — это функция, которая позволяет обернуть другую функцию для расширения её функциональности без непосредственного изменения её кода.
Чтобы лучше пониманять работу декораторов нужно помнить тот факт, что:
- в Python всё является объектами;
- Функции — это объекты первого класса;
- следовательно, язык поддерживает функции высших порядков.
Объектами первого класса в контексте конкретного языка программирования называются элементы, с которыми можно делать всё то же, что и с любым другим объектом: передавать как параметр, возвращать из функции и присваивать переменной.
Функции высших порядков — это такие функции, которые могут принимать в качестве аргументов и возвращать другие функции.
Это означает, что мы можем:
- сохранять функции в переменные;
- передавать их в качестве аргументов;
- возвращать из других функций;
- определить одну функцию внутри другой (вложенные функции).
По сути, это и позволяет реализовать декоратор:
Выражение @decorator — это синтаксический сахар, короткая запись для outer_func = decorator(outer_func) .
Еще немного о декораторах:
- декораторы можно вкладывать друг в друга (при этом порядок декорирования важен);
- декораторы можно использовать с другими методами (например, «магическими»);
- декораторы могут принимать в качестве аргументов не только функции.
Условные недостатки декораторов:
- несколько замедляют вызов функции
- если функция декорирована — это не отменить (существуют трюки, позволяющие создать декоратор, который можно отсоединить от функции, но это плохая практика)
- оборачивают функции, что может затруднить отладку (лечится использованием functools.wraps ).
Области применения декораторов:
- когда нужно избежать повторений при использовании похожих методов;
- могут быть использованы для расширения возможностей функций из сторонних библиотек (код которых мы не можем изменять);
- в Django декораторы используются для управления кешированием, контроля за правами доступа и определения обработчиков адресов;
- в Twisted — для создания поддельных асинхронных inline-вызовов.
Функция с аргументами
Допустим, теперь декорируемая функция outer_func() может принимать произвольное количество аргументов.
Чтобы наш декоратор работал корректно необходимо использовать *args и **kwargs (распаковка аргументов) во внутренней функции wrapper() , а так же передавать произвольное число позиционных и ключевых аргументов функции func() , которую декоратор получает в качестве аргумента:
Теперь декоратор @decorator будет работать как для функций, которые вообще не принимают аргументы, так и для функций которые принимают произвольное количество аргументов.
functools.wraps
Из-за того, что декоратор возвращает не первоначальную функцию, а функцию обертку wrapper — теряется строка документации (docstring) основной функции, доступ к которой можно получить с помощью метода __doc__ .
Функция wraps из модуля functools копирует всю информацию об оборачиваемой функции (имя, модуля, docstrings и т.п.) в функцию-обёртку.
Декораторы с аргументами
Чтобы передать параметр в сам декоратор нужно добавить еще один слой абстракции, то есть — еще одну функцию-обертку.
Функция benchmark() не является декоратором. Это обычная функция, которая принимает аргументы type и iters , а затем возвращает декоратор. В свою очередь, он декорирует функцию outer_func() . Поэтому мы использовали не выражение @benchmark , а @benchmark(type=»ms», iters=100) — круглые скобки означают, что функция вызывается, после чего возвращает сам декоратор.
Объекты-декораторы
Декоратором могут быть не только функции, но и любые вызываемые объекты. Экземпляры класса с методом __call__ тоже можно вызывать, поэтому классы можно использовать в качестве декораторов.
Для функций с параметрами *args и **kwargs нужно передать в метод __call__ .
Для передачи аргументов в класс-декоратор эти аргументы получает инициализатор __init__ . Метод __call__ будет получать декорируемую функцию и возвращать функцию-обертку, которая, по сути, будет выполнять эту декорируемую функцию. Функция-обертка wrapper получает *args и **kwargs :
Декоратор метода
Функции и методы в Python — это практически одно и то же. Отличие в том, что методы всегда принимают первым параметром self (ссылку на объект). Следовательно, мы легко можем написать декоратор для метода:
Декоратор класса на примере Singleton
Декоратор можно использовать для декорирования класса. Отличие лишь в том, что декоратор получает класс, а не функцию.
Singleton — это класс с одним экземпляром. Его можно сохранить как атрибут функции-обертки и вернуть при запросе.
Встроенные декораторы
Python: Decorators in OOP
A guide on classmethods, staticmethods and the property decorator
The Object Oriented Programming paradigm became popular in the ’60s and ‘70s, in languages like Lisp and Smalltalk. Such features were also added to existing languages like Ada, Fortran and Pascal.
Python is an object oriented programming language, though it doesn’t support strong encapsulation.
Introductory topics in object-oriented programming in Python — and more generally — include things like defining classes, creating objects, instance variables, the basics of inheritance, and maybe even some special methods like __str__ . But when we have an advanced look, we could talk about things like the use of decorators, or writing a custom new method, metaclasses, and Multiple Inheritance.
In this post, we’ll first discuss what decorators are, followed by a discussion on classmethods and staticmethods along with the property decorator.
Classmethods, staticmethods and property are examples of what are called descriptors. These are objects which implement the __get__ , __set__ or __delete__ methods.
But, that’s a topic for another post.
Table of Contents
We’ll talk about the following in this article:
- what are decorators?
- classmethods
- staticmethods
- @property
An example
Let’s work on a simple example: a Student class.
For now, this class has two variables:
We’ll add a simple __init__ method to instantiate an object when these two attributes are provided.
Our percent function can be defined like so:
- takes our two arguments score and total
- calls the function object passed to the grade_decorator
- then calculates the grade that corresponding to the percent scored.
- Finally, it returns the calculated percentage along with the grade.
We can implement our decorator like so.
classmethod
Let’s first talk about instance methods. Instance methods are those methods that are called by an object, and hence are passed information about that object. This is done through the self argument as a convention, and when that method is called, the object’s information is passed implicitly through self .
For example, we could add a method to our class that calculates a student’s grade and percentage (using the get_percent method) and generates a report as a string with the student’s name, percentage, and grade.
The getter and setter methods
Let’s start with getter and setter methods. These methods are used to access and modify (respectively) a private instance. In Java, we would do something like this:
Now, anytime you access or modify this value, you would use these methods. Since the variable x is private, it can’t be accessed outside JavaClass .
In python, there is no private keyword. We prepend a variable by a dunder( __ ) to show that it is private and shouldn’t be accessed or modified directly.
Adding a __ before a variable name modifies that variable’s name from varname to _Classname__varname , so direct access and modification like print(obj.varname) and obj.varname = 5 won’t work. Still, this isn’t very strong since you could directly replace varname with the modified form to get a direct modification to work.
Let’s take the following example to understand this:
Adding getter and setter methods
Taking our Student class example, let’s make the score attribute “private” by adding a __ before the variable name.
If we directly went ahead and added get_score and set_score like Java, the main issue is that if we wanted to do this to existing code, we’d have to change every access from:
print("Score: " + str(student1.score))
student1.score = 100
print(student1.get_score())
student1.set_score(100)
Here’s where the @property decorator comes in. You can simply define getter , setter and deleter methods using this feature.
Our class now looks like this:
To make the attribute score read-only, just remove the setter method.
Then, when we update score , we get the following error:
Traceback (most recent call last):
File "main.py", line 16, in
student.score = 10
AttributeError: can't set attribute
The deleter method
The deleter method lets you delete a protected or private attribute using the del function. Using the same example as before, if we directly try and delete the score attribute, we get the following error:
student = Student("Tom", 50, 100)
del student.scoreThis gives:
Traceback (most recent call last):
File "", line 17, in
AttributeError: can't delete attribute
But when we add a deleter method, we can delete our private variable score .
The attribute has been successfully removed now. Printing out the value of score gives “object has no attribute…”, since we deleted it.
Traceback (most recent call last):
File "", line 23, in
File "", line 9, in x
AttributeError: 'PythonClass' object has no attribute '__score'
use-cases for the property decorator
The property decorator is very useful when defining methods for data validation, like when deciding if a value to be assigned is valid and won’t lead to issues later in the code.
Another use-case would be when wanting to display information in a specific way. Coming back to our example, if we wanted to display a student’s name as “Student Name: ” , instead of just , we could return the first string from a property getter on the name attribute:
Now, any time we access name , we get a formatted result.
student = Student("Bob", 350, 500)
print(student.name)
The property decorator can also be used for logging changes.
For example, in a setter method, you could add code to log the updating of a variable.
Now, whenever the setter is called, which is when the variable is modified, the change is logged. Let’s say there was a totaling error in Bob’s math exam and he ends up getting 50 more marks.
student = Student("Bob", 350, 500)
print(student.score)
student.score = 400
print(student.score)
The above gives the following output, with the logged change visible:
70.0 %
INFO:root:Setting new value.
80.0 %
Finally, our class looks like this:
Note#1: Where should you define a decorator wrt a class?
There are many places you could define a decorator: outside the class, in a separate class, or maybe even in an inner class (with respect to the class we are using the decorator in). In this example, we simply defined grade_decorator outside the Student class. Though this works, the decorator now has nothing to do with our class, which we may not prefer.
For a more detailed discussion on this, check out this post:
Decorator inside Python class
Let’s suppose we need a decorator that we gonna use inside one specific class only. Something like this:
Note#2: Are there options other than constructor overloading in Java to simulate the methods we discussed (like, from_str or from_tuple)?
Apart from overloading the constructor, we could make use of static factory methods in java. We could define a static method like from_str that would extract key information from the string passed to it and then return an object.
Conclusion
Object-oriented programming is a very important paradigm to learn and use. Regardless of whether you’ll ever need to use the topics discussed here in your next project, it’s necessary to know the basics really well. Topics like the ones in this post aren’t used all that often compared to more basic concepts — like inheritance or the basic implementation of classes and objects — on which they are built. In any case, I hope this post gave you an idea of the other kinds of methods in Python OOP (apart from instance methods) and the property decorator.