Python“可调用”属性(伪属性)

mgi*_*son 3 python properties

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

foo.thing = 'baz'
Run Code Online (Sandbox Code Playgroud)

或者:

foo.thing('baz')
Run Code Online (Sandbox Code Playgroud)

有没有一种很好的方法来创建一个接受上述两种形式的类,该类可以扩展到大量具有这种行为的属性?(很快,我将展示一个我不太喜欢的实现示例。)如果您认为这是一个愚蠢的 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>'
Run Code Online (Sandbox Code Playgroud)

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

doc = Document()
doc.title = "Hello World"
print (str(doc))
doc.title("Goodbye World",font="Helvetica")
print (str(doc))
Run Code Online (Sandbox Code Playgroud)

不过,这个实现看起来有点混乱(带有__special_attr)。也许这是因为这是一个混乱的 API。我不知道。 有一个更好的方法吗? 或者说我在这件事上离开了人迹罕至的地方有点太远了?

我意识到我@property也可以用它来实现,但是如果我有不止一个以这种方式表现的属性,那根本无法很好地扩展——我需要为每个属性编写一个 getter 和 setter,哎呀

Pet*_*ten 5

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

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

tmp = obj.attrib
tmp(...)
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

简而言之:你想要的事情不可能透明地完成。如果你不坚持绕过这个限制,你会写出更好的 Python 代码