Instanciação Python

Digamos que você tenha uma classe Foo :


class Foo(object): def __init__(self, x, y=0): self.x = x self.y = y 

O que acontece quando você cria seu objeto?


 f = Foo(1, y=2) 

Qual método é chamado primeiro com esta chamada para Foo ? A maioria dos recém-chegados, e talvez alguns pythonists experientes, responderão imediatamente: "método __init__ ". Mas se você examinar atentamente os trechos acima, logo ficará claro que essa resposta está incorreta.


__init__ não retorna nenhum resultado e Foo (1, y = 2) , pelo contrário, retorna uma instância da classe . Além disso, __init__ se considera o primeiro parâmetro, o que não acontece quando se chama Foo (1, y = 2) . Criar uma instância é um pouco mais complicado, sobre o qual falaremos neste artigo.


Procedimento de criação de objeto


A instanciação do Python consiste em vários estágios. Compreender cada passo nos aproxima um pouco da compreensão do idioma como um todo. Foo é uma classe, mas em Python, classes também são objetos! Classes, funções, métodos e instâncias são todos objetos, e sempre que você coloca colchetes após o nome, chama o método __call__ . Então Foo (1, y = 2) é o equivalente a Foo .__ chame __ (1, y = 2) . Além disso, o método __call__ é declarado na classe do objeto Foo . Qual é a classe do objeto Foo ?


 >>> Foo.__class__ <class 'type'> 

Portanto, a classe Foo é uma instância da classe type e chamar o método __call__ do último retorna a classe Foo . Agora, vejamos qual é o método __call__ da classe type . Abaixo estão suas implementações em C no CPython e no PyPy. Se você se cansar de vê-los, role um pouco mais para encontrar uma versão simplificada:


Cpython


Link para a fonte .


 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


Link para a fonte .


 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 



Se você esquecer todos os tipos de verificação de erros, os códigos acima são aproximadamente equivalentes a isso:


 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__ aloca memória para um objeto "vazio" e chama __init__ para inicializá-lo.


Para resumir:


  1. Foo (* args, ** kwargs) é equivalente a Foo .__ chame __ (* args, ** kwargs) .
  2. Como o objeto Foo é uma instância da classe type , chamar Foo .__ chamar __ (* args, ** kwargs) é equivalente ao tipo .__ chamar __ (Foo, * args, ** kwargs) .
  3. type .__ chama __ (Foo, * args, ** kwargs) chama o tipo .__ new __ (Foo, * args, ** kwargs) , que retorna obj .
  4. obj é inicializado chamando obj .__ init __ (* args, ** kwargs) .
  5. O resultado de todo o processo é um objetivo inicializado.

Personalização


Agora vamos voltar nossa atenção para __novo__ . Este método aloca memória para o objeto e o retorna. Você é livre para personalizar esse processo de várias maneiras diferentes. Deve-se observar que, embora __new__ seja um método estático, você não precisa declará-lo usando @staticmethod : o intérprete trata __new__ como um caso especial.


Um exemplo comum de substituições __new__ é a criação de Singleton:


 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 

Então:


 >>> s1 = Singleton() ... s2 = Singleton() ... s1 is s2 True 

Observe que __init__ será chamado toda vez que Singleton () for chamado, portanto, deve-se tomar cuidado.


Outro exemplo de substituições __new__ é a implementação do padrão Borg ("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 

Então:


 >>> b1 = Borg() ... b2 = Borg() ... b1 is b2 False >>> b1.x = 8 ... b2.x 8 

Lembre-se de que, embora os exemplos acima demonstrem as possibilidades de substituir __new__ , isso não significa que deve ser usado:


__new__ é uma das vítimas mais frequentes de abuso. O que pode ser feito substituindo esse método é mais frequentemente alcançado por outros meios. No entanto, quando é realmente necessário, __new__ é uma ferramenta extremamente útil e poderosa.


- Arion Sprag, o velho esquecido em Python


Raramente encontramos um problema no Python, onde a melhor solução era usar o __new__ . Mas quando você tem um martelo, todo problema começa a parecer um prego; portanto, sempre prefira usar a ferramenta mais poderosa para usar a mais adequada .

Source: https://habr.com/ru/post/pt480022/


All Articles