【问题标题】:How to call methods on Python class descriptor objects?如何在 Python 类描述符对象上调用方法?
【发布时间】:2011-05-16 21:20:46
【问题描述】:

我用__get__()__set__()和一个方法to_db()创建了一个class String();但是,当我执行name = String() 时,我不能执行self.name.to_db(),因为它在__get__() 返回的值上调用to_db(),而不是对象“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__() 中创建一个条件来检查它并调用to_db(),如果它是True,但是这看起来很笨拙。有没有更好的办法?

另外,我是描述符的新手——使用instance 和/或instance.__dict__ 存储状态与将其存储在self 中的优缺点是什么?

【问题讨论】:

  • 对于任何需要显式方法调用的东西不使用描述符怎么样?
  • 在我看来,您试图以错误的方式使用描述符。这里有一个很好的描述:docs.python.org/howto/descriptor.html,特别注意property()的实现。
  • 你为什么不使用属性?
  • String 描述符对象将在所有Example 对象之间共享。因此,如果您创建exp1 = Example("Example1")exp2 = Example("Example2"),那么exp1.nameexp2.name 将给出相同的输出'Example2'。使用property 代替@JochenRitzel

标签: python descriptor


【解决方案1】:

这很简单 - 只需让你的描述符返回一个带有你想要的额外方法的字符串子类。

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

【讨论】:

  • 按照我上面的评论,它不能完全工作。如果您真的想使用描述符 - 使用带有标签的 instance 参数来获取每个实例级别的 value
【解决方案2】:

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

例如,我们可以在__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'}

【讨论】:

    【解决方案3】:

    这是一个允许您绕过类中定义的任何描述符的解决方案:

    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。再说一次,这是一个非常兼容的解决方案,可以避免对描述符包装的对象进行猴子修补。

    也就是说,我不确定描述符是否是您尝试做的事情(写入数据库?)的最佳解决方案。

    【讨论】:

      【解决方案4】:

      to_db 方法中,您可以通过

      直接访问该值
      self.__dict__['value'] # value as key is not ideal, but that's what OP used
      

      或者,如果您只使用新样式类,

      object.__set__(self, name, value)
      

      由于您使用的是魔法属性,因此访问魔法__dict__ 是完全合理的。

      __setattr__ [1] 的文档中也提到了这一点(抱歉,__set__ 中没有直接引用 __dict__,但它是同一个问题域)

      If __setattr__() wants to assign to an instance attribute, it should not 
      simply execute   self.name = value — this would cause a recursive call to itself. 
      Instead, it should insert the value in the dictionary of instance attributes, e.g., 
      self.__dict__[name] = value. For new-style classes, rather than accessing the instance 
      dictionary, it should call the base class method with the same name, for example, 
      object.__setattr__(self, name, value).
      

      [1]http://docs.python.org/2/reference/datamodel.html#customizing-attribute-access

      【讨论】:

      • 这充其量是一个讨厌的解决方法。我会避免摆弄__dict__
      • 也许我遗漏了什么,但是 e = Example(name="Julie",age="21") 然后 e.name.to_db() 说: >>> e.name.to_db () Traceback (last recent call last): File "", line 1, in AttributeError: 'str' object has no attribute 'to_db'" -- 大概是因为 get 是首先被调用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多