【问题标题】:Python "callable" attribute (pseudo-property)Python“可调用”属性(伪属性)
【发布时间】:2012-09-20 03:05:33
【问题描述】:

在 python 中,我可以通过直接分配给属性或通过调用改变属性状态的方法来改变实例的状态:

foo.thing = 'baz'

或:

foo.thing('baz')

有没有一种很好的方法来创建一个可以接受上述两种形式的类,这些形式可以扩展到大量具有这种行为的属性? (简短地说,我将展示一个我不太喜欢的实现示例。)如果您认为这是一个愚蠢的 API,请告诉我,但也许需要一个更具体的示例。假设我有一个Document 课程。 Document 可以有一个属性 title。然而,title 可能也希望有一些状态(字体、字体大小、对齐方式...),但普通用户可能会很高兴只需将标题设置为字符串并完成它...

实现此目的的一种方法是:

class Title(object):
     def __init__(self,text,font='times',size=12):
         self.text = text
         self.font = font
         self.size = size
     def __call__(self,*text,**kwargs):
         if(text):
             self.text = text[0]
         for k,v in kwargs.items():
             setattr(self,k,v)
     def __str__(self):
         return '<title font={font}, size={size}>{text}</title>'.format(text=self.text,size=self.size,font=self.font)

class Document(object):
     _special_attr = set(['title'])
     def __setattr__(self,k,v):
         if k in self._special_attr and hasattr(self,k):
             getattr(self,k)(v)
         else:
             object.__setattr__(self,k,v)

     def __init__(self,text="",title=""):
         self.title = Title(title)
         self.text = text

     def __str__(self):
         return str(self.title)+'<body>'+self.text+'</body>'

现在我可以按如下方式使用它:

doc = Document()
doc.title = "Hello World"
print (str(doc))
doc.title("Goodbye World",font="Helvetica")
print (str(doc))

虽然这个实现看起来有点乱(__special_attr)。也许那是因为这是一个混乱的 API。我不知道。 有没有更好的方法来做到这一点? 还是我在这条路上走得太远了?

我意识到我也可以为此使用 @property,但如果我有多个属性以这种方式运行,那将根本无法很好地扩展——我需要编写每个都有一个 getter 和 setter,哎呀

【问题讨论】:

  • 另外请注意,我的实际用例不涉及标记语言,我只是将其用作更多受众熟悉的示例。 (请不要建议我使用elementtreexml.favorite.parser ...)

标签: python properties


【解决方案1】:

这比之前的答案假设的要难一些。

描述符中存储的任何值都将在所有实例之间共享,因此它不是存储每个实例数据的正确位置。 另外,obj.attrib(...) 分两步执行:

tmp = obj.attrib
tmp(...)

Python 事先并不知道 第二步会紧随其后,因此您总是必须返回可调用的内容并引用其 对象。

在以下示例中,set 参数中隐含了引用:

class CallableString(str):
    def __new__(class_, set, value):
        inst = str.__new__(class_, value)
        inst._set = set
        return inst
    def __call__(self, value):
        self._set(value)

class A(object):
    def __init__(self):
        self._attrib = "foo"
    def get_attrib(self):
        return CallableString(self.set_attrib, self._attrib)
    def set_attrib(self, value):
        try:
            value = value._value
        except AttributeError:
            pass
        self._attrib = value
    attrib = property(get_attrib, set_attrib)

a = A()
print a.attrib
a.attrib = "bar"
print a.attrib
a.attrib("baz")
print a.attrib

简而言之:您想要的事情无法透明地完成。如果你不坚持破解这个限制,你会写出更好的 Python 代码

【讨论】:

    【解决方案2】:

    您只需创建一个遵循适当规则的descriptor class,就可以避免在可能的数百个属性上使用@property

    # Warning: Untested code ahead
    class DocAttribute(object):
        tag_str = "<{tag}{attrs}>{text}</{tag}>"
    
        def __init__(self, tag_name, default_attrs=None):
            self._tag_name = tag_name
            self._attrs = default_attrs if default_attrs is not None else {}
    
        def __call__(self, *text, **attrs):
            self._text = "".join(text)
            self._attrs.update(attrs)
            return self
    
        def __get__(self, instance, cls):
            return self
    
        def __set__(self, instance, value):
            self._text = value
    
        def __str__(self):
            # Attrs left as an exercise for the reader
            return self.tag_str.format(tag=self._tag_name, text=self._text)
    

    然后您可以使用Document__setattr__ 方法添加基于此类的描述符,如果它在批准名称的白名单中(或不在禁止名称的黑名单中,具体取决于您的域) :

    class Document(object):
        # prelude
        def __setattr__(self, name, value):
            if self.is_allowed(name):  # Again, left as an exercise for the reader
                object.__setattr__(self, name, DocAttribute(name)(value))
    

    【讨论】:

    • 我认为解决方案可能与描述符有关。 叹息。我想是时候真正深入研究并尝试学习如何使用它们了。 (这是我没有花太多时间探索的python的角落之一)。
    • 明确一点——如果描述符没有绑定到实例,它的行为是否与任何其他类不同?
    • @mgilson - 没有丝毫不同。 __get____set____delete__ 方法在您尝试访问具有这些方法的类或实例的属性时调用(并且仅在某些情况下)。请参阅docs.python.org/howto/descriptor.html 了解更多信息。
    • 所以当我们执行 doc.title 时会返回一个 DocAttribute 的实例 - 如果我们执行 title = doc.title 我们可以调用 __str____call__ 的方法 DocAttribute 实例没有问题 - 只有当 title 附加到其他对象时,描述符方法才会发挥作用。
    猜你喜欢
    • 2012-10-13
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多