Considere el siguiente código de Python:

class a_class:
    defined_var = 1

    def a_function(self):
        self.defined_var = 2
        self.undefined_var = 3 

La posibilidad de asignar un valor a una variable (y luego declararlo implícitamente) que no he declarado al comienzo de la clase (como lo hago para undefined_var en el ejemplo anterior) me está creando varios problemas, ya que para grandes clases olvido lo que defino y lo que no.

Sé que esta pregunta puede sonar tonta. Solía desarrollar usando C / C ++ / Java durante mucho tiempo donde la definición de variables en una clase es obligatoria ...

Hay alguna manera de evitar esto? Quiero decir, me gustaría un comportamiento como en C / C ++ / Java donde obtengo un error si uso una variable indefinida.

3
the_candyman 29 sep. 2019 a las 21:33

3 respuestas

La mejor respuesta

Por defecto, las instancias de Python tienen un __dict__ - Un diccionario que almacena todos los atributos del objeto. Esto es lo que les permite almacenar atributos arbitrarios. Puede suprimir la creación de __dict__ definiendo __slots__. Esta es una variable de nivel de clase que enumera los nombres de todos los atributos que pueden tener las instancias de la clase. Si una clase tiene __slots__, intentar asignar a un atributo indefinido arrojará una excepción.

Ejemplo:

class MyClass:
    __slots__ = ['defined_var']

    def a_function(self):
        self.defined_var = 2

obj = MyClass()
obj.a_function()
obj.undefined_var = 3  # throws AttributeError
5
Aran-Fey 29 sep. 2019 a las 18:50

Solo agregue algunas notas aquí:

El creador de Python, Guido van Rossum, afirma que en realidad creó slots para un acceso más rápido a los atributos.

Del libro python fluido , __slots__ se usa para un acceso más rápido a los atributos y reduce el uso de memoria para no evitar que el usuario agregue más atributos.

Verifica este código:

class MyClass:
    __slots__ = ['defined_var', '__dict__']

obj = MyClass()
obj.undefined_var = 3  # works fine

El problema al usar __slots__. Si crea una subclase de esta clase, también necesita repetir el atributo __slot__ en ella.

class MyClass:
    __slots__ = ['defined_var']

class MySubClass(MyClass):
    # you need to redefine the __slots__ here too
    pass

obj = MyClass()
sub_obj = MySubClass()
sub_obj.undefined_var = 3  # works fine not excepting this
obj.undefined_var = 3  # throw exception

Entonces, si realmente necesita esto, anule el método setattr como sugirió @Olvin Right.

class MyClass:
    x = None 
    def __setattr__(self, key, value):
        if hasattr(self, key):
            object.__setattr__(self, key, value)
        else:
            raise AttributeError(f"\"{key}\" is not defined in \"{self.__class__.__name__}\"")


class MySubClass(MyClass):
    pass


obj = MyClass()
sub_obj = MySubClass()
sub_obj.x = 3  # works fine
obj.x = 3  # works fine
sub_obj.undefined_var = 3  # error
obj.undefined_var = 3  # error  

Si algún día quieres hacer parches de mono en tu objeto:

obj = MyClass()
obj.__dict__['z'] = 4
print(obj.z)  # print 4, we added a new attribute

Así que no se preocupe por evitar que el cliente agregue un nuevo atributo, es un mal hábito. y evitará que usted mismo controle su objeto y cambie su comportamiento, eliminando la flexibilidad que le brinda Python, ¿por qué querría eso?

1
Charif DZ 30 sep. 2019 a las 13:20

Puede anular object.__setattr__:

class a_class:
    defined_var = 1

    def a_function(self):
        self.defined_var = 2
        self.undefined_var = 3

    def __setattr__(self, key, value):
        if hasattr(self, key):
            object.__setattr__(self, key, value)
        else:
            raise AttributeError(f"\"{key}\" is not defined in \"{self.__class__.__name__}\"")

a = a_class()
print(a.defined_var)
a.a_function()  # <-- throws an error

P.S. defined_var es una variable de clase compartida por todas las instancias. Significa que todos los cambios afectarán a todas las instancias de clase (tal vez no sea lo que esperas). Más información aquí.

3
Olvin Roght 29 sep. 2019 a las 19:09
58158104