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

mva*_*een 10 python properties built-in

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

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

这是特别诱人的,因为我定义的配置功能集合,像get_port,get_hostname等等,所有这些都可能被替换的更简单,更简洁的财产同行:port,hostname,等.

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

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

TypeError: int() argument must be a string or a number, not 'property'
Run Code Online (Sandbox Code Playgroud)

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

下面有趣的黑客可以在pbs库源代码中找到.它使得能够在模块级别进行类似属性的属性查找,但它非常可怕,非常可怕.

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

下面是将此类插入导入名称空间的代码.它实际上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)
Run Code Online (Sandbox Code Playgroud)

现在我已经看到了pbs必须经历的长度,我想知道为什么Python的这个功能没有直接内置到语言中.该property特别装饰似乎是一个自然的地方加入这样的功能.

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

lvc*_*lvc 8

这与两个因素的组合有关:第一,使用描述符协议实现属性,第二,模块始终是特定类的实例,而不是可实例化的类.

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

  1. 在类中mro搜索具有的类型字典name
  2. 如果第一个匹配项是数据描述符,则调用它__get__并返回其结果
  3. 如果name在实例字典中,则返回其关联值
  4. 如果类字典中存在匹配项并且它是非数据描述符,请调用它__get__并返回结果
  5. 如果类字典中有匹配项,则返回它
  6. raise AttributeError

关键是在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>
Run Code Online (Sandbox Code Playgroud)

你可以看到,.__getattribute__ 被调用,但没有治疗f.blah的描述.

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

  • @mvanveen来自答案中的'描述符协议'链接:"如果一个对象定义了`__get __()`和`__set __()`,它被认为是一个数据描述符.只定义`__get __()`的描述符叫做non - 数据描述符". (2认同)