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!
КАК СОЗДАТЬ ИНИЦИАЛИЗАТОР КЛАССА 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: концепции, принципы и примеры реализации
Программирование на Python допускает различные методологии, но в его основе лежит объектный подход, поэтому работать в стиле ООП на Python очень просто.

- Данные структурируются в виде объектов, каждый из которых имеет определенный тип, то есть принадлежит к какому-либо классу.
- Классы – результат формализации решаемой задачи, выделения главных ее аспектов.
- Внутри объекта инкапсулируется логика работы с относящейся к нему информацией.
- Объекты в программе взаимодействуют друг с другом, обмениваются запросами и ответами.
- При этом объекты одного типа сходным образом отвечают на одни и те же запросы.
- Объекты могут организовываться в более сложные структуры, например, включать другие объекты или наследовать от одного или нескольких объектов.
Если вам интересно, что думает об этой концепции сам ее создатель Алан Кэй, загляните сюда.
Язык Python – типичный представитель ООП-семейства, обладающий элегантной и мощной объектной моделью. В этом языке от объектов никуда не спрятаться (ведь даже числа являются ими), поэтому давайте разбираться, как это все реализовано.
Статьи по теме:
Основы ООП на Python для начинающих
Классы
Создавать классы в Python очень просто:
Классы-родители перечисляются в скобках через запятую:
С реализацией наследования разберемся чуть позже.
Свойства классов устанавливаются с помощью простого присваивания:
Методы объявляются как простые функции:
Обратите внимание на первый аргумент – self – общепринятое имя для ссылки на объект, в контексте которого вызывается метод. Этот параметр обязателен и отличает метод класса от обычной функции.
Все пользовательские атрибуты сохраняются в атрибуте __dict__ , который является словарем.
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста»
Экземпляры классов
Инстанцировать класс в Python тоже очень просто:
Можно создавать разные инстансы одного класса с заранее заданными параметрами с помощью инициализатора (специальный метод __init__ ). Для примера возьмем класс Point (точка пространства), объекты которого должны иметь определенные координаты:
Подробнее о других специальных методах жизненного цикла объектов поговорим чуть ниже.
Динамическое изменение
Можно обойтись даже без определения атрибутов и методов:
Кажется, этот класс совершенно бесполезен? Отнюдь. Классы в Python могут динамически изменяться после определения:
Статические и классовые методы
Для создания статических методов в Python предназначен декоратор @staticmethod . У них нет обязательных параметров-ссылок вроде self . Доступ к таким методам можно получить как из экземпляра класса, так и из самого класса:
Еще есть так называемые методы классов. Они аналогичны методам экземпляров, но выполняются не в контексте объекта, а в контексте самого класса (классы – это тоже объекты). Такие методы создаются с помощью декоратора @classmethod и требуют обязательную ссылку на класс ( cls ).
Статические и классовые методы доступны без инстанцирования.
Специальные методы
Жизненный цикл объекта
С инициализатором объектов __init__ вы уже знакомы. Кроме него есть еще и метод __new__ , который непосредственно создает новый экземпляр класса. Первым параметром он принимает ссылку на сам класс:
Это обсуждение на stackoverflow поможет лучше разобраться с инстанцированием классов.
Метод __new__ может быть очень полезен для решения ряда задач, например, создания иммутабельных объектов или реализации паттерна Синглтон:
В Python вы можете поучаствовать не только в создании объекта, но и в его удалении. Специально для этого предназначен метод-деструктор __del__ .
На практике деструктор используется редко, в основном для тех ресурсов, которые требуют явного освобождения памяти при удалении объекта. Не следует совершать в нем сложные вычисления.
Объект как функция
Объект класса может имитировать стандартную функцию, то есть при желании его можно «вызвать» с параметрами. За эту возможность отвечает специальный метод __call__ :
Имитация контейнеров
Вы знакомы с функцией len() , которая умеет вычислять длину списков значений?
Но для объектов вашего пользовательского класса это не пройдет:
Этот код выдаст ошибку object of type ‘Collection’ has no len() . Интерпретатор просто не понимает, как ему посчитать длину collection.
Решить эту проблему поможет специальный метод __len__ :
Можно работать с объектом как с коллекцией значений, определив для него интерфейс классического списка с помощью специальных методов:
- __getItem__ – реализация синтаксиса obj[key] , получение значения по ключу;
- __setItem__ – установка значения для ключа;
- __delItem__ – удаление значения;
- __contains__ – проверка наличия значения.
Имитация числовых типов
Ваши объекты могут участвовать в математических операциях, если у них определены специальные методы. Например, __mul__ позволяет умножать объект на число по определенной программистом логике:
Другие специальные методы
В Python существует огромное количество специальных методов, расширяющих возможности пользовательских классов. Например, можно определить вид объекта на печати, его «официальное» строковое представление или поведение при сравнениях. Узнать о них подробнее вы можете в официальной документации языка.
Эти методы могут эмулировать поведение встроенных классов, но при этом они необязательно существуют у самих встроенных классов. Например, у объектов int при сложении не вызывается метод __add__ . Таким образом, их нельзя переопределить.
Принципы ООП на Python
Рассмотрим «большую тройку» объектно-ориентированной концепции: инкапсуляцию, полиморфизм и наследование.
Инкапсуляция
Все объекты в Python инкапсулируют внутри себя данные и методы работы с ними, предоставляя публичные интерфейсы для взаимодействия.
Атрибут может быть объявлен приватным (внутренним) с помощью нижнего подчеркивания перед именем, но настоящего скрытия на самом деле не происходит – все на уровне соглашений.
Если поставить перед именем атрибута два подчеркивания, к нему нельзя будет обратиться напрямую. Но все равно остается обходной путь:
Специальные свойства и методы класса, некоторые из которых вам уже знакомы, имеют двойные подчеркивания до и после имени.
Кроме прямого доступа к атрибутам ( obj.attrName ), могут быть использованы специальные методы доступа (геттеры, сеттеры и деструкторы):
Такой подход очень удобен, если получение или установка значения атрибута требует сложной логики.
Вместо того чтобы вручную создавать геттеры и сеттеры для каждого атрибута, можно перегрузить встроенные методы __getattr__ , __setattr__ и __delattr__ . Например, так можно перехватить обращение к свойствам и методам, которых в объекте не существует:
__getattribute__ перехватывает все обращения (в том числе и к существующим атрибутам):
Таким образом, можно организовать динамический доступ к методам и свойствам объекта, как действуют, например, RPC-системы.
Наследование
Язык программирования Python реализует как стандартное одиночное наследование:
так и множественное:

Используя множественное наследования можно создавать классы-миксины (примеси), представляющие собой определенную особенность поведения. Такой микси можно «примешать» к любому классу.
Ассоциация
Кроме наследования, существует и другой способ организации межклассового взаимодействия – ассоциация (агрегация или композиция), при которой один класс является полем другого.
Ассоциированные объекты могут циклически ссылаться друг на друга, что ломает стандартный механизм сборки мусора. Избежать подобных проблем при ассоциации помогают слабые ссылки (модуль weakref ).
Полиморфизм
Концепция полиморфизма – важная часть ООП на Python. Все методы в языке изначально виртуальные. Это значит, что дочерние классы могут их переопределять и решать одну и ту же задачу разными путями, а конкретная реализация будет выбрана только во время исполнения программы. Такие классы называют полиморфными.
Впрочем, можно получить и доступ к методам класса-предка либо по прямому обращению, либо с помощью функции super :
Одинаковый интерфейс с разной реализацией могут иметь и классы, не связанные родственными узами. В следующем примере код может одинаково удобно работать с классами English и French , так как они обладают одинаковым интерфейсом:
Это возможно благодаря утиной типизации.
Множественная диспетчеризация
Виртуальные методы обеспечивают одиночную диспетчеризацию, при которой выбор конкретного метода зависит от объекта, в контексте которого произошел вызов. Мультиметоды позволяют выбирать нужную функциональность в зависимости от количества, типов или значений аргументов.
Программирование на Python не поддерживает мультиметоды из коробки, поэтому для их реализации необходимо подключать сторонние Python библиотеки, например, multimethods.py.
Подробнее о множественной диспетчеризации читайте в этой статье из серии «Очаровательный Python».
Порядок разрешения доступа к атрибутам
Складывается достаточно интересная картина: у одного объекта может быть несколько родительских классов, а также специальные методы вроде __getattribute__ , которые перехватывают запросы к атрибутам.
Каким же образом интерпретатор разрешает сложные запросы к свойствам и методам? Рассмотрим последовательность поиска на примере запроса obj.field :
- Вызов obj.__getattribute__(‘field’) , если он определен. При установке или удалении атрибута проверяется соответственно наличие __setattr__ или __delattr__ .
- Поиск в obj.__dict__ (пользовательские атрибуты).
- Поиск в object.__class__.__slots__ .
- Рекурсивный поиск в поле __dict__ всех родительских классов. Если класс имеет несколько предков, порядок проверки соответствует порядку их перечисления в определении.
- Если определен метод __getattr__ , то происходит вызов obj.__getattr__(‘field’)
- Выбрасывается исключение несуществующего атрибута – AttributeError .
Наконец, когда атрибут нашелся, проверяется наличие метода __get__ (при установке – __set__ , при удалении – __delete__ ).
Все эти проверки совершаются только для пользовательских атрибутов.
Метаклассы
Метаклассы – это классы, инстансы которых тоже являются классами.
Узнайте больше о метаклассах в Python из нашего материала.
ООП на Python
Подведем краткий итог всему вышесказанному и выделим основные особенности реализации ООП на Python:
- Классы в Python – это тоже объекты.
- Допустимо динамическое изменение и добавление атрибутов классов.
- Жизненным циклом объекта можно управлять.
- Многие операторы могут быть перезагружены.
- Многие методы встроенных объектов можно эмулировать.
- Для скрытия внутренних данных используются синтаксические соглашения.
- Поддерживается наследование.
- Полиморфизм обеспечивается виртуальностью всех методов.
- Доступно метапрограммирование.
Хотите лучше разобраться в объектно-ориентированном программировании? Смотрите понятный видеокурс по основам ООП.
Объектно-ориентированное программирование. Специальные методы.
Декоратор @staticmethod определяет обычную функцию (статический метод) в пространстве имён класса. У него нет обязательного параметра-ссылки self. Может быть полезно для вспомогательных функций, чтобы не мусорить пространство имён модуля. Доступ к таким методам можно получить как из экземпляра класса, так и из самого класса:
Декоратор @classmethod создаёт метод класса и требует обязательную ссылку на класс (cls). Поэтому объект класса явно передаётся через первый параметр как это с параметром self происходит для обычных методов. Также как и для self, переданный cls может отличаться от класса, в котором определён класс-метод (может быть потомок). Часто используется для создания альтернативных конструкторов.
Давайте взглянем на пример кода, в котором одновременно показаны она декоратора, это может помочь понять основные принципы:
Важно понимать, что ни classmethod ни staticmethod НЕ являются функциями от конкретного объекта класса и соответственно не принимают self. Подчеркнем еще раз их различия: — classmethod принимает cls как первый параметр, тогда как staticmethod в специальных аргументах не нуждается — classmethod может получать доступ или менять состояние класса, в то время как staticmethod нет — staticmethod в целом вообще ничего не знают про класс. Это просто функция над аргументами, объявленная внутри класса.
Специальные методы (магические) вида _ < param > _
В Python существует огромное количество специальных методов, расширяющих возможности пользовательских классов. Например, можно определить вид объекта на печати, его "официальное" строковое представление или поведение при сравнениях.
Эти методы могут эмулировать поведение встроенных классов, но при этом они необязательно существуют у самих встроенных классов. Например, у объектов int при сложении не вызывается метод add. Таким образом, их нельзя переопределить.
Давайте для примера переопределим стандартную операцию сложения. Рассмотрим класс Vector, используемый для представления радиус-векторов на координатной плоскости, и определим в нем поля-координаты: x и y. Также очень хотелось бы определить для векторов операцию +, чтобы их можно было складывать столь же удобно, как и числа или строки.
Для этого необходимо перегрузить операцию +: определить функцию, которая будет использоваться, если операция + будет вызвана для объекта класса Vector. Для этого нужно определить метод add класса Vector, у которого два параметра: неявная ссылка self на экземпляр класса, для которого она будет вызвана (это левый операнд операции +) и явная ссылка other на правый операнд:
Теперь при вызове оператора A + B Питон вызовет метод A.add(B), то есть вызовет указанный метод, где self = A, other = B.
Аналогично можно определить и оставшиеся операции. Полезной для переопределения является операция <. Она должна возвращать логическое значение True, если левый операнд меньше правого или False в противном случае (также в том случае, если объекты равны). Для переопределения этого операнда нужно определить метод lt (less than):
В этом примере оператор вернет True, если у левого операнда поле x меньше, чем у правого операнда, а также если поля x у них равны, а поле y меньше у левого операнда.
После определения оператора <, появляется возможность упорядочивать объекты, используя этот оператор. Теперь можно сортировать списки объектов при помощи метода sort() или функции sorted, при этом будет использоваться именно определенный оператор сравнения <.
Список основных перегружаемых операторов
| Метод | Использование |
|---|---|
| Операторы сравнения | |
| __lt__(self, other) | x < y |
| __le__(self, other) | x <= y |
| __eq__(self, other) | x == y |
| __ne__(self, other) | x != y |
| __gt__(self, other) | x > y |
| __ge__(self, other) | x >= y |
| Арифметические операторы | |
| Сложение | |
| __add__(self, other) | x + y |
| __radd__(self, other) | y + x |
| __iadd__(self, other) | x += y |
| Вычитание | |
| __sub__(self, other) | x — y |
| __rsub__(self, other) | y — x |
| __isub__(self, other) | x -= y |
| Умножение | |
| __mul__(self, other) | x * y |
| __rmul__(self, other) | y * x |
| __imul__(self, other) | x *= y |
| Математическое умножение (например векторное) | |
| __matmul__(self, other) | x @ y |
| __rmatmul__(self, other) | y @ x |
| __imatmul__(self, other) | x @= y |
| Деление | |
| __truediv__(self, other) | x / y |
| __rtruediv__(self, other) | y / x |
| __itruediv__(self, other) | x /= y |
| Целочисленное деление | |
| __floordiv__(self, other) | x // y |
| __rfloordiv__(self, other) | y // x |
| __ifloordiv__(self, other) | x //= y |
| __divmod__(self, other) | divmod(x, y) |
| Остаток | |
| __mod__(self, other) | x % y |
| __rmod__(self, other) | y % x |
| __imod__(self, other) | x %= y |
| Возведение в степень | |
| __pow__(self, other) | x ** y |
| __rpow__(self, other) | y ** x |
| __ipow__(self, other) | x **= y |
| Отрицание, модуль | |
| __pos__(self) | +x |
| __neg__(self) | -x |
| __abs__(self) | abs(x) |
| __len__(self) | len(x) |
| Преобразование к стандартным типам | |
| __int__(self) | int(x) |
| __float__(self) | float(x) |
| __complex__(self) | complex(x) |
| __str__(self) | str(x) |
| __round__(self, digits = 0) | round(x, digits) |
| Блок with | |
| __enter__(self) | |
| __exit__(self) | |
Задачи:
Задача 1:
Реализуйте свой класс Complex для комплексных чисел, аналогично встроенной реализации complex: