2

在 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,哎呀

4

2 回答 2

4

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

描述符中存储的任何值都将在所有实例之间共享,因此它不是存储每个实例数据的正确位置。此外,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 代码

于 2012-09-20T06:49:29.683 回答
3

您可以通过简单地创建一个遵循适当规则的描述符类来避免使用@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's__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))
于 2012-09-20T03:18:06.797 回答