10

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'

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

下面有趣的 hack 可以在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]

下面是将此类插入导入命名空间的代码。居然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)

现在我已经了解pbs了必须经历的时间,我想知道为什么 Python 的这种功能没有直接内置到语言中。特别property是装饰器似乎是添加此类功能的自然场所。

为什么这不是直接内置的,有什么特别的原因或动机吗?

4

2 回答 2

8

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

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

  1. 在类中搜索mro具有name
  2. 如果第一个匹配项是数据描述符,则调用它__get__并返回其结果
  3. 如果名称在实例字典中,则返回其关联值
  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>

您可以看到它.__getattribute__ 正在被调用,但没有被视为f.blah描述符。

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

于 2012-08-01T05:55:23.873 回答
0

属性是特定于类(特别是新式类)的特性,因此通过扩展属性装饰器只能应用于类方法。

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

更多信息:模块可以像对象一样具有属性吗?

于 2012-08-01T04:29:54.427 回答