简体   繁体   中英

Python descriptor returning itself not value when called from class

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?

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)

eg 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM