lenguaje - python download



Anulando__setattr__ en tiempo de ejecución (2)

Desafortunadamente, no hay manera de "anular, después de init" los métodos especiales de python; como un efecto secundario de cómo funciona esa búsqueda. El quid del problema es que Python no mira la instancia; excepto para obtener su clase; antes de que comience a buscar el método especial; por lo tanto, no hay manera de hacer que el estado del objeto afecte a qué método se busca.

Si no le gusta el comportamiento especial en __init__ , puede refactorizar su código para poner el conocimiento especial en __setattr__ en __setattr__ lugar. Algo como:

class Foo(object):
    __initialized = False
    def __init__(self, a, b):
        try:
            self.a = a
            self.b = b
            # ...
        finally:
            self.__initialized = True

    def __setattr__(self, attr, value):
        if self.__initialzed:
            print(self.b)
        super(Foo, self).__setattr__(attr, value)

Edición: en realidad, hay una manera de cambiar el método especial que se busca, siempre que cambie su clase después de que se haya inicializado. Este enfoque lo enviará lejos en las malezas de las metaclases, así que sin más explicaciones, así es como se ve:

class AssignableSetattr(type):
    def __new__(mcls, name, bases, attrs):
        def __setattr__(self, attr, value):
            object.__setattr__(self, attr, value)

        init_attrs = dict(attrs)
        init_attrs['__setattr__'] = __setattr__

        init_cls = super(AssignableSetattr, mcls).__new__(mcls, name, bases, init_attrs)

        real_cls = super(AssignableSetattr, mcls).__new__(mcls, name, (init_cls,), attrs)
        init_cls.__real_cls = real_cls

        return init_cls

    def __call__(cls, *args, **kwargs):
        self = super(AssignableSetattr, cls).__call__(*args, **kwargs)
        print "Created", self
        real_cls = cls.__real_cls
        self.__class__ = real_cls
        return self


class Foo(object):
    __metaclass__ = AssignableSetattr

    def __init__(self, a, b):
        self.a = a
        self.b = b
        for key, value in process(a).items():
            setattr(self, key, value)

    def __setattr__(self, attr, value):
        frob(self.b)
        super(Foo, self).__setattr__(attr, value)


def process(a):
    print "processing"
    return {'c': 3 * a}


def frob(x):
    print "frobbing", x


myfoo = Foo(1, 2)
myfoo.d = myfoo.c + 1

https://ffff65535.com

Estoy intentando anular el método __setattr__ de una clase de Python, ya que quiero llamar a otra función cada vez que un atributo de instancia cambie su valor. Sin embargo, no quiero este comportamiento en el método __init__ , porque durante esta inicialización establezco algunos atributos que se utilizarán más adelante:

Hasta ahora tengo esta solución, sin anular __setattr__ en tiempo de ejecución:

class Foo(object):
    def __init__(self, a, host):
        object.__setattr__(self, 'a', a)
        object.__setattr__(self, 'b', b)
        result = self.process(a)
        for key, value in result.items():
            object.__setattr__(self, key, value)

    def __setattr__(self, name, value):
        print(self.b) # Call to a function using self.b
        object.__setattr__(self, name, value)

Sin embargo, me gustaría evitar estos object.__setattr__(...) y anular __setattr__ al final del método __init__ :

class Foo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        result = self.process(a)
        for key, value in result.items():
            setattr(self, key, value)
        # override self.__setattr__ here

    def aux(self, name, value):
        print(self.b)
        object.__setattr__(self, name, value)

He intentado con self.__dict__['__setitem__'] = self.aux y object.__setitem__['__setitem__'] = self.aux , pero ninguno de estos intentos tiene efecto. He leído esta sección de la referencia del modelo de datos , pero parece que la asignación del propio __setattr__ es un poco difícil.

¿Cómo podría ser posible anular __setattr__ al final de __init__ , o al menos tener una solución __setattr__ donde __setattr__ se llama de la forma normal solo en el constructor?


La respuesta de @ SingleNegationElimination es excelente, pero no puede funcionar con herencia, ya que la tienda __mro__ la clase infantil es la clase original de la súper clase. Inspirado por su respuesta, con poco cambio,

La idea es simple, cambie __setattr__ antes de __init__ , y __init__ después de que __init__ finalizado.

class CleanSetAttrMeta(type):
    def __call__(cls, *args, **kwargs):
        real_setattr = cls.__setattr__
        cls.__setattr__ = object.__setattr__
        self = super(CleanSetAttrMeta, cls).__call__(*args, **kwargs)
        cls.__setattr__ = real_setattr
        return self


class Foo(object):
    __metaclass__ = CleanSetAttrMeta

    def __init__(self):
        super(Foo, self).__init__()
        self.a = 1
        self.b = 2

    def __setattr__(self, key, value):
        print 'after __init__', self.b
        super(Foo, self).__setattr__(key, value)


class Bar(Foo):
    def __init__(self):
        super(Bar, self).__init__()
        self.c = 3

>>> f = Foo()
>>> f.a = 10
after __init__ 2
>>>
>>> b = Bar()
>>> b.c = 30
after __init__ 2




python