2011-05-19 23 views
31

Tengo una subclase y la quiero no incluye un atributo de clase que está presente en la clase base.Python: eliminar un atributo de clase en una subclase

yo probamos este, pero no funciona:

>>> class A(object): 
...  x = 5 
>>> class B(A): 
...  del x 
Traceback (most recent call last): 
    File "<pyshell#1>", line 1, in <module> 
    class B(A): 
    File "<pyshell#1>", line 2, in B 
    del x 
NameError: name 'x' is not defined 

¿Cómo puedo hacer esto?

+14

Esto rompe [LSP] (http://en.wikipedia.org/wiki/Liskov_substitution_principle). ¿Por qué quieres hacer esto? –

+1

La variable de clase 'x' de' B' que desea eliminar está realmente almacenada en 'A .__ dict__', por lo que si logra eliminarla, también eliminará' A.x'. Por lo tanto, lo más cerca que puede venir es _hiding_ 'A.x' al darle' B' una variable de clase 'x' también, como @Keith sugiere en su respuesta. –

+1

@JBernardo: No hay mucha conexión entre LSP y el tipado estático. LSP es un principio sólido porque hace que las jerarquías de clase se comporten de una manera predecible y coherente. Esto se aplica igualmente a los lenguajes tipados de forma estática o dinámica, independientemente de si proporcionan los medios para violar el LSP. –

Respuesta

7

Piensa detenidamente acerca de por qué quieres hacer esto; probablemente no. Considere no hacer que B herede de A.

La idea de subclases es especializar un objeto. En particular, los niños de una clase deben ser instancias válidas de la clase padre:

>>> class foo(dict): pass 
>>> isinstance(foo(), dict) 
... True 

Si implementa este comportamiento (por ejemplo, con x = property(lambda: AttributeError)), que está rompiendo el concepto de subclases, y esto es malo.

+18

En general, odio las respuestas que no se reducen a "No hagas esto increíble e interesante, porque soy un adulto responsable y ... !" Esta no es una excepción Si bien preservar [la sustituibilidad de Liskov] (https://en.wikipedia.org/wiki/Liskov_substitution_principle) es vital para un desarrollo sano, todas las reglas estaban destinadas a romperse. Todos somos adultos, aquí. ** Gracias. ** –

+0

Generalmente estoy de acuerdo con @CecilCurry, porque encontré respuestas similares a mis otras preguntas antes, y las odio. Sin embargo, en este caso particular, diría que eliminar un atributo principal no es asombroso e interesante en primer lugar, por lo que un recordatorio amable de [LSP] (https://en.wikipedia.org/wiki/Liskov_substitution_principle) no es una tontería.Terminé votando tanto esta respuesta como el comentario de CecilCurry. – RayLuo

+0

Creo que las respuestas que apuntan a los principios de programación ampliamente aceptados son muy beneficiosas, porque estoy aprendiendo. También sé que cada regla tiene algún tipo de flexibilidad, así que tómelas como pautas. – mehmet

16

No es necesario que lo elimine. Simplemente anularlo.

class B(A): 
    x = None 

o simplemente no lo referencia.

¿O considerar un diseño diferente (atributo de instancia?).

+1

+1 porque responde la pregunta Y sugiere un cambio de diseño. – FlipMcF

+3

** Esto no responde a la pregunta en absoluto. ** Establecer un atributo a 'Ninguno' no _ elimina_ ese atributo, que debería ser bastante obvio. Eliminar atributos es lo que 'del' y' delattr() 'hacen. La [respuesta] sucinta [Bhushan] (https://stackoverflow.com/users/1594534/bhushan) (https://stackoverflow.com/a/15920132/2809027) parece ser la única respuesta real a esta pregunta. –

+0

@CecilCurry la pregunta más profunda es por qué el OP piensa que necesita eliminarla? De todos modos, es un mal diseño. – Keith

6

Quizás podría establecer x como property y aumentar AttributeError cada vez que alguien intente acceder a él.

>>> class C: 
     x = 5 

>>> class D(C): 
     def foo(self): 
      raise AttributeError 
     x = property(foo) 

>>> d = D() 
>>> print(d.x) 
File "<pyshell#17>", line 3, in foo 
raise AttributeError 
AttributeError 
+0

'AttributeError' sería más consistente. –

+0

Tienes razón. Editado – JBernardo

+0

Esta es la * única * solución que realmente funciona en este momento. Excepto, por supuesto, que "elimina" atributos solo en las subclases * instancias *, y no en las subclases mismas. – drdaeman

5

También tuve el mismo problema, y ​​pensé que tenía una razón válida para eliminar el atributo de clase en la subclase: mi superclase (llámala A) tenía una propiedad de solo lectura que proporcionaba el valor del atributo, pero en mi subclase (llámelo B), el atributo era una variable de instancia de lectura/escritura. Descubrí que Python llamaba a la función de propiedad aunque pensé que la variable de instancia debería haberla anulado. Podría haber hecho una función getter separada para usarla para acceder a la propiedad subyacente, pero eso parecía un desorden innecesario y poco elegante del espacio de nombres de la interfaz (como si eso realmente importara).

Como resultado, la respuesta fue crear una nueva superclase abstracta (llámala S) con los atributos comunes originales de A, y tener A y B derivan de S. Dado que Python tiene tipado de pato, en realidad no lo hace Si B no extiende A, todavía puedo usarlos en los mismos lugares, ya que implícitamente implementan la misma interfaz.

32

Puede usar delattr(class, field_name) para eliminarlo de la definición de clase.

+3

No estoy creando subclases. Estoy eliminando una configuración de una clase de configuración en mi prueba para verificar el correcto manejo de errores, ¡así que esto es exactamente lo que necesito! – sage

+0

Sería útil tener un ejemplo completamente codificado. ¿Esto puede ser completado? No estoy seguro de a dónde va esto en el código. – broccoli2000

0

tratando de hacer esto es probablemente una mala idea, pero ...

No parece ser hacer esto a través de la herencia "adecuada" debido a la forma en que mira hacia arriba B.x funciona por defecto. Al obtener B.x, el x se busca por primera vez en B y si no se encuentra allí se busca en A, pero por otro lado al establecer o eliminar B.x se buscará solamente B.Así, por ejemplo

>>> class A: 
>>>  x = 5 

>>> class B(A): 
>>> pass 

>>> B.x 
5 

>>> del B.x 
Traceback (most recent call last): 
File "<stdin>", line 1, in <module> 
AttributeError: class B has no attribute 'x' 

>>> B.x = 6 
>>> B.x 
6 

>>> del B.x 
>>> B.x 
5 

Aquí vemos que primero no parece ser capaz de eliminar B.x ya que no existe (A.x existe y es lo que se sirvió al evaluar B.x). Sin embargo mediante el establecimiento de B.x a 6 el B.x existirá, puede ser recuperada por B.x y eliminados por del B.x por el que deja de existir así que después de que de nuevo A.x se servirá como respuesta a B.x.

Lo que podría hacer en el otro lado es el uso de metaclases hacer B.x aumento AttributeError:

class NoX(type): 
    @property 
    def x(self): 
     raise AttributeError("We don't like X") 

class A(object): 
    x = [42] 

class B(A, metaclass=NoX): 
    pass 

print(A.x) 
print(B.x) 

ahora de los puristas del curso puede gritar que esto rompe el LSP, pero no es tan simple. Todo se reduce a si considera que ha creado un subtipo al hacer esto. Los métodos issubclass y isinstance dicen que sí, pero LSP dice que no (y muchos programadores suponen que "sí" ya que hereda de A).

El LSP significa que si B es un subtipo de A entonces podríamos utilizar B siempre podríamos utilizar A, pero ya que no podemos hacer esto mientras se hace este constructo podríamos concluir que B en realidad no es un subtipo de A y, por lo tanto, LSP no se infringe.

2

Ninguna de las respuestas me había funcionado.

Por ejemplo delattr(SubClass, "attrname") (o su equivalente exacto, del SubClass.attrname) no "ocultará" un método principal, porque no es así como funciona la resolución del método. Fallaría con AttributeError('attrname',), ya que la subclase no tiene attrname. Y, por supuesto, reemplazar el atributo con None en realidad no lo elimina.

Vamos a considerar esta clase de base:

class Spam(object): 
    # Also try with `expect = True` and with a `@property` decorator 
    def expect(self): 
     return "This is pretty much expected" 

Sé que sólo dos únicas formas de subclase, ocultando el atributo expect:

  1. El uso de una clase de descriptor que raises AttributeError from __get__. En la búsqueda de atributos, habrá una excepción, generalmente indistinguible de una falla de búsqueda.

    La manera más simple es simplemente declarar una propiedad que aumenta AttributeError. Esto es esencialmente lo que @JBernardo había sugerido.

    class SpanishInquisition(Spam): 
        @property 
        def expect(self): 
         raise AttributeError("Nobody expects the Spanish Inquisition!") 
    
    assert hasattr(Spam, "expect") == True 
    # assert hasattr(SpanishInquisition, "expect") == False # Fails! 
    assert hasattr(SpanishInquisition(), "expect") == False 
    

    Sin embargo, esto sólo funciona para las instancias, y no para las clases (la afirmación hasattr(SpanishInquisition, "expect") == True se rompería).

    Si desea que todas las afirmaciones anteriores para ser verdad, utilice esto:

    class AttributeHider(object): 
        def __get__(self, instance, owner): 
         raise AttributeError("This is not the attribute you're looking for") 
    
    class SpanishInquisition(Spam): 
        expect = AttributeHider() 
    
    assert hasattr(Spam, "expect") == True 
    assert hasattr(SpanishInquisition, "expect") == False # Works! 
    assert hasattr(SpanishInquisition(), "expect") == False 
    

    Creo que este es el método más elegante, ya que el código es claro, genérica y compacto. Por supuesto, uno debería realmente pensar dos veces si eliminar el atributo es lo que realmente quieren.

  2. Anulación de búsqueda de atributos with __getattribute__ magic method. Puede hacer esto en una subclase (o en una mixin, como en el ejemplo a continuación, ya que quería escribirla solo una vez), y eso escondería el atributo en la subclase instancias. Si también desea ocultar el método de la subclase, debe usar metaclases.

    class ExpectMethodHider(object): 
        def __getattribute__(self, name): 
         if name == "expect": 
          raise AttributeError("Nobody expects the Spanish Inquisition!") 
         return super().__getattribute__(name) 
    
    class ExpectMethodHidingMetaclass(ExpectMethodHider, type): 
        pass 
    
    # I've used Python 3.x here, thus the syntax. 
    # For Python 2.x use __metaclass__ = ExpectMethodHidingMetaclass 
    class SpanishInquisition(ExpectMethodHider, Spam, 
             metaclass=ExpectMethodHidingMetaclass): 
        pass 
    
    assert hasattr(Spam, "expect") == True 
    assert hasattr(SpanishInquisition, "expect") == False 
    assert hasattr(SpanishInquisition(), "expect") == False 
    

    Esto se ve peor (más detallado y menos genérico) que el método anterior, pero se puede considerar este enfoque también.

    Nota, esto hace no trabajar ("mágicos") métodos especiales (por ejemplo __len__), porque aquellos de derivación __getproperty__. Consulte la sección Special Method Lookup de la documentación de Python para obtener más detalles. Si esto es lo que necesita deshacer, simplemente anule y llame a la implementación de object, omitiendo el elemento principal.

Huelga decir que esto sólo se aplica a las "clases de nuevo estilo" (los que heredan de object), como métodos mágicos y protocolos de descriptores no son compatibles allí. Afortunadamente, esos son una cosa del pasado.

Cuestiones relacionadas