为什么属性装饰器仅为类定义?

时间:2012-08-01 04:23:27

标签: python properties built-in

tl; dr:属性装饰器如何使用类级函数定义,而不是模块级定义?

我将属性装饰器应用于某些模块级函数,认为它们允许我通过属性查找来调用方法。

这特别诱人,因为我正在定义一组配置函数,例如get_portget_hostname等,所有这些函数都可以用更简单,更简洁的属性对象替换:{ {1}},port

因此,hostname只会更好config.get_port()

当我发现以下追溯时,我感到很惊讶,证明这不是一个可行的选择:

config.port

我知道我已经在模块级别看到了类似属性的功能,因为我使用优雅但是hacky pbs library来编写shell命令脚本。

下面有趣的黑客可以在pbs library source code中找到。它使得能够在模块级别执行类似属性的属性查找,但是它非常可怕,非常hackish。

TypeError: int() argument must be a string or a number, not 'property'

以下是将此类插入导入命名空间的代码。它实际上直接修补了# this is a thin wrapper around THIS module (we patch sys.modules[__name__]). # this is in the case that the user does a "from pbs import whatever" # in other words, they only want to import certain programs, not the whole # system PATH worth of commands. in this case, we just proxy the # import lookup to our Environment class class SelfWrapper(ModuleType): def __init__(self, self_module): # this is super ugly to have to copy attributes like this, # but it seems to be the only way to make reload() behave # nicely. if i make these attributes dynamic lookups in # __getattr__, reload sometimes chokes in weird ways... for attr in ["__builtins__", "__doc__", "__name__", "__package__"]: setattr(self, attr, getattr(self_module, attr)) self.self_module = self_module self.env = Environment(globals()) def __getattr__(self, name): return self.env[name]

sys.modules

现在我已经看到了# we're being run as a stand-alone script, fire up a REPL if __name__ == "__main__": globs = globals() f_globals = {} for k in ["__builtins__", "__doc__", "__name__", "__package__"]: f_globals[k] = globs[k] env = Environment(f_globals) run_repl(env) # we're being imported from somewhere else: self = sys.modules[__name__] sys.modules[__name__] = SelfWrapper(self) 必须经历的长度,我想知道为什么Python的这个功能没有直接内置到语言中。特别是pbs装饰器似乎是添加此类功能的自然场所。

为什么不直接建立这个原因还有任何原因或动机吗?

2 个答案:

答案 0 :(得分:8)

这与两个因素的组合有关:第一,使用descriptor protocol实现属性,第二,模块始终是特定类的实例,而不是可实例化的类。

描述符协议的这一部分在object.__getattribute__中实现(相关代码是PyObject_GenericGetAttr,从第1319行开始)。查找规则如下:

  1. 在班级mro中搜索包含name
  2. 的类型字典
  3. 如果第一个匹配项是数据描述符,请调用其__get__并返回其结果
  4. 如果name在实例字典中,则返回其关联值
  5. 如果类字典中存在匹配项并且它是非数据描述符,请调用其__get__并返回结果
  6. 如果类词典中有匹配的项目,请将其返回
  7. raise AttributeError
  8. 关键是第3位 - 如果在实例字典中找到name (就像模块一样),那么它的值将被返回 - 它赢了不测试描述符,并且不会调用它的__get__。这导致了这种情况(使用Python 3):

    >>> class F:
    ...    def __getattribute__(self, attr):
    ...      print('hi')
    ...      return object.__getattribute__(self, attr)
    ... 
    >>> f = F()
    >>> f.blah = property(lambda: 5)
    >>> f.blah
    hi
    <property object at 0xbfa1b0>
    

    您可以看到.__getattribute__ 调用,但f.blah视为描述符。

    规则以这种方式构造的原因很可能是在实例上允许描述符(以及因此在模块中)的有用性与这将导致的额外代码复杂性之间的明确权衡。

答案 1 :(得分:0)

属性是特定于类的特性(具体是新风格类),因此通过扩展,属性修饰器只能应用于类方法。

新式类是从对象派生的类,即类Foo(对象):

更多信息:Can modules have properties the same way that objects can?