2012-08-01 80 views
9

tl; dr:属性装饰器如何与类级别的函数定义一起使用,但不能与模块级别的定义一起使用?为什么属性Decorator只能定义类?

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

这是特别诱人的,因为我是定义一组配置功能,如get_portget_hostname,等等,所有这些都可能被替换的更简单,更简洁的财产同行:porthostname

因此,config.get_port()只会是好得多config.port

我很惊讶,当我发现下面的回溯,证明这不是一个可行的选择:

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

我知道我已经在模块级看到过类似于属性的功能,因为我曾经使用优雅但很好用的pbs library来将它用于脚本shell命令。

下面有趣的黑客可以在pbs library source code找到。它可以在模块级别上进行类似属性的属性查找,但这是可怕的,可怕的黑客行为。

# 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装饰者似乎是添加这种功能的自然地方。

是否有任何partiuclar原因或动机为什么这不是直接建立?

+0

为了更容易找到:现在有一个名为mprop的模块,它可以为你做到这一点https://pypi.python.org/pypi/mprop – AbdealiJK 2017-04-16 05:28:35

回答

7

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

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

  1. 搜索通过类mro对于具有name
  2. 如果第一个匹配项是数据描述符类型的字典,调用其__get__并返回结果
  3. 如果name在实例字典,返回其关联值
  4. 如果是从类字典匹配的项目,这是一个非数据描述符,调用其__get__并返回结果
  5. 如果疗法e为从类字典的匹配项,则返回它
  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的描述符。

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

+0

这真的很有帮助,谢谢! – mvanveen 2012-08-01 06:30:18

+1

什么区分数据描述符和非数据描述符? – mvanveen 2012-08-01 06:52:39

+2

@mvanveen在回答中来自'descriptor protocol'链接:“如果一个对象同时定义了__get __()'和'__set __()',它就被认为是一个数据描述符,仅仅定义'__get __()'的描述符被称为非数据描述符“。 – lvc 2012-08-01 07:48:20

0

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

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

进一步信息:Can modules have properties the same way that objects can?

+4

你说的是真的,但明确提到模块不是类是很重要的。它们是(“模块”类型的)实例。 – BrenBarn 2012-08-01 04:33:29

+0

谢谢,这有助于很多,但我仍然感到困惑!我明白一个模块是'ModuleType'的一个实例,但是我仍然无法理解为什么'property'的行为就像它一样。阅读[this](http://www.cafepy.com/article/python_types_and_objects/python_types_and_objects.html#object-type-example)证明是有帮助的,但是我可以继承“ModuleType”的事实使它看起来可行......这是事实,你必须直接修补'sys.modules',这样做你自己的子类似乎hackish。 – mvanveen 2012-08-01 04:57:46

相关问题