如何调用Python类描述符对象的方法?

5 投票
4 回答
3628 浏览
提问于 2025-04-16 17:46

我创建了一个叫做 class String() 的类,里面有 __get__()__set__() 这两个方法,还有一个叫 to_db() 的方法。但是,当我执行 name = String() 时,我不能直接使用 self.name.to_db(),因为这时候调用的 to_db() 方法是从 __get__() 返回的值上调用的,而不是对象 "name" 本身。

class String(object):

    def __init__(self, value=None):
        if value:
            self.value = str(value)

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        self.value = str(value)

    def to_db(self):
        return {'type':'string', 'value': self.value}

class Example(object):

    name = String()
    age = Integer()

    def __init__(self,name,age):
        self.name = name
        self.age = age

    def save():
        data = dict(name=self.name.to_db(), age=self.age.to_db())
        db.save(data)

解决这个问题的一种方法是,不直接调用 self.name.to_db(),而是在 instance 中设置一个标志,然后在 __get__() 中创建一个条件判断,如果这个标志是 True,就调用 to_db(),但这样做感觉有点笨拙。有没有更好的办法呢?

另外,我对描述符还很陌生,使用 instance 和/或 instance.__dict__ 来存储状态和直接存储在 self 中各有什么优缺点呢?

4 个回答

0

这里有一个解决方案,可以让你绕过类中定义的任何描述符:

class BypassableDescriptor(object):
    pass

class String(BypassableDescriptor):
    def __init__(self, value=None):
        if value:
            self.value = str(value)

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        self.value = str(value)

    def to_db(self):
        return {'type': 'string', 'value': self.value}

class Integer(BypassableDescriptor):
    def __init__(self, value=None):
        if value:
            self.value = str(value)

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        self.value = int(value)

    def to_db(self):
        return {'type': 'integer', 'value': self.value}

class BypassDescriptor(object):
    def __init__(self, descriptor):
        self.descriptor = descriptor

    def __getattr__(self, name):
        return getattr(self.descriptor, name)

class AllowBypassableDescriptors(type):
    def __new__(cls, name, bases, members):
        new_members = {}
        for name, value in members.iteritems():
            if isinstance(value, BypassableDescriptor):
                new_members['real_' + name] = BypassDescriptor(value)
        members.update(new_members)
        return type.__new__(cls, name, bases, members)

class Example(object):
    __metaclass__ = AllowBypassableDescriptors

    name = String()
    age  = Integer()

    def __init__(self,name,age):
        self.name = name
        self.age  = age

    def save(self):
        data = dict(name = self.real_name.to_db(), age = self.real_age.to_db())

需要注意的是,这个方法并不完美——你总是需要用 real_fieldname.method() 来代替 fieldname.method(),而且你所有需要访问这个字段的类都得使用名为 AllowBypassableDescriptors 的元类。不过,这个方案还是挺兼容的,避免了对描述符所包裹的对象进行复杂的修改。

话虽如此,我不太确定描述符是否是你想要的最佳解决方案(比如说,写入数据库?)。

1

描述符用于说明“这是什么”或“它是如何工作的”。

举个例子,我们可以在 __get__()__set__() 中设置一些限制。

根据你的问题,我觉得你想在 type<str> 中添加你自己的方法,而不是描述如何设置或获取实例。

所以你可以使用下面的代码来表达你想做的事情。

class String(str):
    def __init__(self, value):
        self.value = value

    def to_db(self):
        return {'type':'string', 'value': self.value}

ss = String('123')
print ss #123
print ss.to_db() #{'type':'string', 'value': '123'}
8

这很简单——只需要让你的描述符返回一个字符串的子类,并添加你想要的额外方法。

def __get__(self, instance, owner):
    class TaggedString(str):
        def to_db(self):
            return {'type':'string', 'value': self}
    return TaggedString(self.value)`

撰写回答