topher
topher

Reputation: 83

python read-only class properties

Is there a way to make read-only class properties in Python? Ex. in Unity3d you can do this:

transform.position = Vector3.zero

Vector3.zero returns an instance of the Vector3 class where x, y, and z are 0. This is basically the same as:

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

I've tried doing something like this:

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

But I get an undefined variable error because the class hasn't been defined yet. So how do you make read-only class properties that don't require an instance of the class?

Upvotes: 7

Views: 3368

Answers (7)

cnelson
cnelson

Reputation: 1375

Use a 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__)

Should do what you want:

>>> 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}

Upvotes: 6

neurino
neurino

Reputation: 12395

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

Shamelessly stolen from here

Upvotes: 0

Devin Jeanpierre
Devin Jeanpierre

Reputation: 95516

The most obvious way might be to alter the class object after the fact:

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

The main problem with this is that there's then only one zero object, and if it's mutable you can cause accidental damage all over the place. It may be easier (and feel less hacky) to use a dynamic descriptor that creates a zero vector every time it's accessed (this is done by creating a 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)

I consider none of these really "pythonic", though. Consider the other mathematical types in Python: ints, floats, and complex numbers. None of these have a "zero" class attribute, or a zero constructor, instead they return zero when called with no arguments. So perhaps it might be best to do like so:

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

This is less like Unity3D and more like Python, if you know what I mean.

Upvotes: 10

Adam Parkin
Adam Parkin

Reputation: 18670

That's a really interesting question, the workaround I would go with is to do a classmethod as a "getter" for the zero object:

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()

Upvotes: 1

Eryk Sun
Eryk Sun

Reputation: 34260

Use a metaclass

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)

Upvotes: 8

Nick ODell
Nick ODell

Reputation: 25210

As for the read-only part, this is a good resource.

template = property(lambda self: self.__template)

Upvotes: -1

Mike Graham
Mike Graham

Reputation: 76673

What you're imagining is possible, but not necessary in this case. Just wait until after your class is defined to assign the attribute

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

or make it a module level constant.


There is a good chance you want simply to use a shape (3,) numpy array instead of writing this class, for any practical purposes.

Upvotes: 2

Related Questions