2012-03-18 21 views
18

Tengo una clase Klass con un atributo de clase my_list. Tengo una subclase de ella SubKlass, en el que quiero tener un atributo de clase my_list que es una versión modificada del mismo atributo de la clase padre:Acceso al atributo de clase principal del cuerpo de subclase

class Klass(): 
    my_list = [1, 2, 3] 


class SubKlass(Klass): 
    my_list = Klass.my_list + [4, 5] # this works, but i must specify parent class explicitly 
    #my_list = super().my_list + [4, 5] # SystemError: super(): __class__ cell not found 
    #my_list = my_list + [4, 5] # NameError: name 'my_list' is not defined 


print(Klass.my_list) 
print(SubKlass.my_list) 

Entonces, ¿hay una manera de acceder atributo de clase de padres sin especificando su nombre?

ACTUALIZACIÓN:

Hay un error de seguimiento de incidencias Python: http://bugs.python.org/issue11339. Esperemos que sea solved en algún momento.

Respuesta

10

No puede.

Una definición de clase funciona en Python funciona de la siguiente manera.

  1. El intérprete ve una instrucción class seguida de un bloque de código.

  2. Crea un nuevo espacio de nombres y ejecuta ese código en el espacio de nombres.

  3. Llama al type incorporado con el espacio de nombres resultante, el nombre de clase, las clases base y la metaclase (si corresponde).

  4. Asigna el resultado al nombre de la clase.

Mientras ejecuta el código dentro de la definición de clase, no sabe cuáles son las clases base, por lo que no puede obtener sus atributos.

Lo que puede hacer es modificar la clase inmediatamente después de haberla definido.


EDITAR: aquí hay un pequeño decorador de clases que puede usar para actualizar el atributo. La idea es darle un nombre y una función. Examina todas las clases base de su clase y obtiene sus atributos con ese nombre. Luego llama a la función con la lista de valores heredados de la clase base y el valor que definió en la subclase. El resultado de esta llamada está vinculado al nombre.

Código podría tener más sentido:

>>> def inherit_attribute(name, f): 
...  def decorator(cls): 
...    old_value = getattr(cls, name) 
...    new_value = f([getattr(base, name) for base in cls.__bases__], old_value) 
...    setattr(cls, name, new_value) 
...    return cls 
...  return decorator 
... 
>>> def update_x(base_values, my_value): 
... return sum(base_values + [my_value], tuple()) 
... 
>>> class Foo: x = (1,) 
... 
>>> @inherit_attribute('x', update_x) 
... class Bar(Foo): x = (2,) 
... 
>>> Bar.x 
(1, 2) 

La idea es que se define x ser (2,) en Bar. El decorador irá y mirará a través de las subclases de Bar, encontrará todos sus x s, y llamará al update_x con ellos. Así se llamará

update_x([(1,)], (2,)) 

los combina mediante la concatenación de ellos, entonces se une de nuevo a que x nuevo. ¿Tiene sentido?

+2

Tenga en cuenta que las metaclases secuestran # 3, e incluso pueden modificar 'bases' antes de pasarlo a' tipo'. Entonces sí, no puedes. – delnan

+0

Sí, eso tiene sentido. Pensé en eso yo mismo (a través de metaclases), pero extender una lista heredada fue solo un ejemplo. Es posible que desee eliminar algunos elementos de la lista en lugar de ampliarlos. O no tiene una lista sino otro tipo ... ¡Gracias por la amplia respuesta! – warvariuc

+0

He oído en Ruby que puedes acceder a la clase antes de que estuviera completamente definida ... – warvariuc

0

No hay forma de que el cuerpo de la clase; desde un contexto en el que el yo está disponible (o la clase misma), puede examinar __bases__.

+0

No, 'self' no está disponible. De lo contrario, podría haber usado 'super()' – warvariuc

+0

Sí, sé que no está disponible en el cuerpo de una clase. Es por eso que lo dije. Si no se va a molestar en leer las respuestas correctamente, ¿por qué hacer la pregunta? – Marcin

1

Puede usar una metaclass o un decorador de clase. He aquí cómo se usaría una metaclase en Python 2.x:

class Klass(object): 
    my_list = [1, 2, 3] 

class KlassMeta(type): 

    def __new__(cls, name, bases, attrs): 
     # grab last base class, which in this case is Klass 
     attrs['my_list'] = bases[-1].my_list + [4, 5] 
     return super(KlassMeta, cls).__new__(cls, name, bases, attrs) 

class SubKlass(Klass): 
    __metaclass__ = KlassMeta 

con Python 3 que declararía la metaclase utilizando la nueva sintaxis:

class SubKlass(Klass, metaclass=KlassMeta): 
    pass 
+0

No anime a las personas a usar metaclases innecesariamente. Son complicadas y deben evitarse 9 de cada 10 veces que alguien crea que las necesita. La mayoría de las personas no necesita saber cómo funcionan las metaclases. – gps

0

Como hemos visto de las otras respuestas como katriealex: No, no puedes. Al menos no es fácil.

P: ¿Qué está tratando de hacer realmente?

P: ¿Por qué quieres hacer esto?

+1

Python tiene una buena característica que Alex Martelli llama 'herencia de datos'. En '__init__' puedo escribir' self.setting1 = self.setting1 + 5', donde 'setting1' es un atributo de clase. Puede ser incluso un atributo de clase padre. En el último ejemplo, no he usado ningún código especial, el nombre de búsqueda por nombre en los atributos de clase se realiza automáticamente. Quiero la misma característica dentro del cuerpo de la clase. – warvariuc

+1

@warvariuc que va a crear un atributo de instancia llamado 'self.setting1', que luego se accederá delante de cualquier atributo de clase. Entonces, aunque eso va a ayudar en algunas circunstancias, no va a permitir que las personas establezcan atributos de clase ... – Maximilian

2

Respondido por @katrielalex, my_list no está en el espacio de nombres predeterminado antes de la clase ha sido creada. Lo que podría hacer sin embargo en Python 3, si desea sumergirse en metaclases, es agregar my_list al espacio de nombres de forma manual:

class Meta(type): 
    def __prepare__(name, bases, **kwds): 
     print("preparing {}".format(name), kwds) 
     my_list = [] 
     for b in bases: 
      if hasattr(b, 'my_list'): 
       my_list.extend(b.my_list) 
     return {'my_list': my_list} 

class A(metaclass=Meta): 
    my_list = ["a"] 

class B(A): 
    my_list.append(2) 

print(A().my_list) 
print(B().my_list) 

Tenga en cuenta que este ejemplo probablemente todavía no maneja bien las estructuras de herencia en forma de diamante.

El nuevo atributo __prepare__ se define en PEP 3115.

+0

Enfoque interesante – warvariuc

Cuestiones relacionadas