Python 确定类属性是否为只读数据描述符

Python 确定类属性是否为只读数据描述符,python,python-3.x,Python,Python 3.x,只读数据描述符是一种描述符,它定义了\uuuu get\uuuuu和\uuuu set\uuuuu,但\uuuu set\uuuuuu在调用时会引发属性错误 例如,一个简单的只读属性: class Test(): _i = 1 @property def i(self): return self._i assert hasattr(Test.i, '__get__') assert hasattr(Test.i, '__set__') t = Tes

只读数据描述符是一种描述符,它定义了
\uuuu get\uuuuu
\uuuu set\uuuuu
,但
\uuuu set\uuuuuu
在调用时会引发
属性错误

例如,一个简单的只读属性:

class Test():

    _i = 1

    @property
    def i(self):
        return self._i

assert hasattr(Test.i, '__get__')
assert hasattr(Test.i, '__set__')
t = Test()
t.i # 1
t.i = 2 # ERROR
如果我有一个类的实例,我可以通过这种方式确定实例属性是否为只读数据描述符(尽管我一点也不喜欢):

如果我知道该类不需要实例化任何参数,我可以确定它的class属性是否是只读数据描述符,类似于上面所述:

def is_ro_data_descriptor_from_klass(klass, attr):
    try:
        setattr(klass(), attr, None)
    except AttributeError:
        return True
    else:
        return False
但是,如果我事先不知道类的签名,并且我尝试以这种方式实例化一个临时对象,我可能会得到一个错误:

class MyClass():
    i = 1
    def __init__(self, a, b, c):
        '''a, b, and c are required!'''
        pass

def is_ro_data_descriptor_from_klass(MyClass, 'i') # Error
如何确定类属性是否为只读数据描述符

编辑:添加更多信息

下面是我正在尝试使用的代码:

class StaticVarsMeta(type):
    '''A metaclass that will emulate the "static variable" behavior of
    other languages. For example: 

        class Test(metaclass = StaticVarsMeta):
            _i = 1
            @property
            def i(self):
                return self._i
        t = Test()
        assert t.i == Test.i'''
    statics = {}
    def __new__(meta, name, bases, dct):
        klass = super().__new__(meta, name, bases, dct)
        meta.statics[klass] = {}
        for key, value in dct.items():
            if "_" + key in dct:
                meta.statics[klass][key] = set()
                if hasattr(value, '__get__'):
                    meta.statics[klass][key].add('__get__')
                if hasattr(value, '__set__'):
                    try:
                        value.__set__(None, None)
                    except AttributeError:
                        continue
                    else:
                        meta.statics[klass][key].add('__set__')
        return klass
    def __getattribute__(klass, attr):
        if attr not in StaticVarsMeta.statics[klass]:
            return super().__getattribute__(attr)
        elif '__get__' not in StaticVarsMeta.statics[klass][attr]:
            return super().__getattribute__(attr)
        else:
            return getattr(klass, '_' + attr)
    def __setattr__(klass, attr, value):
        if attr not in StaticVarsMeta.statics[klass]:
            super().__setattr__(attr, value)
        elif '__set__' not in StaticVarsMeta.statics[klass][attr]:
            super().__setattr__(attr, value)
        else:
            setattr(klass, '_' + attr, value)

class Test(metaclass = StaticVarsMeta):
    _i = 1
    def get_i(self):
        return self._i
    i = property(get_i)
注意以下几点:

type(Test.i) # int
type(Test.__dict__['i']) # property
Test().i = 2 # ERROR, as expected
Test.i = 2 # NO ERROR - should produce an error

这看起来非常尴尬,但根据我的评论,以下是您如何实现它:

class StaticVarsMeta(type):

    statics = {}

    def __new__(meta, name, bases, dct):
        cls = super().__new__(meta, name, bases, dct)
        meta.statics[cls] = {}
        for key, val in dct.items():
            if hasattr(val, '__get__') and hasattr(val, '__set__'):
                meta.statics[cls][key] = {'__get__'}
                try:
                    val.__set__(None, None)
                except AttributeError as err:
                    if "can't set attribute" in err.args:
                        continue
                meta.statics[cls][key].add('__set__')
        return cls
使用中:

>>> class ReadOnly(metaclass=StaticVarsMeta):
    @property
    def foo(self):
        return None


>>> class ReadWrite(metaclass=StaticVarsMeta):
    @property
    def bar(self):
        return None
    @bar.setter
    def bar(self, val):
        pass


>>> StaticVarsMeta.statics
{<class '__main__.ReadOnly'>: {'foo': {'__get__'}}, 
 <class '__main__.ReadWrite'>: {'bar': {'__get__', '__set__'}}}
类只读(元类=StaticVarsMeta): @财产 def foo(self): 一无所获 >>>类ReadWrite(元类=StaticVarsMeta): @财产 def bar(自): 一无所获 @酒吧服务员 def条(自身,val): 通过 >>>静力学 {:{'foo':{'uuuuuuuuuuuuuuu'}, :{'bar':{'uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu'}

这更像是一个“10的起点”,必须有更好的方法来做到这一点。

您的第一个解决方案可以通过尝试分配它已经具有的值来变得更简单、更健壮。这样,就不需要撤消(尽管如此,这不是线程安全的)


我已经重读了这个问题,并且更好地理解了它。我已经删除了与其他类型描述符相关的两个实现,因为我认为它们只是在混淆视听?这里的用例是什么?很复杂。我是在补充。我正在编写一个元类
StaticVarsMeta
,它将模拟其他语言的静态变量行为。这些类允许您通过类本身或实例获取和设置“静态变量”。我的大部分工作正常,但意识到我必须在
StaticVarsMeta.\uuuu setattr\uuuuu
中提出一个
AttributeError
,当描述符是只读数据描述符时。这确实有点麻烦,但请注意,您得到了
AttributeError:无法设置属性
传递给
Test.I.\uu set\uuuuu
的任何“实例”对象,因此,您可以调用例如
Test.i.\uu set\uuu(None,None)
,查看
err.args
是否是您所期望的(如果它不是只读的,您仍然会得到
AttributeError
,但是现在使用
'NoneType'对象没有属性'i'
)@RickTeachey——也许我没有跟踪,但是看起来提升AttributeError是一个只读描述符的特性,您正在使用它来将其标识为只读——那么为什么不直接传递到描述符,让它提升AttributeError呢?太好了。这比我想出的任何主意都好。我不认为有一种方法可以做到这一点,它不会非常尴尬。@RickTeachey也许不会,但我不喜欢依赖这样的特定错误消息。无论如何,希望有人会出现,告诉我为什么我错了@RickTeachey嗯,我展示的例子对我很有用。您使用的是哪个版本?顺便说一句,上面写的我的元类仍然是坏的,但现在它有一个不同的问题。虽然这是一个好的观点,但我不确定这是否回答了这个问题,因为它没有告诉OP如何从类对象中找出属性是否是只读的。@jonrsharpe不是问题的答案,但仍然是一个有用的想法。谢谢
>>> class ReadOnly(metaclass=StaticVarsMeta):
    @property
    def foo(self):
        return None


>>> class ReadWrite(metaclass=StaticVarsMeta):
    @property
    def bar(self):
        return None
    @bar.setter
    def bar(self, val):
        pass


>>> StaticVarsMeta.statics
{<class '__main__.ReadOnly'>: {'foo': {'__get__'}}, 
 <class '__main__.ReadWrite'>: {'bar': {'__get__', '__set__'}}}
def is_ro_data_descriptor_from_instance(instance, attr):
    temp = getattr(instance, attr)
    try:
        setattr(instance, attr, temp)
    except AttributeError:
        return True
    else:
        return False