1

I want to make an attribute accessible from instance and class that cannot be reassigned. I have the "prevent from reassigned" part tackled thanks to a metaclass. However, now the attribute cannot be read from the class. How to make it so.

Having this:

class Meta(type):
    def __setattr__(cls, attr, val):
        if attr =="frozen":
            print "You can't unfreeze the frozen"
        else:
            cls.name = val


class Fixed(object):
    __metaclass__ = Meta
    frzn = 'I AM AWESOME'
    def holdset(_,val):
        _.frzn = _.frzn
        print "frozen is frozen not setting to ", val

    def get(_):
        return _.frzn
    frozen = property(fset=holdset,fget=get)

When calling

print Fixed.frozen
print Fixed().frozen

Gives

<property object at 0x106dbeba8>
I AM AWESOME

Why doesn't it give the same? How to make it give the same?

2
  • 2
    Why would you call the first parameter _ rather than self, which is the universal Python convention? Commented Feb 26, 2014 at 20:02
  • 1
    How about you shrink that code by removing the metaclass, and test whether the issue remains. If so, ta-dah! you can simplify your question, or perhaps even find a solution yourself. Commented Feb 26, 2014 at 20:04

2 Answers 2

1

A property normally only works on an instance. To make a property work on a class too, you'll need to create your own descriptor instead:

class ClassAndInstanceProperty(object):
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            obj = objtype
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

The only difference between the above and a regular property is in the __get__ method; a normal property object does (in C) the equivalent of:

def __get__(self, obj, objtype=None):
    if obj is None:
        return self
    if self.fget is None:
        raise AttributeError("unreadable attribute")
    return self.fget(obj)

e.g. on a class the descriptor returns self, while my version sets obj to objtype in that case.

Also note that a descriptor __set__ method is only called when working with an instance; Fixed.frozen = 'something' would not invoke the descriptor __set__, only Fixed().frozen = 'something' would. Your metaclass __setattr__ intercepts the attribute assignment on the class instead.

You could also put a descriptor object on the metaclass, which would then be given the opportunity to have it's __set__ called for attribute assignment on the class itself.

Sign up to request clarification or add additional context in comments.

1 Comment

"You could also put a descriptor object on the class, which would then be given the opportunity to have it's set called for attribute assignment on the class itself." How could this be achieved? Could it be achieved by putting the descriptor on the metaclass?
1

By convention a Python descriptor returns itself when not called with an instance. Properties are just a shortcut for a descriptor, to do this you need to implement your own descriptor. The following should do what you want, without needing a metaclass:

class Frozen(object):
    def __init__(self, fixed_value):
        self.fixed_value = fixed_value

    def __get__(self, instance, owner=None):
        return self.fixed_value

    def __set__(self, instance, value):
        print "You can't unfreeze the frozen"


class Fixed(object):
    frzn = Frozen("I AM AWESOME")

You would also probably want to override the delete method which is part of the descriptor interface. There are some good articles explaining exactly how descriptors work.

4 Comments

Fixed.frzn = 'spam' will not invoke the descriptor __set__ method though. You'd need to have a descriptor on the metaclass instead for that to work.
@Martijn. Good point -- a metaclass would be necessary to prevent Fixed.frzn from overriding the class property. I'm not sure that's what the OP wants to do though.
Perhaps that's why the OP is using a metaclass with __setattr__?
Quite possibly. It's not clear whether there's an intent to prevent setting the property via class object, or just a desire to have Fixed.frzn and Fixed().frzn return the same thing while prevening Fixes().frzn = 'something." A metaclass may or not be overkill depending on what this is being used for.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.