¿Hay alguna manera de hacer propiedades de clase de solo lectura en Python? Ex. en Unity3d puedes hacer esto:

transform.position = Vector3.zero

Vector3.zero devuelve una instancia de la clase Vector3 donde x, y y z son 0. Esto es básicamente lo mismo que:

transform.position = Vector3(0, 0, 0)

He intentado hacer algo como esto:

class Vector3(object):
    zero = Vector3(0, 0, 0)
    ...

Pero obtengo un error variable indefinido porque la clase aún no se ha definido. Entonces, ¿cómo se hacen las propiedades de clase de solo lectura que no requieren una instancia de la clase?

7
topher 1 sep. 2011 a las 20:22

7 respuestas

La mejor respuesta

Usa una metaclase

class MetaVector3(type):

    @property
    def zero(cls):
        return cls(0,0,0)

class Vector3(object):
    __metaclass__ = MetaVector3

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

>>> v = Vector3.zero
>>> v.x, v.y, v.z
(0, 0, 0)
9
Eryk Sun 1 sep. 2011 a las 16:38

Lo que estás imaginando es posible, pero no necesario en este caso. Solo espere hasta que se haya definido su clase para asignar el atributo

class Vector3(object):
    ...
Vector3.zero = Vector3(0, 0, 0)

O convertirlo en un nivel de módulo constante.


Hay una buena posibilidad de que desee simplemente usar una matriz numpy de forma (3,) en lugar de escribir esta clase, para cualquier propósito práctico.

2
Mike Graham 1 sep. 2011 a las 16:25
class ClassProperty(property):
    def __get__(self, cls, owner):
        return self.fget.__get__(None, owner)()

class Vector3(object):
    _zero = None

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

    @ClassProperty
    @classmethod
    def zero(cls):
        if cls._zero is None:
            cls._zero = cls(0,0,0) 
        return cls._zero

Robado descaradamente de aquí

0
Community 23 may. 2017 a las 12:18

La forma más obvia podría ser alterar el objeto de clase después del hecho:

class Vector3(object):
    # ...
Vector3.zero = Vector3(0, 0, 0)

El principal problema con esto es que solo hay un objeto cero, y si es mutable puede causar daños accidentales en todo el lugar. Puede ser más fácil (y sentirse menos hacky) usar un descriptor dinámico que crea un vector cero cada vez que se accede (esto se hace creando un ClassProperty class):

class ClassProperty(property):
    def __get__(self, cls, owner):
        return self.fget.__get__(None, owner)()

class Vector3(object):
    @ClassProperty
    @classmethod
    def zero(cls):
        return cls(0, 0, 0)

Sin embargo, considero que ninguno de estos es realmente "pitónico". Considere los otros tipos matemáticos en Python: ints, flotantes y números complejos. Ninguno de estos tiene un atributo de clase "cero", o un constructor cero, en su lugar, devuelven cero cuando se les llama sin argumentos. Entonces quizás sea mejor hacerlo así:

class Vector3(object):
    def __init__(self, x=0, y=0, z=0):
        self.x = x
        self.y = y
        self.z = z 

Esto es menos como Unity3D y más como Python, si sabes a lo que me refiero.

10
Community 23 may. 2017 a las 12:26

Utilice un descriptor:

class Zero(object):
    def __get__(self, instance, owner):
        return owner(0, 0, 0)

    def __set__(self, instance, value):
        #could raise an exception here or somethiing
        #this gets called if the user attempts to overwrite the property
        pass  

class Vector3(object):
    zero = Zero()

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

    def __repr__(self):
        return str(self.__dict__)

Debes hacer lo que quieras:

>>> v = Vector3(1, 2, 3)
>>> v
{'y': 2, 'x': 1, 'z': 3}
>>> v.zero
{'y': 0, 'x': 0, 'z': 0}
>>> v.zero = 'foo'
>>> v.zero
{'y': 0, 'x': 0, 'z': 0}
6
cnelson 1 sep. 2011 a las 22:06

Esa es una pregunta realmente interesante, la solución que usaría es hacer un método de clase como "captador" para el objeto cero:

class Vector3(object):
    __zero = None
    def __init__(self, x,y,z):
        self.x = x
        self.y = y
        self.z = z

    @classmethod
    def zero(cls):
        if not cls.__zero:
            cls.__zero = Vector3(0,0,0) 
        return cls.__zero

myzerovec = Vector3.zero()
1
Adam Parkin 1 sep. 2011 a las 16:39

En cuanto a la parte de solo lectura, este es un buen recurso.

template = property(lambda self: self.__template)
-1
Nick ODell 1 sep. 2011 a las 16:31