2010-07-01 10 views
42

que tienen una clase como:__getattr__ para las variables estáticas/clase en Python

class MyClass: 
    Foo = 1 
    Bar = 2 

Siempre que se invoca MyClass.Foo o MyClass.Bar, necesito un método personalizado que se debe invocar antes de devolver el valor. ¿Es posible en Python? Sé que es posible si creo una instancia de la clase y puedo definir mi propio método __getattr__. Pero mi scnenario implica el uso de esta clase como tal sin crear ninguna instancia de la misma.

También necesito un método personalizado __str__ para invocar cuando se invoca str(MyClass.Foo). ¿Python proporciona esa opción?

Respuesta

48

__getattr__() y __str__() para un objeto se encuentran en su clase, por lo que si desea personalizar esas cosas para una clase, necesita la clase de clase. Una metaclase

class FooType(type): 
    def _foo_func(cls): 
     return 'foo!' 

    def _bar_func(cls): 
     return 'bar!' 

    def __getattr__(cls, key): 
     if key == 'Foo': 
      return cls._foo_func() 
     elif key == 'Bar': 
      return cls._bar_func() 
     raise AttributeError(key) 

    def __str__(cls): 
     return 'custom str for %s' % (cls.__name__,) 

class MyClass: 
    __metaclass__ = FooType 


print MyClass.Foo 
print MyClass.Bar 
print str(MyClass) 

impresión:

foo! 
bar! 
custom str for MyClass 

Y no, un objeto no puede interceptar una solicitud de stringifying uno de sus atributos. El objeto devuelto para el atributo debe definir su propio comportamiento __str__().

+0

Gracias Ignacio and Matt. Creo que ahora entiendo cómo funciona la creación de clase (no instancia) y también pensé que el comportamiento de __str__ requerido era difícil de lograr. Creo que las metaclases deberían ser lo suficientemente buenas para mi requerimiento. :) – Tuxdude

+0

Si siempre obtienes 'AttributeError' mira la otra respuesta, que tiene la sintaxis para python3 (este ejemplo parece ser python2) – Chris

8

Para la primera, necesitarás crear una metaclase, y definir __getattr__() en eso.

class MyMetaclass(type): 
    def __getattr__(self, name): 
    return '%s result' % name 

class MyClass(object): 
    __metaclass__ = MyMetaclass 

print MyClass.Foo 

Para el segundo, no. Llamar al str(MyClass.Foo) invoca MyClass.Foo.__str__(), por lo que deberá devolver un tipo apropiado para MyClass.Foo.

+0

Probé este ejemplo y solo funciona para mí en Python 2.x.Después de cambiar la impresión a una función, Python 3.2 me da un "AttributeError: type object 'MyClass' no tiene el atributo 'Foo'" error. ¿Alguna sugerencia? – CyberFonic

+1

@CyberED: http://docs.python.org/py3k/reference/datamodel.html#metaclasses: "Cuando se lee la definición de clase, si se pasa un argumento de palabra clave' metaclass' invocable después de las bases en la definición de clase, el callable dado se llamará en lugar de 'type()'. " –

+3

No podía esperar ... después de un poco de búsqueda en Google y pruebas: El ejemplo es para Python 2.x. En Python 3.x necesita: clase MyClass (metaclass = MyMetaclass): pase – CyberFonic

5

nadie Sorprendido señaló éste hacia fuera:

class FooType(type): 
    @property 
    def Foo(cls): 
     return "foo!" 

    @property 
    def Bar(cls): 
     return "bar!" 

class MyClass(metaclass=FooType): 
    pass 

Obras:

>>> MyClass.Foo 
'foo!' 
>>> MyClass.Bar 
'bar!' 

(para Python 2.x, cambio de definición de MyClass a:

class MyClass(object): 
    __metaclass__ = FooType 

)

Lo que dicen las demás respuestas sobre str es válido para esta solución: debe implementarse en el tipo realmente devuelto.

+2

Aunque normalmente trato de evitar publicar "Gracias", realmente necesito publicar "Gracias" en este momento: ¡Gracias! – Chris

11

(sé que esto es una cuestión de edad, pero como todas las otras respuestas utilizan una metaclase ...)

Usted puede utilizar el siguiente sencillo classproperty descriptor:

class classproperty(object): 
    """ @[email protected] """ 
    def __init__(self, f): 
     self.f = classmethod(f) 
    def __get__(self, *a): 
     return self.f.__get__(*a)() 

usarlo como:

class MyClass(object): 

    @classproperty 
    def Foo(cls): 
     do_something() 
     return 1 

    @classproperty 
    def Bar(cls): 
     do_something_else() 
     return 2 
+0

Esto solo ayudará si conoce los nombres de sus propiedades de antemano. Si los lee desde el archivo y necesita reaccionar dinámicamente, esto no funcionará. – Chris

Cuestiones relacionadas