КАК СОЗДАТЬ ИНИЦИАЛИЗАТОР КЛАССА SOMECLASS PYTHON
Инициализатор класса в Python — это метод, который автоматически вызывается при создании нового экземпляра класса. Чтобы создать инициализатор класса, нужно определить метод __init__().
Пример создания инициализатора класса someclass:
class Someclass: def __init__(self, arg1, arg2): self.arg1 = arg1 self.arg2 = arg2
В приведенном примере инициализатор класса someclass принимает два аргумента и сохраняет их в атрибутах arg1 и arg2. Эти атрибуты могут быть использованы в других методах класса.

#5. Методы класса (classmethod) и статические методы (staticmethod) — ООП Python
#2. Методы классов. Параметр self — Объектно-ориентированное программирование Python
#12. Магический метод __call__. Функторы и классы-декораторы — ООП Python
#37. Введение в Python Data Classes (часть 1) — Объектно-ориентированное программирование Python
#3. Инициализатор __init__ и финализатор __del__ — Объектно-ориентированное программирование Python
Python Class Constructors: Control Your Object Instantiation
Class constructors are a fundamental part of object-oriented programming in Python. They allow you to create and properly initialize objects of a given class, making those objects ready to use. Class constructors internally trigger Python’s instantiation process, which runs through two main steps: instance creation and instance initialization.
If you want to dive deeper into how Python internally constructs objects and learn how to customize the process, then this tutorial is for you.
In this tutorial, you’ll:
- Understand Python’s internal instantiation process
- Customize object initialization using .__init__()
- Fine-tune object creation by overriding .__new__()
With this knowledge, you’ll be able to tweak the creation and initialization of objects in your custom Python classes, which will give you control over the instantiation process at a more advanced level.
To better understand the examples and concepts in this tutorial, you should be familiar with object-oriented programming and special methods in Python.
Free Bonus: Click here to get access to a free Python OOP Cheat Sheet that points you to the best tutorials, videos, and books to learn more about Object-Oriented Programming with Python.
Python’s Class Constructors and the Instantiation Process
Like many other programming languages, Python supports object-oriented programming. At the heart of Python’s object-oriented capabilities, you’ll find the class keyword, which allows you to define custom classes that can have attributes for storing data and methods for providing behaviors.
Once you have a class to work with, then you can start creating new instances or objects of that class, which is an efficient way to reuse functionality in your code.
Creating and initializing objects of a given class is a fundamental step in object-oriented programming. This step is often referred to as object construction or instantiation. The tool responsible for running this instantiation process is commonly known as a class constructor.
Getting to Know Python’s Class Constructors
In Python, to construct an object of a given class, you just need to call the class with appropriate arguments, as you would call any function:
In this example, you define SomeClass using the class keyword. This class is currently empty because it doesn’t have attributes or methods. Instead, the class’s body only contains a pass statement as a placeholder statement that does nothing.
Then you create a new instance of SomeClass by calling the class with a pair of parentheses. In this example, you don’t need to pass any argument in the call because your class doesn’t take arguments yet.
In Python, when you call a class as you did in the above example, you’re calling the class constructor, which creates, initializes, and returns a new object by triggering Python’s internal instantiation process.
A final point to note is that calling a class isn’t the same as calling an instance of a class. These are two different and unrelated topics. To make a class’s instance callable, you need to implement a .__call__() special method, which has nothing to do with Python’s instantiation process.
Understanding Python’s Instantiation Process
You trigger Python’s instantiation process whenever you call a Python class to create a new instance. This process runs through two separate steps, which you can describe as follows:
- Create a new instance of the target class
- Initialize the new instance with an appropriate initial state
To run the first step, Python classes have a special method called .__new__() , which is responsible for creating and returning a new empty object. Then another special method, .__init__() , takes the resulting object, along with the class constructor’s arguments.
The .__init__() method takes the new object as its first argument, self . Then it sets any required instance attribute to a valid state using the arguments that the class constructor passed to it.
In short, Python’s instantiation process starts with a call to the class constructor, which triggers the instance creator, .__new__() , to create a new empty object. The process continues with the instance initializer, .__init__() , which takes the constructor’s arguments to initialize the newly created object.
To explore how Python’s instantiation process works internally, consider the following example of a Point class that implements a custom version of both methods, .__new__() and .__init__() , for demonstration purposes:
Here’s a breakdown of what this code does:
Line 3 defines the Point class using the class keyword followed by the class name.
Line 4 defines the .__new__() method, which takes the class as its first argument. Note that using cls as the name of this argument is a strong convention in Python, just like using self to name the current instance is. The method also takes *args and **kwargs , which allow for passing an undefined number of initialization arguments to the underlying instance.
Line 5 prints a message when .__new__() runs the object creation step.
Line 6 creates a new Point instance by calling the parent class’s .__new__() method with cls as an argument. In this example, object is the parent class, and the call to super() gives you access to it. Then the instance is returned. This instance will be the first argument to .__init__() .
Line 8 defines .__init__() , which is responsible for the initialization step. This method takes a first argument called self , which holds a reference to the current instance. The method also takes two additional arguments, x and y . These arguments hold initial values for the instance attributes .x and .y . You need to pass suitable values for these arguments in the call to Point() , as you’ll learn in a moment.
Line 9 prints a message when .__init__() runs the object initialization step.
Lines 10 and 11 initialize .x and .y , respectively. To do this, they use the provided input arguments x and y .
Lines 13 and 14 implement the .__repr__() special method, which provides a proper string representation for your Point class.
With Point in place, you can uncover how the instantiation process works in practice. Save your code to a file called point.py and start your Python interpreter in a command-line window. Then run the following code:
Calling the Point() class constructor creates, initializes, and returns a new instance of the class. This instance is then assigned to the point variable.
In this example, the call to the constructor also lets you know the steps that Python internally runs to construct the instance. First, Python calls .__new__() and then .__init__() , resulting in a new and fully initialized instance of Point , as you confirmed at the end of the example.
To continue learning about class instantiation in Python, you can try running both steps manually:
In this example, you first call .__new__() on your Point class, passing the class itself as the first argument to the method. This call only runs the first step of the instantiation process, creating a new and empty object. Note that creating an instance this way bypasses the call to .__init__() .
Note: The code snippet above is intended to be a demonstrative example of how the instantiation process works internally. It’s not something that you would typically do in real code.
Once you have the new object, then you can initialize it by calling .__init__() with an appropriate set of arguments. After this call, your Point object is properly initialized, with all its attributes set up.
A subtle and important detail to note about .__new__() is that it can also return an instance of a class different from the class that implements the method itself. When that happens, Python doesn’t call .__init__() in the current class, because there’s no way to unambiguously know how to initialize an object of a different class.
Consider the following example, in which the .__new__() method of the B class returns an instance of the A class:
Because B.__new__() returns an instance of a different class, Python doesn’t run B.__init__() . To confirm this behavior, save the code into a file called ab_classes.py and then run the following code in an interactive Python session:
The call to the B() class constructor runs B.__new__() , which returns an instance of A instead of B . That’s why B.__init__() never runs. Note that b doesn’t have a .b_value attribute. In contrast, b does have an .a_value attribute with a value of 42 .
Now that you know the steps that Python internally takes to create instances of a given class, you’re ready to dig a little deeper into other characteristics of .__init__() , .__new__() , and the steps that they run.
Object Initialization With .__init__()
In Python, the .__init__() method is probably the most common special method that you’ll ever override in your custom classes. Almost all your classes will need a custom implementation of .__init__() . Overriding this method will allow you to initialize your objects properly.
The purpose of this initialization step is to leave your new objects in a valid state so that you can start using them right away in your code. In this section, you’ll learn the basics of writing your own .__init__() methods and how they can help you customize your classes.
Providing Custom Object Initializers
The most bare-bones implementation of .__init__() that you can write will just take care of assigning input arguments to matching instance attributes. For example, say you’re writing a Rectangle class that requires .width and .height attributes. In that case, you can do something like this:
As you learned before, .__init__() runs the second step of the object instantiation process in Python. Its first argument, self , holds the new instance that results from calling .__new__() . The rest of the arguments to .__init__() are normally used to initialize instance attributes. In the above example, you initialized the rectangle’s .width and .height using the width and height arguments to .__init__() .
It’s important to note that, without counting self , the arguments to .__init__() are the same ones that you passed in the call to the class constructor. So, in a way, the .__init__() signature defines the signature of the class constructor.
Additionally, keep in mind that .__init__() must not explicitly return anything different from None , or you’ll get a TypeError exception:
In this example, the .__init__() method attempts to return an integer number, which ends up raising a TypeError exception at run time.
The error message in the above example says that .__init__() should return None . However, you don’t need to return None explicitly, because methods and functions without an explicit return statement just return None implicitly in Python.
With the above implementation of .__init__() , you ensure that .width and .height get initialized to a valid state when you call the class constructor with appropriate arguments. That way, your rectangles will be ready for use right after the construction process finishes.
In .__init__() , you can also run any transformation over the input arguments to properly initialize the instance attributes. For example, if your users will use Rectangle directly, then you might want to validate the supplied width and height and make sure that they’re correct before initializing the corresponding attributes:
In this updated implementation of .__init__() , you make sure that the input width and height arguments are positive numbers before initializing the corresponding .width and .height attributes. If either validation fails, then you get a ValueError .
Note: A more Pythonic technique to tackle attribute validation is to turn attributes into properties. To learn more about properties, check out Python’s property(): Add Managed Attributes to Your Classes.
Now say that you’re using inheritance to create a custom class hierarchy and reuse some functionality in your code. If your subclasses provide a .__init__() method, then this method must explicitly call the base class’s .__init__() method with appropriate arguments to ensure the correct initialization of instances. To do this, you should use the built-in super() function like in the following example:
The first line in the .__init__() method of Employee calls super().__init__() with name and birth_date as arguments. This call ensures the initialization of .name and .birth_date in the parent class, Person . This technique allows you to extend the base class with new attributes and functionality.
To wrap up this section, you should know that the base implementation of .__init__() comes from the built-in object class. This implementation is automatically called when you don’t provide an explicit .__init__() method in your classes.
Building Flexible Object Initializers
You can make your objects’ initialization step flexible and versatile by tweaking the .__init__() method. To this end, one of the most popular techniques is to use optional arguments. This technique allows you to write classes in which the constructor accepts different sets of input arguments at instantiation time. Which arguments to use at a given time will depend on your specific needs and context.
As a quick example, check out the following Greeter class:
In this example, .__init__() takes a regular argument called name . It also takes an optional argument called formal , which defaults to False . Because formal has a default value, you can construct objects by relying on this value or by providing your own.
The class’s final behavior will depend on the value of formal . If this argument is False , then you’ll get an informal greeting when you call .greet() . Otherwise, you’ll get a more formal greeting.
To try Greeter out, go ahead and save the code into a greet.py file. Then open an interactive session in your working directory and run the following code:
In the first example, you create an informal_greeter object by passing a value to the name argument and relying on the default value of formal . You get an informal greeting on your screen when you call .greet() on the informal_greeter object.
In the second example, you use a name and a formal argument to instantiate Greeter . Because formal is True , the result of calling .greet() is a formal greeting.
Even though this is a toy example, it showcases how default argument values are a powerful Python feature that you can use to write flexible initializers for your classes. These initializers will allow you to instantiate your classes using different sets of arguments depending on your needs.
Okay! Now that you know the basics of .__init__() and the object initialization step, it’s time to change gears and start diving deeper into .__new__() and the object creation step.
Object Creation With .__new__()
When writing Python classes, you typically don’t need to provide your own implementation of the .__new__() special method. Most of the time, the base implementation from the built-in object class is sufficient to build an empty object of your current class.
However, there are a few interesting use cases for this method. For example, you can use .__new__() to create subclasses of immutable types, such as int , float , tuple , and str .
In the following sections, you’ll learn how to write custom implementations of .__new__() in your classes. To do this, you’ll code a few examples that’ll give you an idea of when you might need to override this method.
Providing Custom Object Creators
Typically, you’ll write a custom implementation of .__new__() only when you need to control the creation of a new instance at a low level. Now, if you need a custom implementation of this method, then you should follow a few steps:
- Create a new instance by calling super().__new__() with appropriate arguments.
- Customize the new instance according to your specific needs.
- Return the new instance to continue the instantiation process.
With these three succinct steps, you’ll be able to customize the instance creation step in the Python instantiation process. Here’s an example of how you can translate these steps into Python code:
This example provides a sort of template implementation of .__new__() . As usual, .__new__() takes the current class as an argument that’s typically called cls .
Note that you’re using *args and **kwargs to make the method more flexible and maintainable by accepting any number of arguments. You should always define .__new__() with *args and **kwargs , unless you have a good reason to follow a different pattern.
In the first line of .__new__() , you call the parent class’s .__new__() method to create a new instance and allocate memory for it. To access the parent class’s .__new__() method, you use the super() function. This chain of calls takes you up to object.__new__() , which is the base implementation of .__new__() for all Python classes.
Note: The built-in object class is the default base class of all Python classes.
The next step is to customize your newly created instance. You can do whatever you need to do to customize the instance at hand. Finally, in the third step, you need to return the new instance to continue the instantiation process with the initialization step.
It’s important to note that object.__new__() itself only accepts a single argument, the class to instantiate. If you call object.__new__() with more arguments, then you get a TypeError :
In this example, you hand over *args and **kwargs as additional arguments in the call to super().__new__() . The underlying object.__new__() accepts only the class as an argument, so you get a TypeError when you instantiate the class.
However, object.__new__() still accepts and passes over extra arguments to .__init__() if your class doesn’t override .__new__() , as in the following variation of SomeClass :
In this implementation of SomeClass , you don’t override .__new__() . The object creation is then delegated to object.__new__() , which now accepts value and passes it over to SomeClass.__init__() to finalize the instantiation. Now you can create new and fully initialized instances of SomeClass , just like some_obj in the example.
Cool! Now that you know the basics of writing your own implementations of .__new__() , you’re ready to dive into a few practical examples that feature some of the most common use cases of this method in Python programming.
Subclassing Immutable Built-in Types
To kick things off, you’ll start with a use case of .__new__() that consists of subclassing an immutable built-in type. As an example, say you need to write a Distance class as a subclass of Python’s float type. Your class will have an additional attribute to store the unit that’s used to measure the distance.
Here’s a first approach to this problem, using the .__init__() method:
When you subclass an immutable built-in data type, you get an error. Part of the problem is that the value is set during creation, and it’s too late to change it during initialization. Additionally, float.__new__() is called under the hood, and it doesn’t deal with extra arguments in the same way as object.__new__() . This is what raises the error in your example.
To work around this issue, you can initialize the object at creation time with .__new__() instead of overriding .__init__() . Here’s how you can do this in practice:
In this example, .__new__() runs the three steps that you learned in the previous section. First, the method creates a new instance of the current class, cls , by calling super().__new__() . This time, the call rolls back to float.__new__() , which creates a new instance and initializes it using value as an argument. Then the method customizes the new instance by adding a .unit attribute to it. Finally, the new instance gets returned.
Note: The Distance class in the example above doesn’t provide a proper unit conversion mechanism. This means that something like Distance(10, «km») + Distance(20, «miles») won’t attempt at converting units before adding the values. If you’re interested in converting units, then check out the Pint project on PyPI.
That’s it! Now your Distance class works as expected, allowing you to use an instance attribute for storing the unit in which you’re measuring the distance. Unlike the floating-point value stored in a given instance of Distance , the .unit attribute is mutable, so you can change its value any time you like. Finally, note how a call to the dir() function reveals that your class inherits features and methods from float .
Returning Instances of a Different Class
Returning an object of a different class is a requirement that can raise the need for a custom implementation of .__new__() . However, you should be careful because in this case, Python skips the initialization step entirely. So, you’ll have the responsibility of taking the newly created object into a valid state before using it in your code.
Check out the following example, in which the Pet class uses .__new__() to return instances of randomly selected classes:
In this example, Pet provides a .__new__() method that creates a new instance by randomly selecting a class from a list of existing classes.
Here’s how you can use this Pet class as a factory of pet objects:
Every time you instantiate Pet , you get a random object from a different class. This result is possible because there’s no restriction on the object that .__new__() can return. Using .__new__() in such a way transforms a class into a flexible and powerful factory of objects, not limited to instances of itself.
Finally, note how the .__init__() method of Pet never runs. That’s because Pet.__new__() always returns objects of a different class rather than of Pet itself.
Allowing Only a Single Instance in Your Classes
Sometimes you need to implement a class that allows the creation of a single instance only. This type of class is commonly known as a singleton class. In this situation, the .__new__() method comes in handy because it can help you restrict the number of instances that a given class can have.
Note: Most experienced Python developers would argue that you don’t need to implement the singleton design pattern in Python unless you already have a working class and need to add the pattern’s functionality on top of it.
The rest of the time, you can use a module-level constant to get the same singleton functionality without having to write a relatively complex class.
Here’s an example of coding a Singleton class with a .__new__() method that allows the creation of only one instance at a time. To do this, .__new__() checks the existence of previous instances cached on a class attribute:
The Singleton class in this example has a class attribute called ._instance that defaults to None and works as a cache. The .__new__() method checks if no previous instance exists by testing the condition cls._instance is None .
Note: In the example above, Singleton doesn’t provide an implementation of .__init__() . If you ever need a class like this with a .__init__() method, then keep in mind that this method will run every time you call the Singleton() constructor. This behavior can cause weird initialization effects and bugs.
If this condition is true, then the if code block creates a new instance of Singleton and stores it to cls._instance . Finally, the method returns the new or the existing instance to the caller.
Then you instantiate Singleton twice to try to construct two different objects, first and second . If you compare the identity of these objects with the is operator, then you’ll note that both objects are the same object. The names first and second just hold references to the same Singleton object.
Partially Emulating collections.namedtuple
As a final example of how to take advantage of .__new__() in your code, you can push your Python skills and write a factory function that partially emulates collections.namedtuple() . The namedtuple() function allows you to create subclasses of tuple with the additional feature of having named fields for accessing the items in the tuple.
The code below implements a named_tuple_factory() function that partially emulates this functionality by overriding the .__new__() method of a nested class called NamedTuple :
Here’s how this factory function works line by line:
Line 3 imports itemgetter() from the operator module. This function allows you to retrieve items using their index in the containing sequence.
Line 5 defines named_tuple_factory() . This function takes a first argument called type_name , which will hold the name of the tuple subclass that you want to create. The *fields argument allows you to pass an undefined number of field names as strings.
Line 6 defines a local variable to hold the number of named fields provided by the user.
Line 8 defines a nested class called NamedTuple , which inherits from the built-in tuple class.
Line 9 provides a .__slots__ class attribute. This attribute defines a tuple for holding instance attributes. This tuple saves memory by acting as a substitute for the instance’s dictionary, .__dict__ , which would otherwise play a similar role.
Line 11 implements .__new__() with cls as its first argument. This implementation also takes the *args argument to accept an undefined number of field values.
Lines 12 to 16 define a conditional statement that checks if the number of items to store in the final tuple differs from the number of named fields. If that’s the case, then the conditional raises a TypeError with an error message.
Line 17 sets the .__name__ attribute of the current class to the value provided by type_name .
Lines 18 and 19 define a for loop that turns every named field into a property that uses itemgetter() to return the item at the target index . The loop uses the built-in setattr() function to perform this action. Note that the built-in enumerate() function provides the appropriate index value.
Line 20 returns a new instance of the current class by calling super().__new__() as usual.
Lines 22 and 23 define a .__repr__() method for your tuple subclass.
Line 25 returns the newly created NamedTuple class.
To try your named_tuple_factory() out, fire up an interactive session in the directory containing the named_tuple.py file and run the following code:
In this code snippet, you create a new Point class by calling named_tuple_factory() . The first argument in this call represents the name that the resulting class object will use. The second and third arguments are the named fields available in the resulting class.
Then you create a Point object by calling the class constructor with appropriate values for the .x and .y fields. To access the value of each named field, you can use the dot notation. You can also use indices to retrieve the values because your class is a tuple subclass.
Because tuples are immutable data types in Python, you can’t assign new values to the point’s coordinates in place. If you try to do that, then you get an AttributeError .
Finally, calling dir() with your point instance as an argument reveals that your object inherits all the attributes and methods that regular tuples have in Python.
Conclusion
Now you know how Python class constructors allow you to instantiate classes, so you can create concrete and ready-to-use objects in your code. In Python, class constructors internally trigger the instantiation or construction process, which goes through instance creation and instance initialization. These steps are driven by the .__new__() and .__init__() special methods.
By learning about Python’s class constructors, the instantiation process, and the .__new__() and .__init__() methods, you can now manage how your custom classes construct new instances.
In this tutorial, you learned:
- How Python’s instantiation process works internally
- How your own .__init__() methods help you customize object initialization
- How overriding the .__new__() method allows for custom object creation
Now you’re ready to take advantage of this knowledge to fine-tune your class constructors and take full control over instance creation and initialization in your object-oriented programming adventure with Python.
Free Bonus: Click here to get access to a free Python OOP Cheat Sheet that points you to the best tutorials, videos, and books to learn more about Object-Oriented Programming with Python.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Using Python Class Constructors
Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

About Leodanis Pozo Ramos
Leodanis is an industrial engineer who loves Python and software development. He's a self-taught Python developer with 6+ years of experience. He's an avid technical writer with a growing number of articles published on Real Python and other sites.
Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:





Master Real-World Python Skills With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
Master Real-World Python Skills
With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
What Do You Think?
What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.
Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal. Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!
Руководство по магическим методам в Питоне
Это перевод 1.17 версии руководства от Rafe Kettler.
Содержание
Вступление
Что такое магические методы? Они всё в объектно-ориентированном Питоне. Это специальные методы, с помощью которых вы можете добавить в ваши классы «магию». Они всегда обрамлены двумя нижними подчеркиваниями (например, __init__ или __lt__ ). Ещё, они не так хорошо документированны, как хотелось бы. Все магические методы описаны в документации, но весьма беспорядочно и почти безо всякой организации. Поэтому, чтобы исправить то, что я воспринимаю как недостаток документации Питона, я собираюсь предоставить больше информации о магических методах, написанной на понятном языке и обильно снабжённой примерами. Надеюсь, это руководство вам понравится. Используйте его как обучающий материал, памятку или полное описание. Я просто постарался как можно понятнее описать магические методы.
Конструирование и инициализация.
Всем известен самый базовый магический метод, __init__ . С его помощью мы можем инициализировать объект. Однако, когда я пишу x = SomeClass() , __init__ не самое первое, что вызывается. На самом деле, экземпляр объекта создаёт метод __new__ , а затем аргументы передаются в инициализатор. На другом конце жизненного цикла объекта находится метод __del__ . Давайте подробнее рассмотрим эти три магических метода:
-
__new__(cls, [. )
Это первый метод, который будет вызван при инициализации объекта. Он принимает в качестве параметров класс и потом любые другие аргументы, которые будут переданы в __init__ . __new__ используется весьма редко, но иногда бывает полезен, в частности, когда класс наследуется от неизменяемого (immutable) типа, такого как кортеж (tuple) или строка. Я не намерен очень детально останавливаться на __new__ , так как он не то чтобы очень часто нужен, но этот метод очень хорошо и детально описан в документации.
Замечание от переводчика: svetlov отмечает, что здесь автор ошибается, на самом деле __del__ всегда вызывается по завершении работы интерпретатора.
Переопределение операторов на произвольных классах
Одно из больших преимуществ использования магических методов в Питоне то, что они предоставляют простой способ заставить объекты вести себя по подобию встроенных типов. Это означает, что вы можете избежать унылого, нелогичного и нестандартного поведения базовых операторов. В некоторых языках обычное явление писать как-нибудь так:
Вы, конечно, можете поступать так же и в Питоне, но это добавляет путаницы и ненужной многословности. Разные библиотеки могут по разному называть одни и те же операции, заставляя использующего их программиста совершать больше действий, чем необходимо. Используя силу магических методов, мы можем определить нужный метод ( __eq__ , в этом случае), и так точно выразить, что мы имели в виду:
Это одна из сильных сторон магических методов. Подавляющее большинство из них позволяют определить, что будут делать стандартные операторы, так что мы можем использовать операторы на своих классах так, как будто они встроенные типы.
Магические методы сравнения
В Питоне уйма магических методов, созданных для определения интуитивного сравнения между объектами используя операторы, а не неуклюжие методы. Кроме того, они предоставляют способ переопределить поведение Питона по-умолчанию для сравнения объектов (по ссылке). Вот список этих методов и что они делают:
-
__cmp__(self, other)
Самый базовый из методов сравнения. Он, в действительности, определяет поведение для всех операторов сравнения (>, ==, !=, итд.), но не всегда так, как вам это нужно (например, если эквивалентность двух экземпляров определяется по одному критерию, а то что один больше другого по какому-нибудь другому). __cmp__ должен вернуть отрицательное число, если self < other , ноль, если self == other , и положительное число в случае self > other . Но, обычно, лучше определить каждое сравнение, которое вам нужно, чем определять их всех в __cmp__ . Но __cmp__ может быть хорошим способом избежать повторений и увеличить ясность, когда все необходимые сравнения оперерируют одним критерием.
Теперь мы можем создать два Word (при помощи Word(‘foo’) и Word(‘bar’) ) и сравнить их по длине. Заметьте, что мы не определяли __eq__ и __ne__ , так как это приведёт к странному поведению (например, Word(‘foo’) == Word(‘bar’) будет расцениваться как истина). В этом нет смысла при тестировании на эквивалентность, основанную на длине, поэтому мы оставляем стандартную проверку на эквивалентность от str .
Сейчас, кажется, удачное время упомянуть, что вы не должны определять каждый из магических методов сравнения, чтобы полностью охватить все сравнения. Стандартная библиотека любезно предоставляет нам класс-декторатор в модуле functools , который и определит все сравнивающие методы, от вас достаточно определить только __eq__ и ещё один ( __gt__ , __lt__ и т.п.) Эта возможность доступна начиная с 2.7 версии Питона, но если это вас устраивает, вы сэкономите кучу времени и усилий. Для того, чтобы задействовать её, поместите @total_ordering над вашим определением класса.
Числовые магические методы
Точно так же, как вы можете определить, каким образом ваши объекты будут сравниваться операторами сравнения, вы можете определить их поведение для числовых операторов. Приготовтесь, друзья, их много. Для лучшей организации, я разбил числовые магические методы на 5 категорий: унарные операторы, обычные арифметические операторы, отражённые арифметические операторы (подробности позже), составные присваивания и преобразования типов.
Унарные операторы и функции
Унарные операторы и функции имеют только один операнд — отрицание, абсолютное значение, и так далее.
-
__pos__(self)
Определяет поведение для унарного плюса ( +some_object )
Обычные арифметические операторы
Теперь рассмотрим обычные бинарные операторы (и ещё пару функций): +, -, * и похожие. Они, по большей части, отлично сами себя описывают.
-
__add__(self, other)
Сложение.
Отражённые арифметические операторы
Помните как я сказал, что собираюсь остановиться на отражённой арифметике подробнее? Вы могли подумать, что это какая-то большая, страшная и непонятная концепция. На самом деле всё очень просто. Вот пример:
Это «обычное» сложение. Единственное, чем отличается эквивалентное отражённое выражение, это порядок слагаемых:
Таким образом, все эти магические методы делают то же самое, что и их обычные версии, за исключением выполнения операции с other в качестве первого операнда и self в качестве второго. В большинстве случаев, результат отражённой операции такой же, как её обычный эквивалент, поэтому при определении __radd__ вы можете ограничиться вызовом __add__ да и всё. Заметьте, что объект слева от оператора ( other в примере) не должен иметь обычной неотражённой версии этого метода. В нашем примере, some_object.__radd__ будет вызван только если в other не определён __add__ .
-
__radd__(self, other)
Отражённое сложение.
Составное присваивание
В Питоне широко представлены и магические методы для составного присваивания. Вы скорее всего уже знакомы с составным присваиванием, это комбинация «обычного» оператора и присваивания. Если всё ещё непонятно, вот пример:
Каждый из этих методов должен возвращать значение, которое будет присвоено переменной слева (например, для a += b , __iadd__ должен вернуть a + b , что будет присвоено a ). Вот список:
-
__iadd__(self, other)
Сложение с присваиванием.
Магические методы преобразования типов
Кроме того, в Питоне множество магических методов, предназначенных для определния поведения для встроенных функций преобразования типов, таких как float() . Вот они все:
-
__int__(self)
Преобразование типа в int.
Представление своих классов
Часто бывает полезно представление класса в виде строки. В Питоне существует несколько методов, которые вы можете определить для настройки поведения встроенных функций при представлении вашего класса.
-
__str__(self)
Определяет поведение функции str() , вызванной для экземпляра вашего класса.
Контроль доступа к атрибутам
Многие люди, пришедшие в Питон из других языков, жалуются на отсутствие настоящей инкапсуляции для классов (например, нет способа определить приватные атрибуты с публичными методами доступа). Это не совсем правда: просто многие вещи, связанные с инкапсуляцией, Питон реализует через «магию», а не явными модификаторами для методов и полей. Смотрите:
-
__getattr__(self, name)
Вы можете определить поведение для случая, когда пользователь пытается обратиться к атрибуту, который не существует (совсем или пока ещё). Это может быть полезным для перехвата и перенаправления частых опечаток, предупреждения об использовании устаревших атрибутов (вы можете всё-равно вычислить и вернуть этот атрибут, если хотите), или хитро возвращать AttributeError , когда это вам нужно. Правда, этот метод вызывается только когда пытаются получить доступ к несуществующему атрибуту, поэтому это не очень хорошее решение для инкапсуляции.
Ещё раз, мощь магических методов в Питоне невероятна, а с большой силой приходит и большая ответственность. Важно знать, как правильно использовать магические методы, ничего не ломая.
Итак, что мы узнали об управлении доступом к атрибутам? Их не нужно использовать легкомысленно. На самом деле, они имеют склонность к чрезмерной мощи и нелогичности. Причина, по которой они всё-таки существуют, в удволетворении определённого желания: Питон склонен не запрещать плохие штуки полностью, а только усложнять их использование. Свобода первостепенна, поэтому вы на самом деле можете делать всё, что хотите. Вот пример использования методов контроля доступа (заметьте, что мы используем super , так как не все классы имеют атрибут __dict__ ):
Создание произвольных последовательностей
В Питоне существует множество способов заставить ваши классы вести себя как встроенные последовательности (словари, кортежи, списки, строки и так далее). Это, безусловно, мои любимые магические методы, из-за до абсурда высокой степени контроля, которую они дают и той магии, от которой с экземплярами ваших классов вдруг начинает прекрасно работать целое множество глобальных функций. Но, до того как мы перейдём ко всяким хорошим вещам, мы должны знать о протоколах.
Протоколы
Теперь, когда речь зашла о создании собственных последовательностей в Питоне, пришло время поговорить о протоколах. Протоколы немного похожи на интерфейсы в других языках тем, что они предоставляют набор методов, которые вы должны реализовать. Однако, в Питоне протоколы абсолютно ни к чему не обязывают и не требуют обязательно реализовать какое-либо объявление. Наверное, они больше похожи на руководящие указания.
Почему мы заговорили о протоколах? Потому, что реализация произвольных контейнерных типов в Питоне влечёт за собой использование некоторых из них. Во-первых, протокол для определения неизменяемых контейнеров: чтобы создать неизменяемый контейнер, вы должны только определить __len__ и __getitem__ (продробнее о них дальше). Протокол изменяемого контейнера требует того же, что и неизменяемого контейнера, плюс __setitem__ и __delitem__ . И, наконец, если вы хотите, чтобы ваши объекты можно было перебирать итерацией, вы должны определить __iter__ , который возвращает итератор. Этот итератор должен соответствовать протоколу итератора, который требует методов __iter__ (возвращает самого себя) и next .
Магия контейнеров
Без дальнейшего промедления, вот магические методы, используемые контейнерами:
-
__len__(self)
Возвращает количество элементов в контейнере. Часть протоколов для изменяемого и неизменяемого контейнеров.
Пример
Для примера, давайте посмотрим на список, который реализует некоторые функциональные конструкции, которые вы могли встретить в других языках (Хаскеле, например).
Теперь у вас есть полезный (относительно) пример реализации своей собственной последовательности. Существуют, конечно, и куда более практичные реализации произвольных последовательностей, но большое их число уже реализовано в стандартной библиотеке (с батарейками в комплекте, да?), такие как Counter , OrderedDict , NamedTuple .
Отражение
Вы можете контролировать и отражение, использующее встроенные функции isinstance() и issubclass() , определив некоторые магические методы. Вот они:
-
__instancecheck__(self, instance)
Проверяет, является ли экземлпяр членом вашего класса ( isinstance(instance, class) , например.
Вызываемые объекты
Как вы наверное уже знаете, в Питоне функции являются объектами первого класса. Это означает, что они могут быть переданы в функции или методы так же, как любые другие объекты. Это невероятно мощная особенность.
Специальный магический метод позволяет экземплярам вашего класса вести себя так, как будто они функции, тоесть вы сможете «вызывать» их, передавать их в функции, которые принимают функции в качестве аргументов и так далее. Это другая удобная особенность, которая делает программирование на Питоне таким приятным.
-
__call__(self, [args. ])
Позволяет любому экземпляру вашего класса быть вызванным как-будто он функция. Главным образом это означает, что x() означает то же, что и x.__call__() . Заметьте, __call__ принимает произвольное число аргументов; то есть, вы можете определить __call__ так же как любую другую функцию, принимающую столько аргументов, сколько вам нужно.
Менеджеры контекста
В Питоне 2.5 было представлено новое ключевое слово вместе с новым способом повторно использовать код, ключевое слово with . Концепция менеджеров контекста не являлась новой для Питона (она была реализована раньше как часть библиотеки), но в PEP 343 достигла статуса языковой конструкции. Вы могли уже видеть выражения с with :
Менеджеры контекста позволяют выполнить какие-то действия для настройки или очистки, когда создание объекта обёрнуто в оператор with . Поведение менеджера контекста определяется двумя магическими методами:
-
__enter__(self)
Определяет, что должен сделать менеджер контекста в начале блока, созданного оператором with . Заметьте, что возвращаемое __enter__ значение и есть то значение, с которым производится работа внутри with .
Пример использования Closer с FTP-соединением (сокет, имеющий метод close):
Видите, как наша обёртка изящно управляется и с правильными и с неподходящими объектами. В этом сила менеджеров контекста и магических методов. Заметьте, что стандартная библиотека Питона включает модуль contextlib, который включает в себя contextlib.closing() — менеджер контекста, который делает приблизительно то же (без какой-либо обработки случая, когда объект не имеет метода close() ).
Абстрактные базовые классы
Построение дескрипторов
Дескрипторы это такие классы, с помощью которых можно добавить свою логику к событиям доступа (получение, изменение, удаление) к атрибутам других объектов. Дескрипторы не подразумевается использовать сами по себе; скорее, предполагается, что ими будут владеть какие-нибудь связанные с ними классы. Дескрипторы могут быть полезны для построения объектно-ориентированных баз данных или классов, чьи атрибуты зависят друг от друга. В частности, дескрипторы полезны при представлении атрибутов в нескольких системах исчисления или каких-либо вычисляемых атрибутов (как расстояние от начальной точки до представленной атрибутом точки на сетке).
Чтобы класс стал дескриптором, он должен реализовать по крайней мере один метод из __get__ , __set__ или __delete__ . Давайте рассмотрим эти магические методы:
-
__get__(self, instance, instance_class)
Определяет поведение при возвращении значения из дескриптора. instance это объект, для чьего атрибута-дескриптора вызывается метод. owner это тип (класс) объекта.
Копирование
В Питоне оператор присваивания не копирует объекты, а только добавляет ещё одну ссылку. Но для коллекций, содержащих изменяемые элементы, иногда необходимо полноценное копирование, чтобы можно было менять элементы одной последовательности, не затрагивая другую. Здесь в игру и вступает copy . К счастью, модули в Питоне не обладают разумом, поэтому мы можем не беспокоиться что они вдруг начнут бесконтрольно копировать сами себя и вскоре линуксовые роботы заполонят всю планету, но мы должны сказать Питону как правильно копировать.
-
__copy__(self)
Определяет поведение copy.copy() для экземпляра вашего класса. copy.copy() возвращает поверхностную копию вашего объекта — это означает, что хоть сам объект и создан заново, все его данные ссылаются на данные оригинального объекта. И при изменении данных нового объекта, изменения будут происходить и в оригинальном.
Использование модуля pickle на своих объектах
Pickle это модуль для сериализации структур данных Питона и он может быть невероятно полезен, когда вам нужно сохранить состояние какого-либо объекта и восстановить его позже (обычно, в целях кэширования). Кроме того, это ещё и отличный источник переживаний и путаницы.
Сериализация настолько важна, что кроме своего модуля ( pickle ) имеет и свой собственный протокол и свои магические методы. Но для начала о том, как сериализовать с помощью pickle уже существующие типы данных (спокойно пропускайте, если вы уже знаете).
Вкратце про сериализацию
Давайте погрузимся в сериализацию. Допустим, у вас есть словарь, который вы хотите сохранить и восстановить позже. Вы должны записать его содержимое в файл, тщательно убедившись, что пишете с правильным синтаксисом, потом восстановить его, или выполнив exec() , или прочитав файл. Но это в лучшем случае рискованно: если вы храните важные данные в тексте, он может быть повреждён или изменён множеством способов, с целью обрушить вашу программу или, вообще, запустить какой-нибудь опасный код на вашем компьютере. Лучше использовать pickle:
И вот, спустя несколько часов, нам снова нужен наш словарь:
Что произошло? Точно то, что и ожидалось. data как-будто всегда тут и была.
Теперь, немного об осторожности: pickle не идеален. Его файлы легко испортить случайно или преднамеренно. Pickle, может быть, безопаснее чем текстовые файлы, но он всё ещё может использоваться для запуска вредоносного кода. Кроме того, он несовместим между разными версиями Питона, поэтому если вы будете распространять свои объекты с помощью pickle, не ожидайте что все люди смогут их использовать. Тем не менее, модуль может быть мощным инструментом для кэширования и других распространённых задач с сериализацией.
Сериализация собственных объектов.
Модуль pickle не только для встроенных типов. Он может использоваться с каждым классом, реализующим его протокол. Этот протокол содержит четыре необязательных метода, позволяющих настроить то, как pickle будет с ними обращаться (есть некоторые различия для расширений на C, но это за рамками нашего руководства):
-
__getinitargs__(self)
Если вы хотите, чтобы после десериализации вашего класса был вызыван __init__ , вы можете определить __getinitargs__ , который должен вернуть кортеж аргументов, который будет отправлен в __init__ . Заметьте, что этот метод работает только с классами старого стиля.
Пример
Для примера опишем грифельную доску ( Slate ), которая запоминает что и когда было на ней записано. Впрочем, конкретно эта доска становится чистой каждый раз, когда она сериализуется: текущее значение не сохраняется.
Заключение
Цель этого руководства донести что-нибудь до каждого, кто его читает, независимо от его опыта в Питоне или объектно-ориентированном программировании. Если вы новичок в Питоне, вы получили ценные знания об основах написания функциональных, элегантных и простых для использования классов. Если вы программист среднего уровня, то вы, возможно, нашли несколько новых приятных идей и стратегий, несколько хороших способов уменьшить количество кода, написанного вами и вашими клиентами. Если же вы Питонист-эксперт, то вы обновили некоторые свои, возможно, подзабытые знания, а может и нашли парочку новых трюков. Независимо от вашего уровня, я надеюсь, что это путешествие через специальные питоновские методы было поистине магическим (не смог удержаться).
Дополнение 1: Как вызывать магические методы
Надеюсь, эта таблица избавит вас от любых вопросов о том, что за синтаксис вызова магических методов.
Дополнение 2: Изменения в Питоне 3
Опишем несколько главных случаев, когда Питон 3 отличается от 2.x в терминах его объектной модели:
Как создать инициализатор класса someclass
Понятия класса и объекта являются ключевыми в объектно-ориентированных языках программирования, к которым относится также и язык Python . В нем всё является объектами – и строки, и списки, и словари, и функции, и даже сами классы.
(от англ. class) – это шаблон кода, который используется для описания структуры и создания , т.е. экземпляров этого класса.
По сути классы в Python представляют собой типы данных, а объекты – отдельные экземпляры этих типов. Например, в инструкциях a = ‘abcdef’ и b = ‘ABCDEF’ переменным a и b присваиваются объекты строкового типа данных, т.е. экземпляры встроенного класса string .
Если говорить совсем уж просто, то класс можно сравнить с чертежом, по которому создаются объекты. Достаточно создать всего лишь один класс и далее можно будет порождать любое количество его экземпляров, изменяя и дополняя их по мере необходимости.
Благодаря использованию классов Python обладает всеми преимуществами абстрактного подхода в программировании. В частности Питону присущи:
- (от англ. polymorphism) – способность функций и методов обрабатывать данные разных типов. Простыми примерами полиморфизма могут служить: встроенная функция len(s) , которая может работать с разными типами встроенных последовательностей, возвращая количество их элементов; метод s.count(x) , который подсчитывает количество символов x в строке s (один тип данных) или, например, количество элементов x в списке s (другой тип данных); операторы сложения и умножения, которые для чисел (один тип данных) производят операции сложения и умножения, а для строк (другой тип данных) конкатенацию и повторение конкатенации указанное число раз.
- (от англ. encapsulation) – механизм, который позволяет объединять данные и методы, работающие с этими данными, в единый объект, скрывая при этом детали реализации от пользователя. Грамотно написанный класс должен ограничивать доступность своих членов и взаимодействовать с пользователем только с помощью своего интерфейса. При этом нужно помнить, что в Python инкапсуляция работает лишь на уровне соглашения между программистами о том, какие атрибуты следует считать общедоступными, а какие – приватными (подробнее об этом мы поговорим чуть ниже).
- (от англ. inheritance) – еще одна концепция объектно-ориентированного программирования, которая позволяет на основе одного суперкласса создавать множественные подклассы, заимствующие его данные и функциональность с возможностью их изменения и добавления своих данных и функциональности. При этом в Python поддерживается возможность множественного наследования, когда подкласс наследует атрибуты сразу нескольких суперклассов одновременно. Таким образом, наследование способствует повторному использованию уже написанных компонентов программы, помогая избежать избыточности исходного кода.
- (от англ. composition) – возможность создания классов, включающих в себя вызовы уже существующих классов. В результате при создании объектов такого класса они будут состоять или содержать объекты других классов. Здесь важно не путать композицию с наследованием, в том числе множественным. Ведь при наследовании подкласс получает все атрибуты (т.е. возможности) своих суперклассов, а при композиции класс-агрегатор атрибуты не наследует, он просто создает объекты этих классов для использования, например, в качестве своих атрибутов или локальных переменных своих методов.
Благодаря всем этим преимуществам ООП у программистов появляется возможность параллельной разработки отдельных независимых модулей в виде классов, способных скрывать от внешнего мира детали своего внутреннего устройства и объединяющихся в масштабные комплексные приложения посредством предназначенных для этого интерфейсов. Данный процесс можно наглядно представить на примере из жизни, когда отдельные модули МКС собираются независимо друг от друга разными странами, а затем на орбите собираются в одну монолитную космическую станцию за счет имеющихся интерфейсов, обеспечивающих взаимодействие готовых модулей между собой.
Создание классов и объектов
Для создания классов в Python используется инструкция class , которая в общем виде может быть представлена в следующем формате:
В заголовке инструкции сперва записывается служебное слово class , затем через пробел указывается имя класса, которое по принятому в Python соглашению обычно записывается в нотации CupWords , далее в скобках перечисляются имена наследуемых классов (их еще называют суперклассами) и завершается заголовок двоеточием. Если создаваемый класс не наследует других классов, заголовок инструкции разрешается записывать в формате class ClassName: , опуская круглые скобки и записывая двоеточие сразу после имени класса. В любом случае после заголовка записывается вложенный блок инструкций, в котором перечисляются атрибуты данных класса (по умолчанию они доступны для использования всеми создаваемыми экземплярами класса) и атрибуты-методы для обработки его данных, представляющие собой обычные функции, определенные внутри класса и принимающие в качестве первого аргумента параметр self , которому при вызове методов интерпретатор автоматически присваивает объект текущего экземпляра класса. При необходимости в начале тела класса разрешается указывать строку документирования класса, а также конструктор класса __init__() , представляющий собой специальный метод класса, который вызывается автоматически всякий раз при создании нового экземпляра, инициализируя его исходными данными. Изменение имени конструктора запрещено.
После того, как класс будет определен, можно приступать к созданию его отдельных экземпляров, сохраняя их в переменных или структурах данных. Для этого нужно после имени класса просто указать круглые скобки, перечислив в них начальные данные для инициализации создаваемого экземпляра (см. пример №1 ).
Пример №1. Создание классов и объектов в Python.
В нашем примере мы создали класс Person не наследуя никаких других классов, поэтому круглые скобки в заголовке были опущены ( class Person: ). В качестве строки документации мы указали обычную строку в тройных одинарных кавычках, после чего создали атрибут данных класса, присвоив ему название нашей компании ( company = ‘«Okpython»’ ). Этот атрибут стал доступен как из любого экземпляра класса ( person_1.company или person_2.company ), так и напрямую из объекта самого класса ( Person.company ). Далее был определен конструктор класса ( def __init__(self, name=’Немо’): ), который затем при создании экземпляров класса запустился в автоматическом режиме и создал атрибуты данных создаваемых экземпляров (это атрибуты созданных объектов person_1.name и person_2.name ). Тут важно помнить, что наличие в определении класса конструктора с параметрами влечет за собой необходимость в передаче вновь создаваемым экземплярам начальных данных для их инициализации, иначе будет получена ошибка (мы подстраховались, использовав аргумент со значением по умолчанию). В конце тела класса мы добавили еще два метода для установки и получения атрибута данных экземпляров класса ( def set_date(self, date): и def get_date(self): ). Как и говорилось, в качестве первого аргумента у методов был задан параметр self , которому при каждом вызове этих методов интерпретатор автоматически присвоил объекты текущих экземпляров класса (т.е. объекты person_1 и person_2 ).
После создания класса Person мы создали первый экземпляр этого класса, использовав инструкцию вызова объекта класса person_1 = Person(‘Коля’) и указав в качестве начальных данных для нашего конструктора значение аргумента ‘Коля’ . Далее, для доступа к атрибутам созданного объекта мы использовали синтаксис с точкой, сохранив в переменной глобальной области видимости имя сотрудника ( name_1 = person_1.name ), а также установив и затем также сохранив в переменной дату трудоустройства сотрудника в компанию (инструкции person_1.set_date(‘30.05.2022’) и date_1 = person_1.get_date() ). Проделав тоже самое для второго экземпляра, мы сохранили в глобальной переменной название компании, использовав атрибут данных класса ( company = Person.company ), а затем вывели на экран строку документирования класса и данные сотрудников.
Таким образом, процедура создания класса в простейшем случае сводится к объявлению имени класса ClassName в заголовке инструкции class и перечислению в теле класса его атрибутов, состоящих из атрибутов данных и атрибутов-методов. Что касается создания экземпляров класса, то делается это по аналогии с вызовами обычной функции, т.е. при помощи пары круглых скобок после имени класса в формате ClassName(arg_1, arg_2, . arg_n) . После создания экземпляра класса можно начинать пользоваться объявленными атрибутами: получать к ним доступ, изменять, удалять или же добавлять новые атрибуты.
Доступ к атрибутам классов и объектов
Доступ к открытому атрибуту вне класса или объекта может быть получен с помощью имени этого атрибута, указанного через точку после имени класса или объекта в формате obj.attr . Для добавления нового атрибута или изменения значения уже существующего используется привычная нам инструкция присваивания obj.new_attr = value или, соответственно, obj.attr = new_value , а удаление осуществляется инструкцией del obj.attr (см. пример №2 ).
Пример №2. Атрибуты классов и объектов в Python (часть 1).
- – функция проверяет наличие в объекте object атрибута с именем name , возвращая True в случае успеха и False в противном случае. В качестве аргументов ей передаются объект и строка с именем искомого в нем атрибута (см. пример №3 ).
- – возвращает значение атрибута name переданного объекта object , если он существует. В противном случае функция возвращает значение по умолчанию default , если оно было передано, или возбуждает исключение AttributeError . В качестве обязательных аргументов функции передаются объект и строка с именем искомого в нем атрибута.
- – устанавливает новое значение value атрибуту с именем name переданного объекта object , если он существует. В противном случае интерпретатор создает новый атрибут с указанным именем и присваивает ему переданное значение. В качестве первых двух аргументов функции передаются объект и строка с именем искомого в нем атрибута.
- – удаляет из объекта object атрибут с именем name , если объект позволяет это сделать. В качестве аргументов ей передаются объект и строка с именем искомого в нем атрибута.
Пример №3. Функции для работы с атрибутами классов и объектов.
Здесь важно отметить один нюанс – при обращении к атрибуту объекта, созданного на основе классов, интерпретатор производит поиск этого атрибута сначала в самом экземпляре, а затем, при отсутствии такового, во всех классах, расположенных выше в дереве наследования (см. пример №4 ).
Пример №4. Атрибуты классов и объектов в Python (часть 2).
Как видим, даже при отсутствии атрибута непосредственно в самом экземпляре, интерпретатор не возбуждает ошибку, а сперва пытается отыскать его в самом классе. Поэтому даже после удаления атрибута attr_0 в экземпляре obj_1 при повторной попытке обращения к нему через obj_1.attr_0 интерпретатор выдал не ошибку, а вернул значение общедоступного атрибута, найденного в объекте класса ExampleClass .
Получить доступ или посмотреть текущий набор атрибутов класса или экземпляра можно при помощи словаря атрибутов object.__dict__, который динамически изменяется в процессе изменения как количества доступных атрибутов, так и их значений (см. пример №5 ).
Пример №5. Атрибуты классов и объектов в Python (часть 3).
Стоит добавить, что поскольку в Python объекты присутствуют повсюду, атрибут __dict__ имеется не только у классов, но и у других объектов, например, объектов функций.
Статические методы и методы класса
Как было показано выше, обычные атрибуты-методы классов в момент своего вызова в качестве первого аргумента автоматически получают экземпляр класса, посредством которого они вызываются. Такие методы формально называют , поскольку они подразумевают воздействие на объекты экземпляров класса. Однако в Python присутствуют еще две разновидности методов: , которые при вызове в качестве первого аргумента вместо объекта экземпляра автоматически получают объект самого класса, а также , которые представляют собой обычные функции, определенные внутри класса и не требующие передачи экземпляра класса в качестве первого аргумента. Для получения таких методов служат встроенные функции classmethod(method) и staticmethod(method), которые необходимо вызывать внутри классов. Обе функции помечают переданный объект метода method как специальный, т.е. требующий передачи объекта самого класса для метода класса и, соответственно, не требующий передачи экземпляра класса для статического метода (см. пример №6 ).
Пример №6. Виды методов в классах (часть 1).
Объявить статический метод или метод класса можно более элегантно, если учесть, что обе функции classmethod(method) и staticmethod(method) представляют собой не что иное, как готовые встроенные декораторы. Все что нужно, это использовать синтаксис декораторов, записав перед определениями нужных нам методов строки @classmethod и @staticmethod (см. пример №7 ).
Пример №7. Виды методов в классах (часть 2).
Следует помнить, что методы экземпляра предназначены для обработки данных каждого отдельного экземпляра класса, в то время как методы класса призваны обрабатывать данные самого класса, являющиеся общими сразу для всех экземпляров этого класса. Например, методы класса могут использоваться для подсчета созданных экземпляров класса (см. пример №8 ), вести список экземпляров, находящихся в данный момент в оперативной памяти, изменять существующие или устанавливать новые атрибуты-данных класса. Кроме того, если первый аргумент методов экземпляра при определении принято обозначать через self и его нужно передавать явным образом при вызове через класс, то для первого аргумента методов класса принято использовать имя cls , не указывая его явным образом как при вызове из экземпляров, так и вызове через сам класс (еще раз посмотрите пример №7 ).
Что касается статических методов, то они как и методы класса предназначены для работы с атрибутами класса, а не экземпляра. Но при этом они не предполагают автоматической передачи в качестве первого аргумента объекта класса. Поэтому статические методы могут быть использованы как самые обычные функции, выполняющие какие-либо полезные действия так или иначе связанные с данными класса и определенные в его пространстве имен для более тесной связи с классом, а также уменьшения вероятности конфликта имен в глобальной области видимости. Более того, их можно даже не объявлять статическими при помощи функции staticmethod(method) . В таком случае они останутся доступны посредством объекта класса, но станут недоступны для вызовов из экземпляров класса.
Пример №8. Виды методов в классах (часть 3).
Конечно, мы могли бы определить функцию print_num_objects() и вне класса, она по-прежнему выполняла бы свою работу, но, повторимся, тогда она выглядела бы обособленной от класса (особенно для других программистов, читающих наш код), нарушая при этом принцип инкапсуляции и напрасно захломляя глобальное пространство имен модуля.
Инкапсуляция в классах
По умолчанию все атрибуты и методы класса являются открытыми или публичными (от англ. public). Это значит, что они доступны не только внутри класса, но и за его пределами. Для этого достаточно использовать синтаксис доступа к атрибутам при помощи точки (например, obj.attr ) или предназначенные для этого встроенные функции (например, getattr(obj, ‘attr’) ). Однако бывают случаи, когда требуется ограничить прямой доступ к атрибутам классов из вызывающего кода воизбежание передачи им некорректных значений или случайного удаления. Примером может служить возраст человека, который не может быть отрицательным, или значение атрибута, предназначенного для служебного пользования. В таких случаях вполне логично делать атрибуты закрытыми или приватными (от англ. private), предоставляя доступ к ним посредством интерфейсов взаимодействия в виде специальных методов, осуществляющих необходимые проверки передаваемых им значений. При этом следует помнить, что в Python инкапсуляция в классах (т.е. сокрытие данных) поддерживается лишь на уровне соглашения между программистами. Полностью скрыть реализацию класса от целенаправленных злонамеренных действий извне не получится.
Так атрибуты, предназначенные для внутреннего использования, принято начинать с одного символа подчеркивания (например, _var ). Встретив такой атрибут, другой программист будет иметь в виду, что он не предназначен для публичного использования и в дальнейшем может быть даже удален без предварительного уведомления. В тоже время никто не запрещает изменять такой атрибут на свой страх и риск, надеясь, что это не повлияет на работоспособность класса (см. пример №9 ).
Пример №9. Инкапсуляция в классах (часть 1).
Если нужна большая защищенность атрибута, нужно указывать в начале его имени два знака подчеркивания в формате __attr_name . В результате такой манипуляции атрибут останется доступен внутри класса, но станет недоступен по такому имени за его пределами, т.к. вне класса имена атрибутов, начинающихся с двух знаков подчеркивания, автоматически преобразуются интерпретатором из формата __attr_name в формат _Class__attr_name (см. пример №10 ).
Пример №10. Инкапсуляция в классах (часть 2).
Как видим, атрибуты, в именах которых присутствует приставка с двумя символами подчеркивания, действительно недоступны напрямую по своему имени за пределами класса. Тем не менее, они также не обеспечивают настоящего сокрытия данных, т.к. зная имя вмещающего класса всегда можно обратиться к таким атрибутам по их расширенному имени в формате _Class__attr_name из любой точки программы, где имеется ссылка на экземпляр класса.
- fget – объект функции, которая будет вызываться при попытке прочитать значение атрибута и возвращать вычисленное значение атрибута;
- fset – объект функции, которая будет вызываться при попытке выполнить операцию присваивания;
- fdel – объект функции, которая будет вызываться при попытке удаления атрибута;
- doc – строка документирования с описанием атрибута, если это необходимо.
Результатом вызова функции property является объект свойства, присваиваемый имени атрибута, которым требуется управлять и который будет находиться в области видимости класса и наследоваться всеми его экземплярами (см. пример №11 ).
Пример №11. Инкапсуляция в классах (часть 3).
В нашем примере мы создали класс с управляемым атрибутом age . Благодаря этому, при каждой попытке обращения к нему по имени person_age.age будет вызываться один из предназначенных для его управления методов. Именно поэтому атрибут остался доступен для чтения, но стал недоступен для удаления. При этом заметьте, что по факту, воизбежание конфликта имен, мы сохранили данные в атрибуте с именем _age , т.к. само имя age было зарезервировано для хранения объекта свойства, возвращаемого функцией property .
Если необходимо запретить изменение значения атрибута или даже сделать его полностью закрытым для доступа из кода за пределами класса, нужно всего лишь изменить поведение отвечающих за это методов, переданных функции property в качестве аргументов (см. пример №12 ).
Пример №12. Инкапсуляция в классах (часть 4).
Следует добавить, что объект свойства, возвращаемый функцией property обладает атрибутами-методами getter(get_method), setter(set_method) и deleter(del_method), которые позволяют определить соответствующие методы для обработки обращений к управляемому атрибуту. При этом первый метод практически никогда не используется, т.к. вызов функции property() уже подразумевает его передачу в качестве первого аргумента (см. пример №13 ).
Пример №13. Инкапсуляция в классах (часть 5).
Опять же, весь процесс можно осуществить при помощи декораторов, которые делают код более простым и приятным для восприятия (см. пример №14 ).
Пример №14. Инкапсуляция в классах (часть 6).
Обратите внимание, что при использовании декораторов все методы должны иметь одно и то же имя, иначе интерпретатор возбудит исключение.
Наследование классов
Как упоминалось в начале параграфа, наследование позволяет создавать новый класс, называемый , на основе уже существующего класса, называемого . Более того, в Python поддерживается механизм множественного наследования, при котором подкласс может наследовать атрибуты и методы сразу нескольких суперклассов. Все что нужно, это перечислить наследуемые суперклассы в порядке их приоритета в скобках в заголовке инструкции class (см. пример №15 ).
Пример №15. Наследование классов в Python (часть 1).
В нашем примере подкласс Sub_3 наследует атрибуты сразу двух суперклассов Super_1 и Super_2 . Однако поскольку оба суперкласса обладают атрибутом с именем attr_0 , интерпретатор выбрал для подкласса значение атрибута суперкласса Super_1 , т.к. при наследовании поиск атрибутов ведется сперва в самом подклассе, а затем в списке наследуемых суперклассов слева направо. Именно поэтому подкласс Sub_3 унаследовал конструктор первого суперкласса. Если бы нам понадобилось отдать приоритет атрибутам второго суперкласса, его пришлось бы перечислить в скобках первым.
Помимо того, что подклассы наследуют данные и методы своих суперклассов, они могут определять и свои собственные атрибуты. Так в подклассе Sub_3 были определены собственный атрибут данных attr_3 и метод method_3 . Но и это еще не все. При необходимости подклассы могут изменять (специализировать) наследуемые атрибуты или даже полностью замещать их (см. пример №16 ).
Пример №16. Наследование классов в Python (часть 2).
Следует заметить, что в ходе специализации методов в подклассах внутри замещающего метода обычно предусматривается вызов исходной версии метода наследуемого суперкласса. Это позволяет выполнить предусмотренный в суперклассе набор действий по умолчанию без необходимости повторного написания уже имеющегося кода. Так в подклассе SubClass нашего примера мы расширили конструктор и метод method_2 суперкласса, вызвав исходные версии методов вместо того, чтобы заново переписывать их код.
Кстати, доступ к оригинальным методам может быть получен не только напрямую через имя суперкласса, но и через встроенную функцию super([type[, object-or-type]]), возвращающую специальный объект-посредник, делегирующий вызовы метода суперклассу указанного типа type . В качестве необязательных аргументов она принимает тип (т.е. объект подкласса), для которого будет производиться поиск суперкласса, а также конкретный объект экземпляра подкласса или объект самого подкласса, для которого требуется получить доступ к методу (см. пример №17 ).
Пример №17. Наследование классов в Python (часть 3).
Как видим, функция super() может вызываться из подкласса без аргументов. В таком случае интерпретатор автоматически использует в качестве аргументов текущий подкласс и объект self . Кроме того, ее можно вызывать и вне классов, явно передавая требуемый тип и целевой объект.
Теперь рассмотрим подробнее ситуацию, когда подкласс наследует сразу несколько суперклассов с одинаковыми именами атрибутов. Выше мы уже указывали, что в таком случае подклассом будет унаследовано значение атрибута того суперкласса, который перечисляется в заголовке инструкции class первым. Но что делать, когда важны значения атрибутов обоих классов? В этом случае, чтобы гарантировать принадлежность атрибута тому классу, который его использует, необходимо в начале имени атрибута поставить два символа подчеркивания везде, где оно используется этим классом (см. пример №18 ).
Пример №18. Наследование классов в Python (часть 4).
В нашем примере благодаря наличию приставки из двух знаков подчеркивания атрибуты остались доступны по короткому имени внутри класса, но стали недоступны вне класса, т.к. имена этих атрибутов были дополнены интерпретатором именами их классов до _A__y и _B__y , что и показал словарь атрибутов obj.__dict__ после вызова методов obj.set_a() и obj.set_b() . При этом атрибут x , который использовался без спецприставки, был методом obj.set_b() перезаписан.
Композиция классов
Композиция классов – это еще один принцип ООП, использующийся в Python и предоставляющий возможность создания классов, включающих в себя вызовы уже существующих классов (см. пример №19 ).
Пример №19. Композиция классов в Python.
В нашем примере мы использовали вызовы классов Компания и Площадь внутри класса Магазин , использовав реализации их интерфейсов, т.е. готовые экземпляры этих классов, для получения требуемых нам возможностей: имени компании и метода для расчета площади прямоугольника.
Таким образом, композиция представляет собой еще одну концепцию повторного использования программного кода, предполагающую формирование целого из частей, при котором класс-контейнер использует внутри себя экземпляры других классов-доноров, выступая в качестве контролера их интерфейсов. Такой подход бывает полезным в крупных системах, в которых множественное наследование большого числа классов может приводить к определенным трудностям, а также в случаях, когда нужно повторно использовать лишь небольшую часть возможностей базового класса (как в нашем примере).
Магические методы и перегрузка операторов
представляет собой процесс перехватывания встроенных операций при помощи специальных методов классов, называемых . Все магические методы имеют специальные имена, начинающиеся и заканчивающиеся двумя символами подчеркивания, что отличает их от других имен, которые обычно определяются в классах. Встречая такие методы, интерпретатор автоматически вызывает их при выполнении соответствующих встроенных операций над экземплярами классов, воспринимая возвращаемые ими значения как результаты этих операций.
Перегрузка операторов позволяет классам участвовать в обычных операциях, делая экземпляры классов более похожими на представителей встроенных типов данных. Достигается такой эффект за счет того, что магические методы позволяют перегружать все операторы выражений, а также такие операции, как ввод и вывод, вызов функций, обращение к атрибутам и многие другие операции (см. пример №20 ).
Пример №20. Перегрузка операторов в Python.
В примере мы использовали два метода перегрузки, имеющихся в Python : конструктор класса __init__ , а также метод __sub__ , который отвечает за операцию вычитания. Последний метод мы использовали для того, чтобы экземпляры созданного нами типа Num могли использоваться в операции вычитания целых чисел и других экземпляров этого типа.
Все магические методы могут свободно наследоваться от суперклассов и переопределяться в подклассах, как и любые другие методы. Если же какой-то метод не реализован в классе, это лишь означает, что соответствующая ему операция не поддерживается данным классом, а попытка применения такой операции будет возбуждать исключение (что мы и увидели при попытке выполнить инструкцию d = a + 5 , содержащую операцию сложения экземпляра a нового типа Num с целым числом).
Перечислим для ознакомления наиболее часто используемые магические методы: __add__ (сложение), __sub__ (вычитание), __mul__ (умножение), __truediv__ (деление), __mod__ (остаток от деления), __pow__ (возведение в степень), __and__ (логическое И), __or__ (логическое ИЛИ), __getattr__ (обращение к атрибуту), __setattr__ (присваивание атрибуту), __delattr__ (удаление атрибута), __getitem__ (доступ к элементу по индексу, извлечение среза, итерации), __setitem__ (присваивание элементу по индексу или срезу), __delitem__ (удаление среза или элемента по индексу), __len__ (длина), __bool__ (проверка логического значения), __lt__ (меньше), __gt__ (больше), __le__ (меньше или равно), __ge__ (больше или равно), __eq__ (равно), __ne__ (не равно), __iter__ (получение итератора), __iadd__ (комбинированный оператор сложения), __next__ (получение следующего элемента итератора), __contains__ (оператор проверки на вхождение in ), __new__ (создание объекта), __init__ (конструктор), __del__ (деструктор), __call__ (вызов функции) и др.
Значительно большее количество имеющихся магических методов можно найти, например, на официальном сайте Python в разделе Data model справочного руководства.
Конструкторы и деструкторы
Как говорилось выше, конструктор представляет собой магический метод, который автоматически вызывается при создании каждого нового экземпляра класса, в котором он определен. Это позволяет передавать вновь создаваемым экземплярам начальные данные, тем самым инициализируя некоторое состояние объекта еще до начала его использования. Для объявления конструкторов используется имя __init__ , изменять которое запрещается.
Далее, если созданные объекты после выполнения своего предназначения становятся ненужными, имеет смысл удалять их для высвобождения памяти. В Python для этих целей имеется автоматический сборщик мусора, удаляющий объекты, которые выходят за пределы текущей области видимости или счетчики ссылок на которые становятся равными нулю. Кроме того, ненужные объекты можно удалять и самостоятельно, используя, например, инструкцию del . В любом случае, если ссылок на объект больше не остается, интерпретатор вызывает деструктор – еще один магический метод с именем __del__ , представляющий собой операцию окончательного удаления объектов. Используются деструкторы реже конструкторов, но в некоторых ситуациях их применение бывает весьма оправданным. Так деструкторы обычно определяют в классах, для которых перед стандартным удалением требуется выполнить еще какие-нибудь действия, например, закрыть файл или соединение с базой данных (см. пример №21 ).
Пример №21. Конструкторы и деструкторы в Python.
Обратите внимание, что инструкция del obj не вызывает напрямую метод obj.__del__() , она лишь уменьшает счетчик ссылок для соответствующего объекта на единицу. И только тогда, когда счетчик полностью обнуляется, интерпретатор использует деструктор для окончательной зачистки следов существования ненужного объекта в памяти.
Краткие итоги параграфа
- Класс – это шаблон кода, который используется для описания структуры и создания объектов, т.е. экземпляров этого класса.
- Python обладает всеми преимуществами абстрактного подхода в программировании. В частности ему присущи:
- полиморфизм – способность функций и методов обрабатывать данные разных типов;
- инкапсуляция – механизм, который позволяет объединять данные и методы, работающие с этими данными, в единый объект, скрывая при этом детали реализации от пользователя;
- наследование – еще одна концепция объектно-ориентированного программирования, которая позволяет на основе одного суперкласса создавать множественные подклассы, заимствующие его данные и функциональность с возможностью их изменения и добавления своих данных и функциональности;
- композиция (агрегирование) – возможность создания классов, включающих в себя вызовы уже существующих классов.
Помогите проекту, подпишитесь!
Подписка на учебные материалы сайта оформляется сроком на один год и стоит около 15 у.е. После подписки вам станут доступны следующие возможности.
- Доступ ко всем ответам на вопросы и решениям задач.
- Возможность загрузки учебных кодов и программ на свой компьютер.
- Доступ ко всем тренажерам и видеоурокам. Доступ к видеотрансляциям и онлайн-курсам.
- Возможность внести свой скромный вклад в развитие проекта и мира во всем мире, а также выразить свою благодарить автору за его труд. Нам очень нужна ваша поддержка!

На страницу подписки
Вопросы и задания для самоконтроля
1. Каково основное назначение ООП в языке Python ? Показать решение.
Ответ. Основное назначение ООП в языке Python , да и в программировании в целом, состоит в том, чтобы обеспечить многократное использование программного кода посредством его разложения на структурные составляющие и последующей адаптации, а не изменения или создания нового кода.
2. Перечислите основные компоненты парадигмы ООП в языке Python ? Показать решение.
Ответ. Полиморфизм, инкапсуляция, наследование и композиция (описание смотрите в начале параграфа).
3. Что представляет собой класс? Как создать класс? Показать решение.
Ответ. Класс представляет собой шаблон кода, который используется для описания структуры и создания объектов, т.е. экземпляров этого класса. Для создания классов в Python предназначена инструкция class . В заголовке инструкции указываются имя класса и перечисляемые в скобках через запятую наследуемые суперклассы, а в теле инструкции – атрибуты класса, представляющие собой данные и методы для их обработки.
4. Как создать экземпляр класса? Показать решение.
Ответ. Для создания экземпляра класса нужно после имени класса указать круглые скобки, перечислив в них начальные данные для инициализации создаваемого экземпляра, если конструктор класса это предполагает. Внешне данная инструкция похожа на вызов обычной функции.
5. Для чего используется метод __init__ ? Показать решение.
Ответ. Конструктор класса __init__ , если он присутствует в классе или наследуется им, служит для автоматической инициализации вновь создаваемых экземпляров класса начальными данными. Важно помнить, что если конструктор определяется с параметрами, которые не предполагают значений по умолчанию, то при создании экземпляров класса аргументам должны передаваться значения, иначе интерпретатор обязательно возбудит соответствующее исключение.
6. Что представляет из себя первый аргумент в методах классов? Показать решение.
Ответ. Посредством первого аргумента методы классов получают ссылку на объект экземпляра, который подразумевается при вызове данного метода. Именно поэтому про методы классов говорят, что они «объектно-ориентированные», то есть предназначенны для обработки или изменения объектов. Согласно общепринятым соглашениям в качестве имени первого аргумента используется идентификатор self .
7. Как создать статический метод? А метод класса? Показать решение.
Ответ. Для создания методов классов и статических методов служат встроенные функции classmethod(method) и staticmethod(method) , которые необходимо вызывать либо использовать в качестве декораторов внутри классов.
8. Назовите самый простой способ сделать атрибут класса недоступным по его имени за пределами класса. Показать решение.
Ответ. Проще всего использовать два символа нижнего подчеркивания, например, __name . В этом случае атрибут станет недоступен вне класса по его имени, т.к. оно будет искажено интерпретатором до формата _Class__name . При этом не стоит забывать, что данный способ не обеспечивает настоящего сокрытия данных, т.к. зная имя вмещающего класса всегда можно обратиться к таким атрибутам по их расширенному имени _Class__attr_name из любой точки программы, где имеется ссылка на экземпляр класса.
9. Для чего предназначена встроенная функция property() ? Какие аргументы она ожидает получить? Можно ли использовать ее в качестве декоратора? Показать решение.
Ответ. Функция property(fget=None, fset=None, fdel=None, doc=None) позволяет создать свойство класса (управляемый атрибут). Функция принимает четыре аргумента со значениями по умолчанию None : fget – объект функции, которая будет вызываться при попытке прочитать значение атрибута и возвращать вычисленное значение атрибута; fset – объект функции, которая будет вызываться при попытке выполнить операцию присваивания; fdel – объект функции, которая будет вызываться при попытке удаления атрибута; doc – строка документирования с описанием атрибута, если это необходимо. Для использования ее в качестве декоратора, необходимо пометить метод, например, с именем name , отвечающий за получение значения атрибута аннотацией @property , а также при необходимости создать еще два метода с таким же именем для установки значения атрибута и удаления атрибута, использовав соответственно аннотации @name.setter и @name.deleter .
10. Как выполняется поиск унаследованных атрибутов? Показать решение.
Ответ. Поиск унаследованных атрибутов выполняется сначала в объекте экземпляра, затем в классе, из которого был создан экземпляр, и далее, по умолчанию, во всех суперклассах в направлении снизу вверх и слева направо в дереве объектов. Как только будет найдено первое вхождение атрибута, поиск прекращается.
11. Как получить доступ из подкласса к методу суперкласса? Показать решение.
Ответ. В ходе наследования доступ к оригинальным методам может быть получен как напрямую через имя суперкласса в формате SuperClass.method(self) , так и через встроенную функцию super([type[, object-or-type]]) , возвращающую специальный объект-посредник, делегирующий вызовы метода данного объекта экземпляра или подкласса суперклассу указанного типа type .
12. Что представляет из себя принцип композиции? Показать решение.
Ответ. Принцип композиции подразумевает использование в определении класса вызовов других классов. В результате получаемые экземпляры класса как бы собираются из экземпляров других классов, реализуя подход формирования целого из частей. Такой подход бывает полезным в крупных системах, в которых множественное наследование большого числа классов может приводить к определенным трудностям, а также в случаях, когда нужно повторно использовать лишь небольшую часть возможностей базового класса.
13. Для чего используются магические методы? Как отличить их от обычных методов? Показать решение.
Ответ. Магические методы позволяют перегружать все операторы выражений, а также такие операции, как ввод и вывод, вызов функций, обращение к атрибутам и т.д. Благодаря этому классы могут участвовать в обычных операциях, что делает их более похожими на встроенные типы. Все магические методы имеют специальные имена, начинающиеся и заканчивающиеся двумя символами подчеркивания, что отличает их от других имен, которые обычно определяются в классах.
14. В каких случаях деструктор автоматически вызывается интерпретатором? Показать решение.
Ответ. Деструктор, представляемый магическим методом __del__ , автоматически вызывается сборщиком мусора при окончательном удалении объектов, которые выходят за пределы текущей области видимости или счетчики ссылок на которые становятся равными нулю.
15. Исправьте в коде все ошибки так, чтобы скрипт заработал. Показать решение.
Руководство по магическим методам в Питоне
Это перевод 1.17 версии руководства от Rafe Kettler.
Содержание
Вступление
Что такое магические методы? Они всё в объектно-ориентированном Питоне. Это специальные методы, с помощью которых вы можете добавить в ваши классы «магию». Они всегда обрамлены двумя нижними подчеркиваниями (например, __init__ или __lt__ ). Ещё, они не так хорошо документированны, как хотелось бы. Все магические методы описаны в документации, но весьма беспорядочно и почти безо всякой организации. Поэтому, чтобы исправить то, что я воспринимаю как недостаток документации Питона, я собираюсь предоставить больше информации о магических методах, написанной на понятном языке и обильно снабжённой примерами. Надеюсь, это руководство вам понравится. Используйте его как обучающий материал, памятку или полное описание. Я просто постарался как можно понятнее описать магические методы.
Конструирование и инициализация.
Всем известен самый базовый магический метод, __init__ . С его помощью мы можем инициализировать объект. Однако, когда я пишу x = SomeClass() , __init__ не самое первое, что вызывается. На самом деле, экземпляр объекта создаёт метод __new__ , а затем аргументы передаются в инициализатор. На другом конце жизненного цикла объекта находится метод __del__ . Давайте подробнее рассмотрим эти три магических метода:
-
__new__(cls, [. )
Это первый метод, который будет вызван при инициализации объекта. Он принимает в качестве параметров класс и потом любые другие аргументы, которые будут переданы в __init__ . __new__ используется весьма редко, но иногда бывает полезен, в частности, когда класс наследуется от неизменяемого (immutable) типа, такого как кортеж (tuple) или строка. Я не намерен очень детально останавливаться на __new__ , так как он не то чтобы очень часто нужен, но этот метод очень хорошо и детально описан в документации.Замечание от переводчика: svetlov отмечает, что здесь автор ошибается, на самом деле __del__ всегда вызывается по завершении работы интерпретатора.
Переопределение операторов на произвольных классах
Одно из больших преимуществ использования магических методов в Питоне то, что они предоставляют простой способ заставить объекты вести себя по подобию встроенных типов. Это означает, что вы можете избежать унылого, нелогичного и нестандартного поведения базовых операторов. В некоторых языках обычное явление писать как-нибудь так:
Вы, конечно, можете поступать так же и в Питоне, но это добавляет путаницы и ненужной многословности. Разные библиотеки могут по разному называть одни и те же операции, заставляя использующего их программиста совершать больше действий, чем необходимо. Используя силу магических методов, мы можем определить нужный метод ( __eq__ , в этом случае), и так точно выразить, что мы имели в виду:
Это одна из сильных сторон магических методов. Подавляющее большинство из них позволяют определить, что будут делать стандартные операторы, так что мы можем использовать операторы на своих классах так, как будто они встроенные типы.
Магические методы сравнения
В Питоне уйма магических методов, созданных для определения интуитивного сравнения между объектами используя операторы, а не неуклюжие методы. Кроме того, они предоставляют способ переопределить поведение Питона по-умолчанию для сравнения объектов (по ссылке). Вот список этих методов и что они делают:
-
__cmp__(self, other)
Самый базовый из методов сравнения. Он, в действительности, определяет поведение для всех операторов сравнения (>, ==, !=, итд.), но не всегда так, как вам это нужно (например, если эквивалентность двух экземпляров определяется по одному критерию, а то что один больше другого по какому-нибудь другому). __cmp__ должен вернуть отрицательное число, если self < other , ноль, если self == other , и положительное число в случае self > other . Но, обычно, лучше определить каждое сравнение, которое вам нужно, чем определять их всех в __cmp__ . Но __cmp__ может быть хорошим способом избежать повторений и увеличить ясность, когда все необходимые сравнения оперерируют одним критерием.Теперь мы можем создать два Word (при помощи Word(‘foo’) и Word(‘bar’) ) и сравнить их по длине. Заметьте, что мы не определяли __eq__ и __ne__ , так как это приведёт к странному поведению (например, Word(‘foo’) == Word(‘bar’) будет расцениваться как истина). В этом нет смысла при тестировании на эквивалентность, основанную на длине, поэтому мы оставляем стандартную проверку на эквивалентность от str .
Сейчас, кажется, удачное время упомянуть, что вы не должны определять каждый из магических методов сравнения, чтобы полностью охватить все сравнения. Стандартная библиотека любезно предоставляет нам класс-декторатор в модуле functools , который и определит все сравнивающие методы, от вас достаточно определить только __eq__ и ещё один ( __gt__ , __lt__ и т.п.) Эта возможность доступна начиная с 2.7 версии Питона, но если это вас устраивает, вы сэкономите кучу времени и усилий. Для того, чтобы задействовать её, поместите @total_ordering над вашим определением класса.Числовые магические методы
Точно так же, как вы можете определить, каким образом ваши объекты будут сравниваться операторами сравнения, вы можете определить их поведение для числовых операторов. Приготовтесь, друзья, их много. Для лучшей организации, я разбил числовые магические методы на 5 категорий: унарные операторы, обычные арифметические операторы, отражённые арифметические операторы (подробности позже), составные присваивания и преобразования типов.
Унарные операторы и функции
Унарные операторы и функции имеют только один операнд — отрицание, абсолютное значение, и так далее.
-
__pos__(self)
Определяет поведение для унарного плюса ( +some_object )Обычные арифметические операторы
Теперь рассмотрим обычные бинарные операторы (и ещё пару функций): +, -, * и похожие. Они, по большей части, отлично сами себя описывают.
-
__add__(self, other)
Сложение.Отражённые арифметические операторы
Помните как я сказал, что собираюсь остановиться на отражённой арифметике подробнее? Вы могли подумать, что это какая-то большая, страшная и непонятная концепция. На самом деле всё очень просто. Вот пример:
Это «обычное» сложение. Единственное, чем отличается эквивалентное отражённое выражение, это порядок слагаемых:
Таким образом, все эти магические методы делают то же самое, что и их обычные версии, за исключением выполнения операции с other в качестве первого операнда и self в качестве второго. В большинстве случаев, результат отражённой операции такой же, как её обычный эквивалент, поэтому при определении __radd__ вы можете ограничиться вызовом __add__ да и всё. Заметьте, что объект слева от оператора ( other в примере) не должен иметь обычной неотражённой версии этого метода. В нашем примере, some_object.__radd__ будет вызван только если в other не определён __add__ .
-
__radd__(self, other)
Отражённое сложение.Составное присваивание
В Питоне широко представлены и магические методы для составного присваивания. Вы скорее всего уже знакомы с составным присваиванием, это комбинация «обычного» оператора и присваивания. Если всё ещё непонятно, вот пример:
Каждый из этих методов должен возвращать значение, которое будет присвоено переменной слева (например, для a += b , __iadd__ должен вернуть a + b , что будет присвоено a ). Вот список:
-
__iadd__(self, other)
Сложение с присваиванием.Магические методы преобразования типов
Кроме того, в Питоне множество магических методов, предназначенных для определния поведения для встроенных функций преобразования типов, таких как float() . Вот они все:
-
__int__(self)
Преобразование типа в int.Представление своих классов
Часто бывает полезно представление класса в виде строки. В Питоне существует несколько методов, которые вы можете определить для настройки поведения встроенных функций при представлении вашего класса.
-
__str__(self)
Определяет поведение функции str() , вызванной для экземпляра вашего класса.Контроль доступа к атрибутам
Многие люди, пришедшие в Питон из других языков, жалуются на отсутствие настоящей инкапсуляции для классов (например, нет способа определить приватные атрибуты с публичными методами доступа). Это не совсем правда: просто многие вещи, связанные с инкапсуляцией, Питон реализует через «магию», а не явными модификаторами для методов и полей. Смотрите:
-
__getattr__(self, name)
Вы можете определить поведение для случая, когда пользователь пытается обратиться к атрибуту, который не существует (совсем или пока ещё). Это может быть полезным для перехвата и перенаправления частых опечаток, предупреждения об использовании устаревших атрибутов (вы можете всё-равно вычислить и вернуть этот атрибут, если хотите), или хитро возвращать AttributeError , когда это вам нужно. Правда, этот метод вызывается только когда пытаются получить доступ к несуществующему атрибуту, поэтому это не очень хорошее решение для инкапсуляции.Ещё раз, мощь магических методов в Питоне невероятна, а с большой силой приходит и большая ответственность. Важно знать, как правильно использовать магические методы, ничего не ломая.
Итак, что мы узнали об управлении доступом к атрибутам? Их не нужно использовать легкомысленно. На самом деле, они имеют склонность к чрезмерной мощи и нелогичности. Причина, по которой они всё-таки существуют, в удволетворении определённого желания: Питон склонен не запрещать плохие штуки полностью, а только усложнять их использование. Свобода первостепенна, поэтому вы на самом деле можете делать всё, что хотите. Вот пример использования методов контроля доступа (заметьте, что мы используем super , так как не все классы имеют атрибут __dict__ ):
Создание произвольных последовательностей
В Питоне существует множество способов заставить ваши классы вести себя как встроенные последовательности (словари, кортежи, списки, строки и так далее). Это, безусловно, мои любимые магические методы, из-за до абсурда высокой степени контроля, которую они дают и той магии, от которой с экземплярами ваших классов вдруг начинает прекрасно работать целое множество глобальных функций. Но, до того как мы перейдём ко всяким хорошим вещам, мы должны знать о протоколах.
Протоколы
Теперь, когда речь зашла о создании собственных последовательностей в Питоне, пришло время поговорить о протоколах. Протоколы немного похожи на интерфейсы в других языках тем, что они предоставляют набор методов, которые вы должны реализовать. Однако, в Питоне протоколы абсолютно ни к чему не обязывают и не требуют обязательно реализовать какое-либо объявление. Наверное, они больше похожи на руководящие указания.
Почему мы заговорили о протоколах? Потому, что реализация произвольных контейнерных типов в Питоне влечёт за собой использование некоторых из них. Во-первых, протокол для определения неизменяемых контейнеров: чтобы создать неизменяемый контейнер, вы должны только определить __len__ и __getitem__ (продробнее о них дальше). Протокол изменяемого контейнера требует того же, что и неизменяемого контейнера, плюс __setitem__ и __delitem__ . И, наконец, если вы хотите, чтобы ваши объекты можно было перебирать итерацией, вы должны определить __iter__ , который возвращает итератор. Этот итератор должен соответствовать протоколу итератора, который требует методов __iter__ (возвращает самого себя) и next .
Магия контейнеров
Без дальнейшего промедления, вот магические методы, используемые контейнерами:
-
__len__(self)
Возвращает количество элементов в контейнере. Часть протоколов для изменяемого и неизменяемого контейнеров.Пример
Для примера, давайте посмотрим на список, который реализует некоторые функциональные конструкции, которые вы могли встретить в других языках (Хаскеле, например).
Теперь у вас есть полезный (относительно) пример реализации своей собственной последовательности. Существуют, конечно, и куда более практичные реализации произвольных последовательностей, но большое их число уже реализовано в стандартной библиотеке (с батарейками в комплекте, да?), такие как Counter , OrderedDict , NamedTuple .
Отражение
Вы можете контролировать и отражение, использующее встроенные функции isinstance() и issubclass() , определив некоторые магические методы. Вот они:
-
__instancecheck__(self, instance)
Проверяет, является ли экземлпяр членом вашего класса ( isinstance(instance, class) , например.Вызываемые объекты
Как вы наверное уже знаете, в Питоне функции являются объектами первого класса. Это означает, что они могут быть переданы в функции или методы так же, как любые другие объекты. Это невероятно мощная особенность.
Специальный магический метод позволяет экземплярам вашего класса вести себя так, как будто они функции, тоесть вы сможете «вызывать» их, передавать их в функции, которые принимают функции в качестве аргументов и так далее. Это другая удобная особенность, которая делает программирование на Питоне таким приятным.
-
__call__(self, [args. ])
Позволяет любому экземпляру вашего класса быть вызванным как-будто он функция. Главным образом это означает, что x() означает то же, что и x.__call__() . Заметьте, __call__ принимает произвольное число аргументов; то есть, вы можете определить __call__ так же как любую другую функцию, принимающую столько аргументов, сколько вам нужно.Менеджеры контекста
В Питоне 2.5 было представлено новое ключевое слово вместе с новым способом повторно использовать код, ключевое слово with . Концепция менеджеров контекста не являлась новой для Питона (она была реализована раньше как часть библиотеки), но в PEP 343 достигла статуса языковой конструкции. Вы могли уже видеть выражения с with :
Менеджеры контекста позволяют выполнить какие-то действия для настройки или очистки, когда создание объекта обёрнуто в оператор with . Поведение менеджера контекста определяется двумя магическими методами:
-
__enter__(self)
Определяет, что должен сделать менеджер контекста в начале блока, созданного оператором with . Заметьте, что возвращаемое __enter__ значение и есть то значение, с которым производится работа внутри with .Пример использования Closer с FTP-соединением (сокет, имеющий метод close):
Видите, как наша обёртка изящно управляется и с правильными и с неподходящими объектами. В этом сила менеджеров контекста и магических методов. Заметьте, что стандартная библиотека Питона включает модуль contextlib, который включает в себя contextlib.closing() — менеджер контекста, который делает приблизительно то же (без какой-либо обработки случая, когда объект не имеет метода close() ).
Абстрактные базовые классы
Построение дескрипторов
Дескрипторы это такие классы, с помощью которых можно добавить свою логику к событиям доступа (получение, изменение, удаление) к атрибутам других объектов. Дескрипторы не подразумевается использовать сами по себе; скорее, предполагается, что ими будут владеть какие-нибудь связанные с ними классы. Дескрипторы могут быть полезны для построения объектно-ориентированных баз данных или классов, чьи атрибуты зависят друг от друга. В частности, дескрипторы полезны при представлении атрибутов в нескольких системах исчисления или каких-либо вычисляемых атрибутов (как расстояние от начальной точки до представленной атрибутом точки на сетке).
Чтобы класс стал дескриптором, он должен реализовать по крайней мере один метод из __get__ , __set__ или __delete__ . Давайте рассмотрим эти магические методы:
-
__get__(self, instance, instance_class)
Определяет поведение при возвращении значения из дескриптора. instance это объект, для чьего атрибута-дескриптора вызывается метод. owner это тип (класс) объекта.Копирование
В Питоне оператор присваивания не копирует объекты, а только добавляет ещё одну ссылку. Но для коллекций, содержащих изменяемые элементы, иногда необходимо полноценное копирование, чтобы можно было менять элементы одной последовательности, не затрагивая другую. Здесь в игру и вступает copy . К счастью, модули в Питоне не обладают разумом, поэтому мы можем не беспокоиться что они вдруг начнут бесконтрольно копировать сами себя и вскоре линуксовые роботы заполонят всю планету, но мы должны сказать Питону как правильно копировать.
-
__copy__(self)
Определяет поведение copy.copy() для экземпляра вашего класса. copy.copy() возвращает поверхностную копию вашего объекта — это означает, что хоть сам объект и создан заново, все его данные ссылаются на данные оригинального объекта. И при изменении данных нового объекта, изменения будут происходить и в оригинальном.Использование модуля pickle на своих объектах
Pickle это модуль для сериализации структур данных Питона и он может быть невероятно полезен, когда вам нужно сохранить состояние какого-либо объекта и восстановить его позже (обычно, в целях кэширования). Кроме того, это ещё и отличный источник переживаний и путаницы.
Сериализация настолько важна, что кроме своего модуля ( pickle ) имеет и свой собственный протокол и свои магические методы. Но для начала о том, как сериализовать с помощью pickle уже существующие типы данных (спокойно пропускайте, если вы уже знаете).
Вкратце про сериализацию
Давайте погрузимся в сериализацию. Допустим, у вас есть словарь, который вы хотите сохранить и восстановить позже. Вы должны записать его содержимое в файл, тщательно убедившись, что пишете с правильным синтаксисом, потом восстановить его, или выполнив exec() , или прочитав файл. Но это в лучшем случае рискованно: если вы храните важные данные в тексте, он может быть повреждён или изменён множеством способов, с целью обрушить вашу программу или, вообще, запустить какой-нибудь опасный код на вашем компьютере. Лучше использовать pickle:
И вот, спустя несколько часов, нам снова нужен наш словарь:
Что произошло? Точно то, что и ожидалось. data как-будто всегда тут и была.
Теперь, немного об осторожности: pickle не идеален. Его файлы легко испортить случайно или преднамеренно. Pickle, может быть, безопаснее чем текстовые файлы, но он всё ещё может использоваться для запуска вредоносного кода. Кроме того, он несовместим между разными версиями Питона, поэтому если вы будете распространять свои объекты с помощью pickle, не ожидайте что все люди смогут их использовать. Тем не менее, модуль может быть мощным инструментом для кэширования и других распространённых задач с сериализацией.
Сериализация собственных объектов.
Модуль pickle не только для встроенных типов. Он может использоваться с каждым классом, реализующим его протокол. Этот протокол содержит четыре необязательных метода, позволяющих настроить то, как pickle будет с ними обращаться (есть некоторые различия для расширений на C, но это за рамками нашего руководства):
-
__getinitargs__(self)
Если вы хотите, чтобы после десериализации вашего класса был вызыван __init__ , вы можете определить __getinitargs__ , который должен вернуть кортеж аргументов, который будет отправлен в __init__ . Заметьте, что этот метод работает только с классами старого стиля.Пример
Для примера опишем грифельную доску ( Slate ), которая запоминает что и когда было на ней записано. Впрочем, конкретно эта доска становится чистой каждый раз, когда она сериализуется: текущее значение не сохраняется.
Заключение
Цель этого руководства донести что-нибудь до каждого, кто его читает, независимо от его опыта в Питоне или объектно-ориентированном программировании. Если вы новичок в Питоне, вы получили ценные знания об основах написания функциональных, элегантных и простых для использования классов. Если вы программист среднего уровня, то вы, возможно, нашли несколько новых приятных идей и стратегий, несколько хороших способов уменьшить количество кода, написанного вами и вашими клиентами. Если же вы Питонист-эксперт, то вы обновили некоторые свои, возможно, подзабытые знания, а может и нашли парочку новых трюков. Независимо от вашего уровня, я надеюсь, что это путешествие через специальные питоновские методы было поистине магическим (не смог удержаться).
Дополнение 1: Как вызывать магические методы
Надеюсь, эта таблица избавит вас от любых вопросов о том, что за синтаксис вызова магических методов.
Дополнение 2: Изменения в Питоне 3
Опишем несколько главных случаев, когда Питон 3 отличается от 2.x в терминах его объектной модели:
Python Class Constructors: Control Your Object Instantiation
Class constructors are a fundamental part of object-oriented programming in Python. They allow you to create and properly initialize objects of a given class, making those objects ready to use. Class constructors internally trigger Python’s instantiation process, which runs through two main steps: instance creation and instance initialization.
If you want to dive deeper into how Python internally constructs objects and learn how to customize the process, then this tutorial is for you.
In this tutorial, you’ll:
- Understand Python’s internal instantiation process
- Customize object initialization using .__init__()
- Fine-tune object creation by overriding .__new__()
With this knowledge, you’ll be able to tweak the creation and initialization of objects in your custom Python classes, which will give you control over the instantiation process at a more advanced level.
To better understand the examples and concepts in this tutorial, you should be familiar with object-oriented programming and special methods in Python.
Free Bonus: Click here to get access to a free Python OOP Cheat Sheet that points you to the best tutorials, videos, and books to learn more about Object-Oriented Programming with Python.
Python’s Class Constructors and the Instantiation Process
Like many other programming languages, Python supports object-oriented programming. At the heart of Python’s object-oriented capabilities, you’ll find the class keyword, which allows you to define custom classes that can have attributes for storing data and methods for providing behaviors.
Once you have a class to work with, then you can start creating new instances or objects of that class, which is an efficient way to reuse functionality in your code.
Creating and initializing objects of a given class is a fundamental step in object-oriented programming. This step is often referred to as object construction or instantiation. The tool responsible for running this instantiation process is commonly known as a class constructor.
Getting to Know Python’s Class Constructors
In Python, to construct an object of a given class, you just need to call the class with appropriate arguments, as you would call any function:
In this example, you define SomeClass using the class keyword. This class is currently empty because it doesn’t have attributes or methods. Instead, the class’s body only contains a pass statement as a placeholder statement that does nothing.
Then you create a new instance of SomeClass by calling the class with a pair of parentheses. In this example, you don’t need to pass any argument in the call because your class doesn’t take arguments yet.
In Python, when you call a class as you did in the above example, you’re calling the class constructor, which creates, initializes, and returns a new object by triggering Python’s internal instantiation process.
A final point to note is that calling a class isn’t the same as calling an instance of a class. These are two different and unrelated topics. To make a class’s instance callable, you need to implement a .__call__() special method, which has nothing to do with Python’s instantiation process.
Understanding Python’s Instantiation Process
You trigger Python’s instantiation process whenever you call a Python class to create a new instance. This process runs through two separate steps, which you can describe as follows:
- Create a new instance of the target class
- Initialize the new instance with an appropriate initial state
To run the first step, Python classes have a special method called .__new__() , which is responsible for creating and returning a new empty object. Then another special method, .__init__() , takes the resulting object, along with the class constructor’s arguments.
The .__init__() method takes the new object as its first argument, self . Then it sets any required instance attribute to a valid state using the arguments that the class constructor passed to it.
In short, Python’s instantiation process starts with a call to the class constructor, which triggers the instance creator, .__new__() , to create a new empty object. The process continues with the instance initializer, .__init__() , which takes the constructor’s arguments to initialize the newly created object.
To explore how Python’s instantiation process works internally, consider the following example of a Point class that implements a custom version of both methods, .__new__() and .__init__() , for demonstration purposes:
Here’s a breakdown of what this code does:
Line 3 defines the Point class using the class keyword followed by the class name.
Line 4 defines the .__new__() method, which takes the class as its first argument. Note that using cls as the name of this argument is a strong convention in Python, just like using self to name the current instance is. The method also takes *args and **kwargs , which allow for passing an undefined number of initialization arguments to the underlying instance.
Line 5 prints a message when .__new__() runs the object creation step.
Line 6 creates a new Point instance by calling the parent class’s .__new__() method with cls as an argument. In this example, object is the parent class, and the call to super() gives you access to it. Then the instance is returned. This instance will be the first argument to .__init__() .
Line 8 defines .__init__() , which is responsible for the initialization step. This method takes a first argument called self , which holds a reference to the current instance. The method also takes two additional arguments, x and y . These arguments hold initial values for the instance attributes .x and .y . You need to pass suitable values for these arguments in the call to Point() , as you’ll learn in a moment.
Line 9 prints a message when .__init__() runs the object initialization step.
Lines 10 and 11 initialize .x and .y , respectively. To do this, they use the provided input arguments x and y .
Lines 13 and 14 implement the .__repr__() special method, which provides a proper string representation for your Point class.
With Point in place, you can uncover how the instantiation process works in practice. Save your code to a file called point.py and start your Python interpreter in a command-line window. Then run the following code:
Calling the Point() class constructor creates, initializes, and returns a new instance of the class. This instance is then assigned to the point variable.
In this example, the call to the constructor also lets you know the steps that Python internally runs to construct the instance. First, Python calls .__new__() and then .__init__() , resulting in a new and fully initialized instance of Point , as you confirmed at the end of the example.
To continue learning about class instantiation in Python, you can try running both steps manually:
In this example, you first call .__new__() on your Point class, passing the class itself as the first argument to the method. This call only runs the first step of the instantiation process, creating a new and empty object. Note that creating an instance this way bypasses the call to .__init__() .
Note: The code snippet above is intended to be a demonstrative example of how the instantiation process works internally. It’s not something that you would typically do in real code.
Once you have the new object, then you can initialize it by calling .__init__() with an appropriate set of arguments. After this call, your Point object is properly initialized, with all its attributes set up.
A subtle and important detail to note about .__new__() is that it can also return an instance of a class different from the class that implements the method itself. When that happens, Python doesn’t call .__init__() in the current class, because there’s no way to unambiguously know how to initialize an object of a different class.
Consider the following example, in which the .__new__() method of the B class returns an instance of the A class:
Because B.__new__() returns an instance of a different class, Python doesn’t run B.__init__() . To confirm this behavior, save the code into a file called ab_classes.py and then run the following code in an interactive Python session:
The call to the B() class constructor runs B.__new__() , which returns an instance of A instead of B . That’s why B.__init__() never runs. Note that b doesn’t have a .b_value attribute. In contrast, b does have an .a_value attribute with a value of 42 .
Now that you know the steps that Python internally takes to create instances of a given class, you’re ready to dig a little deeper into other characteristics of .__init__() , .__new__() , and the steps that they run.
Object Initialization With .__init__()
In Python, the .__init__() method is probably the most common special method that you’ll ever override in your custom classes. Almost all your classes will need a custom implementation of .__init__() . Overriding this method will allow you to initialize your objects properly.
The purpose of this initialization step is to leave your new objects in a valid state so that you can start using them right away in your code. In this section, you’ll learn the basics of writing your own .__init__() methods and how they can help you customize your classes.
Providing Custom Object Initializers
The most bare-bones implementation of .__init__() that you can write will just take care of assigning input arguments to matching instance attributes. For example, say you’re writing a Rectangle class that requires .width and .height attributes. In that case, you can do something like this:
As you learned before, .__init__() runs the second step of the object instantiation process in Python. Its first argument, self , holds the new instance that results from calling .__new__() . The rest of the arguments to .__init__() are normally used to initialize instance attributes. In the above example, you initialized the rectangle’s .width and .height using the width and height arguments to .__init__() .
It’s important to note that, without counting self , the arguments to .__init__() are the same ones that you passed in the call to the class constructor. So, in a way, the .__init__() signature defines the signature of the class constructor.
Additionally, keep in mind that .__init__() must not explicitly return anything different from None , or you’ll get a TypeError exception:
In this example, the .__init__() method attempts to return an integer number, which ends up raising a TypeError exception at run time.
The error message in the above example says that .__init__() should return None . However, you don’t need to return None explicitly, because methods and functions without an explicit return statement just return None implicitly in Python.
With the above implementation of .__init__() , you ensure that .width and .height get initialized to a valid state when you call the class constructor with appropriate arguments. That way, your rectangles will be ready for use right after the construction process finishes.
In .__init__() , you can also run any transformation over the input arguments to properly initialize the instance attributes. For example, if your users will use Rectangle directly, then you might want to validate the supplied width and height and make sure that they’re correct before initializing the corresponding attributes:
In this updated implementation of .__init__() , you make sure that the input width and height arguments are positive numbers before initializing the corresponding .width and .height attributes. If either validation fails, then you get a ValueError .
Note: A more Pythonic technique to tackle attribute validation is to turn attributes into properties. To learn more about properties, check out Python’s property(): Add Managed Attributes to Your Classes.
Now say that you’re using inheritance to create a custom class hierarchy and reuse some functionality in your code. If your subclasses provide a .__init__() method, then this method must explicitly call the base class’s .__init__() method with appropriate arguments to ensure the correct initialization of instances. To do this, you should use the built-in super() function like in the following example:
The first line in the .__init__() method of Employee calls super().__init__() with name and birth_date as arguments. This call ensures the initialization of .name and .birth_date in the parent class, Person . This technique allows you to extend the base class with new attributes and functionality.
To wrap up this section, you should know that the base implementation of .__init__() comes from the built-in object class. This implementation is automatically called when you don’t provide an explicit .__init__() method in your classes.
Building Flexible Object Initializers
You can make your objects’ initialization step flexible and versatile by tweaking the .__init__() method. To this end, one of the most popular techniques is to use optional arguments. This technique allows you to write classes in which the constructor accepts different sets of input arguments at instantiation time. Which arguments to use at a given time will depend on your specific needs and context.
As a quick example, check out the following Greeter class:
In this example, .__init__() takes a regular argument called name . It also takes an optional argument called formal , which defaults to False . Because formal has a default value, you can construct objects by relying on this value or by providing your own.
The class’s final behavior will depend on the value of formal . If this argument is False , then you’ll get an informal greeting when you call .greet() . Otherwise, you’ll get a more formal greeting.
To try Greeter out, go ahead and save the code into a greet.py file. Then open an interactive session in your working directory and run the following code:
In the first example, you create an informal_greeter object by passing a value to the name argument and relying on the default value of formal . You get an informal greeting on your screen when you call .greet() on the informal_greeter object.
In the second example, you use a name and a formal argument to instantiate Greeter . Because formal is True , the result of calling .greet() is a formal greeting.
Even though this is a toy example, it showcases how default argument values are a powerful Python feature that you can use to write flexible initializers for your classes. These initializers will allow you to instantiate your classes using different sets of arguments depending on your needs.
Okay! Now that you know the basics of .__init__() and the object initialization step, it’s time to change gears and start diving deeper into .__new__() and the object creation step.
Object Creation With .__new__()
When writing Python classes, you typically don’t need to provide your own implementation of the .__new__() special method. Most of the time, the base implementation from the built-in object class is sufficient to build an empty object of your current class.
However, there are a few interesting use cases for this method. For example, you can use .__new__() to create subclasses of immutable types, such as int , float , tuple , and str .
In the following sections, you’ll learn how to write custom implementations of .__new__() in your classes. To do this, you’ll code a few examples that’ll give you an idea of when you might need to override this method.
Providing Custom Object Creators
Typically, you’ll write a custom implementation of .__new__() only when you need to control the creation of a new instance at a low level. Now, if you need a custom implementation of this method, then you should follow a few steps:
- Create a new instance by calling super().__new__() with appropriate arguments.
- Customize the new instance according to your specific needs.
- Return the new instance to continue the instantiation process.
With these three succinct steps, you’ll be able to customize the instance creation step in the Python instantiation process. Here’s an example of how you can translate these steps into Python code:
This example provides a sort of template implementation of .__new__() . As usual, .__new__() takes the current class as an argument that’s typically called cls .
Note that you’re using *args and **kwargs to make the method more flexible and maintainable by accepting any number of arguments. You should always define .__new__() with *args and **kwargs , unless you have a good reason to follow a different pattern.
In the first line of .__new__() , you call the parent class’s .__new__() method to create a new instance and allocate memory for it. To access the parent class’s .__new__() method, you use the super() function. This chain of calls takes you up to object.__new__() , which is the base implementation of .__new__() for all Python classes.
Note: The built-in object class is the default base class of all Python classes.
The next step is to customize your newly created instance. You can do whatever you need to do to customize the instance at hand. Finally, in the third step, you need to return the new instance to continue the instantiation process with the initialization step.
It’s important to note that object.__new__() itself only accepts a single argument, the class to instantiate. If you call object.__new__() with more arguments, then you get a TypeError :
In this example, you hand over *args and **kwargs as additional arguments in the call to super().__new__() . The underlying object.__new__() accepts only the class as an argument, so you get a TypeError when you instantiate the class.
However, object.__new__() still accepts and passes over extra arguments to .__init__() if your class doesn’t override .__new__() , as in the following variation of SomeClass :
In this implementation of SomeClass , you don’t override .__new__() . The object creation is then delegated to object.__new__() , which now accepts value and passes it over to SomeClass.__init__() to finalize the instantiation. Now you can create new and fully initialized instances of SomeClass , just like some_obj in the example.
Cool! Now that you know the basics of writing your own implementations of .__new__() , you’re ready to dive into a few practical examples that feature some of the most common use cases of this method in Python programming.
Subclassing Immutable Built-in Types
To kick things off, you’ll start with a use case of .__new__() that consists of subclassing an immutable built-in type. As an example, say you need to write a Distance class as a subclass of Python’s float type. Your class will have an additional attribute to store the unit that’s used to measure the distance.
Here’s a first approach to this problem, using the .__init__() method:
When you subclass an immutable built-in data type, you get an error. Part of the problem is that the value is set during creation, and it’s too late to change it during initialization. Additionally, float.__new__() is called under the hood, and it doesn’t deal with extra arguments in the same way as object.__new__() . This is what raises the error in your example.
To work around this issue, you can initialize the object at creation time with .__new__() instead of overriding .__init__() . Here’s how you can do this in practice:
In this example, .__new__() runs the three steps that you learned in the previous section. First, the method creates a new instance of the current class, cls , by calling super().__new__() . This time, the call rolls back to float.__new__() , which creates a new instance and initializes it using value as an argument. Then the method customizes the new instance by adding a .unit attribute to it. Finally, the new instance gets returned.
Note: The Distance class in the example above doesn’t provide a proper unit conversion mechanism. This means that something like Distance(10, «km») + Distance(20, «miles») won’t attempt at converting units before adding the values. If you’re interested in converting units, then check out the Pint project on PyPI.
That’s it! Now your Distance class works as expected, allowing you to use an instance attribute for storing the unit in which you’re measuring the distance. Unlike the floating-point value stored in a given instance of Distance , the .unit attribute is mutable, so you can change its value any time you like. Finally, note how a call to the dir() function reveals that your class inherits features and methods from float .
Returning Instances of a Different Class
Returning an object of a different class is a requirement that can raise the need for a custom implementation of .__new__() . However, you should be careful because in this case, Python skips the initialization step entirely. So, you’ll have the responsibility of taking the newly created object into a valid state before using it in your code.
Check out the following example, in which the Pet class uses .__new__() to return instances of randomly selected classes:
In this example, Pet provides a .__new__() method that creates a new instance by randomly selecting a class from a list of existing classes.
Here’s how you can use this Pet class as a factory of pet objects:
Every time you instantiate Pet , you get a random object from a different class. This result is possible because there’s no restriction on the object that .__new__() can return. Using .__new__() in such a way transforms a class into a flexible and powerful factory of objects, not limited to instances of itself.
Finally, note how the .__init__() method of Pet never runs. That’s because Pet.__new__() always returns objects of a different class rather than of Pet itself.
Allowing Only a Single Instance in Your Classes
Sometimes you need to implement a class that allows the creation of a single instance only. This type of class is commonly known as a singleton class. In this situation, the .__new__() method comes in handy because it can help you restrict the number of instances that a given class can have.
Note: Most experienced Python developers would argue that you don’t need to implement the singleton design pattern in Python unless you already have a working class and need to add the pattern’s functionality on top of it.
The rest of the time, you can use a module-level constant to get the same singleton functionality without having to write a relatively complex class.
Here’s an example of coding a Singleton class with a .__new__() method that allows the creation of only one instance at a time. To do this, .__new__() checks the existence of previous instances cached on a class attribute:
The Singleton class in this example has a class attribute called ._instance that defaults to None and works as a cache. The .__new__() method checks if no previous instance exists by testing the condition cls._instance is None .
Note: In the example above, Singleton doesn’t provide an implementation of .__init__() . If you ever need a class like this with a .__init__() method, then keep in mind that this method will run every time you call the Singleton() constructor. This behavior can cause weird initialization effects and bugs.
If this condition is true, then the if code block creates a new instance of Singleton and stores it to cls._instance . Finally, the method returns the new or the existing instance to the caller.
Then you instantiate Singleton twice to try to construct two different objects, first and second . If you compare the identity of these objects with the is operator, then you’ll note that both objects are the same object. The names first and second just hold references to the same Singleton object.
Partially Emulating collections.namedtuple
As a final example of how to take advantage of .__new__() in your code, you can push your Python skills and write a factory function that partially emulates collections.namedtuple() . The namedtuple() function allows you to create subclasses of tuple with the additional feature of having named fields for accessing the items in the tuple.
The code below implements a named_tuple_factory() function that partially emulates this functionality by overriding the .__new__() method of a nested class called NamedTuple :
Here’s how this factory function works line by line:
Line 3 imports itemgetter() from the operators module. This function allows you to retrieve items using their index in the containing sequence.
Line 5 defines named_tuple_factory() . This function takes a first argument called type_name , which will hold the name of the tuple subclass that you want to create. The *fields argument allows you to pass an undefined number of field names as strings.
Line 6 defines a local variable to hold the number of named fields provided by the user.
Line 8 defines a nested class called NamedTuple , which inherits from the built-in tuple class.
Line 9 provides a .__slots__ class attribute. This attribute defines a tuple for holding instance attributes. This tuple saves memory by acting as a substitute for the instance’s dictionary, .__dict__ , which would otherwise play a similar role.
Line 11 implements .__new__() with cls as its first argument. This implementation also takes the *args argument to accept an undefined number of field values.
Lines 12 to 16 define a conditional statement that checks if the number of items to store in the final tuple differs from the number of named fields. If that’s the case, then the conditional raises a TypeError with an error message.
Line 17 sets the .__name__ attribute of the current class to the value provided by type_name .
Lines 18 and 19 define a for loop that turns every named field into a property that uses itemgetter() to return the item at the target index . The loop uses the built-in setattr() function to perform this action. Note that the built-in enumerate() function provides the appropriate index value.
Line 20 returns a new instance of the current class by calling super().__new__() as usual.
Lines 22 and 23 define a .__repr__() method for your tuple subclass.
Line 25 returns the newly created NamedTuple class.
To try your named_tuple_factory() out, fire up an interactive session in the directory containing the named_tuple.py file and run the following code:
In this code snippet, you create a new Point class by calling named_tuple_factory() . The first argument in this call represents the name that the resulting class object will use. The second and third arguments are the named fields available in the resulting class.
Then you create a Point object by calling the class constructor with appropriate values for the .x and .y fields. To access the value of each named field, you can use the dot notation. You can also use indices to retrieve the values because your class is a tuple subclass.
Because tuples are immutable data types in Python, you can’t assign new values to the point’s coordinates in place. If you try to do that, then you get an AttributeError .
Finally, calling dir() with your point instance as an argument reveals that your object inherits all the attributes and methods that regular tuples have in Python.
Conclusion
Now you know how Python class constructors allow you to instantiate classes, so you can create concrete and ready-to-use objects in your code. In Python, class constructors internally trigger the instantiation or construction process, which goes through instance creation and instance initialization. These steps are driven by the .__new__() and .__init__() special methods.
By learning about Python’s class constructors, the instantiation process, and the .__new__() and .__init__() methods, you can now manage how your custom classes construct new instances.
In this tutorial, you learned:
- How Python’s instantiation process works internally
- How your own .__init__() methods help you customize object initialization
- How overriding the .__new__() method allows for custom object creation
Now you’re ready to take advantage of this knowledge to fine-tune your class constructors and take full control over instance creation and initialization in your object-oriented programming adventure with Python.
Free Bonus: Click here to get access to a free Python OOP Cheat Sheet that points you to the best tutorials, videos, and books to learn more about Object-Oriented Programming with Python.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Using Python Class Constructors
Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

About Leodanis Pozo Ramos
Leodanis is an industrial engineer who loves Python and software development. He's a self-taught Python developer with 6+ years of experience. He's an avid technical writer with a growing number of articles published on Real Python and other sites.
Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:





Master Real-World Python Skills With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
Master Real-World Python Skills
With Unlimited Access to Real PythonJoin us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
What Do You Think?
What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.
Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal. Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!
Name already in use
PythonCources / lesson16.md
- Go to file T
- Go to line L
- Copy path
- Copy permalink
Copy raw contents
Copy raw contents
Урок 16. Множественное наследование. MRO. Magic methods.

Множественное наследование это возможность у класса потомка наследовать функционал не от одного, а от нескольких родителей. Благодаря этому мы можем создавать сложные структуры, сохраняя простой и легко-поддерживаемый код.
Во многих языках программирования нет множественного наследования, так что давайте разбираться как это вообще работает.
Например, у нас есть класс автомобиля:
Так же у нас есть класс для лодки:
Теперь, если нам нужно запрограммировать автомобиль-амфибию, который будет плавать в воде и ездить по земле, мы вместо написания нового класса, можем просто унаследовать от уже существующих, просто написав их через запятую:
Теперь наш класс имеет атрибуты и методы обоих родителей (их может быть сколько угодно)
Обратите внимание, что объект класса Amphibian, будет одновременно объектом класса Auto и Boat, то есть:
Миксин, он же примесь, это тип классов которые нужны, что бы добавлять к обычным классам какие-то методы или атрибуты, но эти классы не используются для создания объектов, только как примесь. (Нас ничего не останавливает создать объект этого класса, но задача в другом)
Представим, что мы программируем класс для автомобиля. Мы хотим, чтобы у нас была возможность слушать музыку в машине. Конечно, можно просто добавить метод play_music() в класс Car :
Но что если, у нас есть еще и телефон, радио или любой другой девайс, с которого мы будем слушать музыку. В таком случае, лучше вынести функционал проигрывания музыки в отдельный класс-миксин:
Мы можем «домешивать» этот класс в любой, где нужна функция проигрывания музыки:
Diamond problem. MRO

Итак, классы-наследники могут использовать родительские атрибуты и методы. Но что, если у нескольких родителей будут одинаковые атрибуты или методы? Какой метод в таком случае будет использовать наследник?
Рассмотрим классический пример:
Эта ситуация, так называемое ромбовидное наследование (diamond problem) решается в Python путем установления порядка разрешения методов.
В Python3 для определения порядка используется алгоритм поиска в ширину, то есть сначала интерпретатор будет искать метод hi в классе B, если его там нету — в классе С, потом A.
В Python второй версии используется алгоритм поиска в глубину, то есть в данном случае — сначала B, потом — А, потом С.
В Pytho3 можно посмотреть в каком порядке будут проинспектированы родительские классы при помощи метода класса mro():
MRO — Method resolution order
Что бы посмотреть в каком порядке питон будет искать атрибуты или методы у родителей, у любого класса можно вызывать метод mro :
Обратите внимание в конце всегда будет object если вы используете любой питон версии 3. Потому что вообще все отнаследовано от него, как я и говорил на прошлом занятии.
Если по какой-то причине вас не устраивает существующий порядок есть возможность вызвать метод ровно из того класса откуда вам надо, но это считается плохой практикой и лучше так не делать, а полностью поменять структуру.
Если вам необходимо использовать метод конкретного родителя, например hi() класса С, нужно напрямую вызвать его по имени класса, передав self в качестве аргумента:
Magic methods (Они же ногда называются дандер методы)
Что такое магические методы? Они всё в объектно-ориентированном Питоне. Это специальные методы, с помощью которых вы можете добавить в ваши классы «магию». Они всегда обрамлены двумя нижними подчеркиваниями, два вначале, два в конце (например, init или lt)
Это методы, которые отвечают за любые действия под «капотом». Те которые выполняются неявно, допустим, вам нужно сложить два объекта через + , нам нужен магический метод __add__ или мы хотим поменять поведение при удалении __del__ , поведение при переборе объекта в цикле __iter__ или __next__ итд.
Почти любое действие которое вам кажется выполняется само собой, скорее всего описано в меджик методе.
При создании (инициализации) объекта вызывается метод __init__ и помогает нам задать стартовые параметры для нашего класса
Такой способ куда предпочтительнее, чем задавать какое-то бесполезное значение вначале и сразу же его поменять (номер телефона или место на доске из прошлой домашки)
Когда я пишу x = SomeClass(), __init__ не самое первое, что вызывается. На самом деле, экземпляр объекта создаёт метод __new__ , а затем аргументы передаются в инициализатор. На другом конце жизненного цикла объекта находится метод __del__ .
Давайте подробнее рассмотрим эти три магических метода:
Это первый метод, который будет вызван при инициализации объекта. Он принимает в качестве параметров класс и потом любые другие аргументы, которые будут переданы в __init__ . __new__ используется весьма редко, но иногда бывает полезен, в частности, когда класс наследуется от неизменяемого (immutable) типа, такого как кортеж (tuple) или строка. Я не намерен очень детально останавливаться на __new__ , так как он не то чтобы очень часто нужен, но этот метод очень хорошо и детально описан в документации.
Инициализатор класса. Ему передаётся всё, с чем был вызван первоначальный конструктор (так, например, если мы вызываем x = SomeClass(10, ‘foo’), __init__ получит 10 и ‘foo’ в качестве аргументов. __init__ почти повсеместно используется при определении классов.
Если __new__ и __init__ образуют конструктор объекта, __del__ это его деструктор. Он не определяет поведение для выражения del x (поэтому этот код не эквивалентен x.__del__() ). Скорее, он определяет поведение объекта в то время, когда объект попадает в сборщик мусора. Это может быть довольно удобно для объектов, которые могут требовать дополнительных чисток во время удаления, таких как сокеты или файловые объекты. Однако, нужно быть осторожным, так как нет гарантии, что __del__ будет вызван, если объект продолжает жить, когда интерпретатор завершает работу. Поэтому __del__ не может служить заменой для хороших программистских практик (всегда завершать соединение, если закончил с ним работать и тому подобное). Фактически, из-за отсутствия гарантии вызова, __del__ не должен использоваться почти никогда; используйте его с осторожностью
Магические методы сравнения
В Питоне огромное количество магических методов, созданных для определения интуитивного сравнения между объектами используя операторы, а не плохо выглядящие методы (что лучше? a==b или a.is_equal(b) . Кроме того, они предоставляют способ переопределить поведение Питона по-умолчанию для сравнения объектов (по ссылке). Вот список этих методов и что они делают:
Определяет поведение оператора равенства, == .
Определяет поведение оператора неравенства, != .
Определяет поведение оператора меньше, < .
Определяет поведение оператора больше, > .
Определяет поведение оператора меньше или равно, <= .
Определяет поведение оператора больше или равно, >= .
Для примера рассмотрим класс, описывающий слово. Мы можем сравнивать слова лексиграфически (по алфавиту), что является дефолтным поведением при сравнении строк, но можем захотеть использовать при сравнении какой-нибудь другой критерий, такой как длина или количество слогов. В этом примере мы будем сравнивать по длине. Вот реализация:
Теперь мы можем создать два Word (при помощи Word(‘foo’) и Word(‘bar’)) и сравнить их по длине. Заметьте, что мы не определяли __eq__ и __ne__ , так как это приведёт к странному поведению (например, Word(‘foo’) == Word(‘bar’) будет расцениваться как истина). В этом нет смысла при тестировании на эквивалентность, основанную на длине, поэтому мы оставляем стандартную проверку на эквивалентность от str.
Обычные арифметические операторы
Теперь рассмотрим обычные бинарные операторы (и ещё пару функций): +, -, * и похожие. Они, по большей части, отлично сами себя описывают.
Целочисленное деление, оператор //.
Остаток от деления, оператор %.
Определяет поведение для встроенной функции divmod().
Возведение в степень, оператор **.
Двоичный сдвиг влево, оператор <<.
Двоичный сдвиг вправо, оператор >>.
Двоичное И, оператор &.
Двоичное ИЛИ, оператор |.
Двоичный xor, оператор ^.
Магические методы преобразования типов
Кроме того, в Питоне множество магических методов, предназначенных для определения поведения для встроенных функций преобразования типов, таких как float(). Вот они все:
Преобразование типа в int.
Преобразование типа в long.
Преобразование типа в float.
Преобразование типа в комплексное число.
Преобразование типа в восьмеричное число.
Преобразование типа в шестнадцатеричное число.
Преобразование типа к int, когда объект используется в срезах (выражения вида [start:stop:step]). Если вы определяете свой числовой тип, который может использоваться как индекс списка, вы должны определить index.
Вызывается при math.trunc(self). Должен вернуть своё значение, обрезанное до целочисленного типа (обычно long).
Метод для реализации арифметики с операндами разных типов. coerce должен вернуть None если преобразование типов невозможно. Если преобразование возможно, он должен вернуть пару (кортеж из 2-х элементов) из self и other, преобразованные к одному типу.
Представление своих классов
Часто бывает полезно представление класса в виде строки. В Питоне существует несколько методов, которые вы можете определить для настройки поведения встроенных функций при представлении вашего класса.
Определяет поведение функции str(), вызванной для экземпляра вашего класса.
Определяет поведение функции repr(), вызыванной для экземпляра вашего класса. Главное отличие от str() в целевой аудитории. repr() больше предназначен для машинно-ориентированного вывода (более того, это часто должен быть валидный код на Питоне), а str() предназначен для чтения людьми.
Определяет поведение функции unicode(), вызыванной для экземпляра вашего класса. unicode() похож на str(), но возвращает строку в юникоде. Будте осторожны: если клиент вызывает str() на экземпляре вашего класса, а вы определили только _ unicode_(), то это не будет работать. Постарайтесь всегда определять str() для случая, когда кто-то не имеет такой роскоши как юникод.
Определяет поведение, когда экземпляр вашего класса используется в форматировании строк нового стиля. Например, «Hello, !».format(a) приведёт к вызову a.format(«abc»). Это может быть полезно для определения ваших собственных числовых или строковых типов, которым вы можете захотеть предоставить какие-нибудь специальные опции форматирования.
Определяет поведение функции hash(), вызванной для экземпляра вашего класса. Метод должен возвращать целочисленное значение, которое будет использоваться для быстрого сравнения ключей в словарях. Заметьте, что в таком случае обычно нужно определять и eq тоже. Руководствуйтесь следующим правилом: a == b подразумевает hash(a) == hash(b).
__bool__(self) ( __nonzero__(self) в python < 3.0)
Определяет поведение функции bool(), вызванной для экземпляра вашего класса. Должна вернуть True или False, в зависимости от того, когда вы считаете экземпляр соответствующим True или False.
Определяет поведение функции dir(), вызванной на экземпляре вашего класса. Этот метод должен возвращать пользователю список атрибутов. Обычно, определение dir не требуется, но может быть жизненно важно для интерактивного использования вашего класса, если вы переопределили getattr или getattribute (с которыми вы встретитесь в следующей части), или каким-либо другим образом динамически создаёте атрибуты.
Определяет поведение функции sys.getsizeof(), вызыванной на экземпляре вашего класса. Метод должен вернуть размер вашего объекта в байтах. Он главным образом полезен для классов, определённых в расширениях на C, но всё-равно полезно о нём знать.
Протокол для определения неизменяемых контейнеров: чтобы создать неизменяемый контейнер, вы должны только определить __len__ и __getitem__ (подробнее о них дальше). Протокол изменяемого контейнера требует того же, что и неизменяемого контейнера, плюс __setitem__ и __delitem__ . И, наконец, если вы хотите, чтобы ваши объекты можно было перебирать итерацией, вы должны определить __iter__ , который возвращает итератор. Этот итератор должен соответствовать протоколу итератора, который требует методов __iter__ (возвращает самого себя) и __next__ .
Без дальнейшего промедления, вот магические методы, используемые контейнерами:
Возвращает количество элементов в контейнере. Часть протоколов для изменяемого и неизменяемого контейнеров.
Определяет поведение при доступе к элементу, используя синтаксис self[key] . Тоже относится и к протоколу изменяемых и к протоколу неизменяемых контейнеров. Должен выбрасывать соответствующие исключения: TypeError если неправильный тип ключа и KeyError если ключу не соответствует никакого значения.
__setitem__(self, key, value)
Определяет поведение при присваивании значения элементу, используя синтаксис self[nkey] = value . Часть протокола изменяемого контейнера. Опять же, вы должны выбрасывать KeyError и TypeError в соответсвующих случаях.
Определяет поведение при удалении элемента (то есть del self[key] ). Это часть только протокола для изменяемого контейнера. Вы должны выбрасывать соответствующее исключение, если ключ некорректен.
Должен вернуть итератор для контейнера. Итераторы возвращаются в множестве ситуаций, главным образом для встроенной функции iter() и в случае перебора элементов контейнера выражением for x in container: . Итераторы сами по себе объекты и они тоже должны определять метод __iter__ , который возвращает self .
__reversed__(self) Вызывается чтобы определить поведения для встроенной функции reversed() . Должен вернуть обратную версию последовательности. Реализуйте метод только если класс упорядоченный, как список или кортеж.
__contains__ предназначен для проверки принадлежности элемента с помощью in и not in . Вы спросите, почему же это не часть протокола последовательности? Потому что когда __contains__ не определён, Питон просто перебирает всю последовательность элемент за элементом и возвращает True если находит нужный.
Возвращает следующий объкт в последовательности или StopIteration если больше обхектов нет.
Как вы наверное уже знаете, в Питоне функции являются объектами первого класса. Это означает, что они могут быть переданы в функции или методы так же, как любые другие объекты. Это невероятно мощная особенность.
Специальный магический метод позволяет экземплярам вашего класса вести себя так, как будто они функции, тоесть вы сможете «вызывать» их, передавать их в функции, которые принимают функции в качестве аргументов и так далее. Это другая удобная особенность, которая делает программирование на Питоне таким приятным.
Позволяет любому экземпляру вашего класса быть вызванным как-будто он функция. Главным образом это означает, что x() означает то же, что и x.__call__() . Заметьте, __call__ принимает произвольное число аргументов; то есть, вы можете определить __call__ так же как любую другую функцию, принимающую столько аргументов, сколько вам нужно.
__call__ , в частности, может быть полезен в классах, чьи экземпляры часто изменяют своё состояние. «Вызвать» экземпляр может быть интуитивно понятным и элегантным способом изменить состояние объекта. Примером может быть класс, представляющий положение некоторого объекта на плоскости:
На самом деле меджик методов гораздо больше, и вы можете изучить их из документации если вам необходимо, но практически любое действие можно переопределить при помощи мейджик методов.