2010-12-06 13 views
7

Al tropezar con el típico ejemplo de clase Point al aprender Python, noté que por algún motivo no puedo tener un nivel de clase (variable estática) del mismo tipo que el de la clase. P.ej.Variables de clase del mismo tipo en Python

class Point: 

    ORIGIN = Point() # doesn't work 

    def __init__(self, x=0, y=0): 
    self.x = x 
    self.y = y 

mientras que las mismas obras en Java:

class Point { 

    public static void main(final String[] args) { } 

    private static final Point ORIGIN = new Point(0, 0); 

    private int x; 

    private int y; 

    public Point(int x, int y) { 
    this.x = x; 
    this.y = y; 
    } 

} 

La pregunta es: ¿Hay alguna manera de lograr lo mismo en Python. En este momento estoy confiando en las variables de nivel de módulo y no me gusta esa solución. Además, ¿hay alguna razón por la cual no se puede hacer en el cuerpo de la clase?

+2

"En este momento estoy confiando en las variables de nivel de módulo y no me gusta esa solución" ¿Por qué no? Las variables globales a nivel de módulo tienen mucho más sentido que las variables finales estáticas extrañas ocultas dentro de las definiciones de clases. ¿Qué pasa con una solución más simple? –

+0

@ S.Lott: El razonamiento es que el ORIGEN * no * pertenece a ningún módulo. Si, por ejemplo, Decido mover esta clase a algún otro módulo, la forma en que se hace referencia a ORIGIN siempre es la misma. – sasuke

+0

put ORIGIN es una instancia de Point y pertenece al mismo módulo que Point. Si mueves uno, ¿por qué no moverías el otro? –

Respuesta

4

No puede crear una instancia de una clase, hasta que esa clase realmente se cree, que es después de que se evalúe el cuerpo de la clase (nota: se ejecuta como el código Python normal).

Lo mismo ocurre con su ejemplo de Java: ClassLoader crea la clase Point y luego ejecuta el código desde los campos static.

un equivalente aproximado de un cargador de clases en Python es la metaclase, por lo que podría hacer algo como esto:

def class_with_static(name, bases, body): 
    static_block = body.pop("__static__", None) 
    klass = type(name, bases, body) 
    if static_block: 
     static_block(klass) 
    return klass 

class Point(object): 
    __metaclass__ = class_with_static 

    def __static__(cls): 
     cls.ORIGIN = cls() 

    def __init__(self, x=0, y=0): 
     self.x = x 
     self.y = y 

assert isinstance(Point.ORIGIN, Point) 
assert Point.ORIGIN.x == Point.ORIGIN.y == 0 
assert not hasattr(Point, "__static__") 

Por supuesto, esto tendrá algunas otras consecuencias, como: todas las subclases de Point tendrán una ORIGIN atributo propio. Entonces probablemente solo quiera hacerlo como se muestra a los demás :)

+0

Buena explicación; pero el problema aquí parece ser que en Java la variable de nivel de clase 'ORIGIN' se inicializa solo * después * se carga toda la clase, pero en el caso de Python intenta buscar Point y falla, que en realidad no debería haber sido el caso.Intenté pensar una buena razón de por qué esto no está permitido en Python, pero no puedo encontrar ninguno. Como han señalado otros, la solución más limpia sería adjuntar la propiedad ORIGIN a la clase * después * de su declaración. :-( – sasuke

+0

Su confusión proviene del hecho de que está tratando 'clase' como una declaración, mientras que de hecho es una expresión, que almacena su resultado en un nombre de nivel de módulo" Punto ". No puede tener recursivo expresiones en Python, por lo que no puede hacer referencia a 'Punto' en la expresión que se utilizará como un valor de 'Punto'. – lqc

+0

Supongo que cuando dices expresión, te refieres a una forma específica de pitón, ¿no? Porque normalmente las expresiones pueden ser asignado a variables que no parece ser el caso aquí. Entonces, ¿significa que el tiempo de ejecución de Python atraviesa el código secuencialmente, trata la clase de nivel de módulo, la función y las declaraciones de variables como expresiones y las asigna a la propiedad del módulo? puede encontrar los detalles de cómo estas cosas (clases, la ejecución del programa) funcionan bajo el capó en profundidad? Gracias. – sasuke

8
class Point(object): 
    pass 

Point.ORIGIN = Point() 
5

asignarlo después de los hechos:

class Point: 
    def __init__(self, x=0, y=0): 
    self.x = x 
    self.y = y 


Point.ORIGIN = Point() 
+0

Esa es una variable de nivel de módulo que es exactamente con lo que no quiero terminar. :-) – sasuke

+7

No, no lo es. La ** asignación ** está en el nivel del módulo, pero el ** valor ** se almacena como un atributo de la clase Punto. –

+0

De hecho; mal de mi parte. El punto es que esto está empujando la declaración de nivel de clase fuera de la clase. Python puede tener declaraciones normales de clase y de nivel de instancia * dentro de la declaración de clase, ¿por qué no esta? ¿Es esto una deficiencia del idioma? Si no, ¿cuál es la razón detrás de no permitir que los tipos se refieran a sí mismos? – sasuke

2

usted puede hacer esto con un decorador de clase, aunque no estoy seguro de lo que sería un buen nombre para él. Así es como:

def add_class_var(name, *args, **kwrds): 
    def decorator(cls): 
     setattr(cls, name, cls(*args, **kwrds)) 
     return cls 
    return decorator 

@add_class_var('ORIGIN') 
class Point: 
    def __init__(self, x=0, y=0): 
     self.x = x 
     self.y = y 

print Point.ORIGIN, Point.ORIGIN.x, Point.ORIGIN.y 
# <__main__.Point instance at 0x00B5B418> 0 0 

Aunque no se utiliza en el código anterior, también puede pasar argumentos a __init__() constructor de la clase indirectamente a través del decorador.

Cuestiones relacionadas