Инстанцирование в Python
Какой метод вызывается первым при этом вызове Foo? Большинство новичков, да и, возможно, немало опытных питонистов тут же ответят: «метод __init__». Но если внимательно приглядеться к сниппетам выше, вскоре станет понятно, что такой ответ неверен.
__init__ не возвращает никакого результата, а Foo(1, y=2), напротив, возвращает экземпляр класса. К тому же __init__ принимает self в качестве первого параметра, чего не происходит при вызове Foo(1, y=2). Создание экземпляра происходит немного сложнее, о чём мы и поговорим в этой статье.
Порядок создания объекта
Инстанцирование в Python состоит из нескольких стадий. Понимание каждого шага делает нас чуть ближе к пониманию языка в целом. Foo — это класс, но в Питоне классы это тоже объекты! Классы, функции, методы и экземпляры — всё это объекты, и всякий раз, когда вы ставите скобки после их имени, вы вызываете их метод __call__. Так что Foo(1, y=2) — это эквивалент Foo.__call__(1, y=2). Причём метод __call__ объявлен в классе объекта Foo. Какой же класс у объекта Foo?
Так что класс Foo — это экземпляр класса type и вызов метода __call__ последнего возвращает класс Foo. Теперь давайте разберём, что из себя представляет метод __call__ класса type. Ниже находятся его реализации на C в CPython и в PyPy. Если надоест их смотреть, прокручивайте чуть дальше, чтобы найти упрощённую версию:
CPython
static PyObject * type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) < PyObject *obj; if (type->tp_new == NULL) < PyErr_Format(PyExc_TypeError, "cannot create '%.100s' instances", type->tp_name); return NULL; > obj = type->tp_new(type, args, kwds); obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL); if (obj == NULL) return NULL; /* Ugly exception: when the call was type(something), don't call tp_init on the result. */ if (type == &PyType_Type && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 && (kwds == NULL || (PyDict_Check(kwds) && PyDict_Size(kwds) == 0))) return obj; /* If the returned object is not an instance of type, it won't be initialized. */ if (!PyType_IsSubtype(Py_TYPE(obj), type)) return obj; type = Py_TYPE(obj); if (type->tp_init != NULL) < int res = type->tp_init(obj, args, kwds); if (res < 0) < assert(PyErr_Occurred()); Py_DECREF(obj); obj = NULL; >else < assert(!PyErr_Occurred()); >> return obj; >
PyPy
def descr_call(self, space, __args__): promote(self) # invoke the __new__ of the type if not we_are_jitted(): # note that the annotator will figure out that self.w_new_function # can only be None if the newshortcut config option is not set w_newfunc = self.w_new_function else: # for the JIT it is better to take the slow path because normal lookup # is nicely optimized, but the self.w_new_function attribute is not # known to the JIT w_newfunc = None if w_newfunc is None: w_newtype, w_newdescr = self.lookup_where('__new__') if w_newdescr is None: # see test_crash_mro_without_object_1 raise oefmt(space.w_TypeError, "cannot create '%N' instances", self) w_newfunc = space.get(w_newdescr, self) if (space.config.objspace.std.newshortcut and not we_are_jitted() and isinstance(w_newtype, W_TypeObject)): self.w_new_function = w_newfunc w_newobject = space.call_obj_args(w_newfunc, self, __args__) call_init = space.isinstance_w(w_newobject, self) # maybe invoke the __init__ of the type if (call_init and not (space.is_w(self, space.w_type) and not __args__.keywords and len(__args__.arguments_w) == 1)): w_descr = space.lookup(w_newobject, '__init__') if w_descr is not None: # see test_crash_mro_without_object_2 w_result = space.get_and_call_args(w_descr, w_newobject, __args__) if not space.is_w(w_result, space.w_None): raise oefmt(space.w_TypeError, "__init__() should return None") return w_newobject
Если забыть про всевозможные проверки на ошибки, то коды выше примерно эквивалентны такому:
def __call__(obj_type, *args, **kwargs): obj = obj_type.__new__(*args, **kwargs) if obj is not None and issubclass(obj, obj_type): obj.__init__(*args, **kwargs) return obj
__new__ выделяет память под «пустой» объект и вызывает __init__, чтобы его инициализировать.
- Foo(*args, **kwargs) эквивалентно Foo.__call__(*args, **kwargs).
- Так как объект Foo — это экземпляр класса type, то вызов Foo.__call__(*args, **kwargs) эквивалентен type.__call__(Foo, *args, **kwargs).
- type.__call__(Foo, *args, **kwargs) вызывает метод type.__new__(Foo, *args, **kwargs), возвращающий obj.
- obj инициализируется при вызове obj.__init__(*args, **kwargs).
- Результат всего процесса — инициализированный obj.
Кастомизация
Теперь давайте переключим наше внимание на __new__. Этот метод выделяет память под объект и возвращает его. Вы вольны кастомизировать этот процесс множеством разных способов. Следует отметить, что, хотя __new__ и является статическим методом, вам не нужно объявлять его используя @staticmethod: интерпретатор обрабатывает __new__ как специальный случай.
Распространённый пример переопределения __new__ — создание Синглтона:
class Singleton(object): _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance
>>> s1 = Singleton() . s2 = Singleton() . s1 is s2 True
Обратите внимание, что __init__ будет вызываться каждый раз при вызове Singleton(), поэтому следует соблюдать осторожность.
Другой пример переопределения __new__ — реализация паттерна Борг («Borg»):
class Borg(object): _dict = None def __new__(cls, *args, **kwargs): obj = super().__new__(cls, *args, **kwargs) if cls._dict is None: cls._dict = obj.__dict__ else: obj.__dict__ = cls._dict return obj
>>> b1 = Borg() . b2 = Borg() . b1 is b2 False >>> b1.x = 8 . b2.x 8
Учтите, что хотя примеры выше и демонстрируют возможности переопределения __new__, это ещё не значит что его обязательно нужно использовать:
__new__ — одна из самых частых жертв злоупотреблений. То, что может быть сделано переопределением этого метода, чаще всего лучше достигается другими средствами. Тем не менее, когда это действительно необходимо, __new__ — крайне полезный и мощный инструмент.
— Арион Спрэг, Хорошо забытое старое в Python
Редко можно встретить проблему в Python, где лучшим решением было использование __new__. Но когда у вас есть молоток, каждая проблема начинает выглядеть как гвоздь, поэтому всегда предпочитайте использованию нового мощного инструмента использование наиболее подходящего.
Explicit return in __init__¶
__init__ is a special Python method that is automatically called when memory is allocated for a new object. The sole purpose of __init__ is to initialize the values of instance members for the new object. Using __init__ to return a value implies that a program is using __init__ to do something other than initialize the object. This logic should be moved to another instance method and called by the program later, after initialization.
Anti-pattern¶
The __init__ method of the Rectangle class below attempts to return the area of the rectangle within the __init__ method. This violates the rule of only using __init__ to initialize instance members.
class Rectangle: def __init__(self, width, height): self.width = width self.height = height self.area = width * height # causes "Explicit return in __init__" error return self.area
Best practices¶
Remove the return statement from the __init__ method¶
Remove the return statement in the __init__ method that is returning a value.
class Rectangle: def __init__(self, width, height): self.width = width self.height = height self.area = width * height # return statement removed from here
Move the program logic to another instance method¶
There is no reason why the Rectangle class MUST return the area immediately upon initialization. This program logic should be moved to a separate method of the Rectangle class. The program can call the method later, after the object has successfully initialized.
class Rectangle(object): def __init__(self, width, height): self.width = width self.height = height self._area = width * height @property # moved the logic for returning area to a separate method def area(self): return self._area
Note that the class must inherit from object now, since the property decorator only works for new style classes.