为什么仅为类定义属性Decorator? [英] Why Is The property Decorator Only Defined For Classes?

查看:50
本文介绍了为什么仅为类定义属性Decorator?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

tl; dr:属性修饰符如何与类级函数定义一起工作,而不与模块级定义一起工作?



I正在将属性装饰器应用于某些模块级函数,认为它们可以让我仅通过属性查找来调用方法。



这特别具有诱惑力,因为我正在定义一个集合配置功能,例如 get_port get_hostname 等,所有这些都可以用更简单,更多简短的财产对应对象:端口主机名,等等。



因此, config.get_port()将是更好的 config.port



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

  TypeError:int()参数必须是字符串或数字,而不是属性 

我见过一些先例在模块级具有类属性的功能,就像我曾经使用它优雅但笨拙的 pbs库用来编写Shell命令脚本一样。 p>

下面有趣的hack可以在 pbs库中找到源代码。它能够在模块级别执行类似属性的属性查找,但是它却是骇人听闻的,令人毛骨悚然的。

 #这是这模块周围的薄包装(我们修补sys.modules [__ name__])。 
#这是在用户执行从pbs导入任何东西的情况下
#换句话说,他们只想导入某些程序,而不是整个
#系统路径命令。在这种情况下,我们只需将
#导入查找代理到我们的Environment类
类SelfWrapper(ModuleType):
def __init __(self,self_module):
#这非常丑陋必须复制这样的属性
#,但这似乎是使reload()表现良好的唯一方法
#。如果我在
#__getattr__中使这些属性动态查找,则有时会以奇怪的方式重新加载...
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

 #作为独立脚本运行,如果__name__ == __main__,则启动REPL 

globs = globals()
f_globals = {}
对于[ __builtins __, __ doc __, __ name __, __ package__]:
f_globals [k] = globs [k]
env =环境(f_globals)
run_repl(env)

#我们是从其他
处导入的:
self = sys.modules [__ name__]
sys.modules [__ name__] = SelfWrapper(self)

现在我已经看到 pbs 必须走多长一直以来,我都想知道为什么Python的这种功能没有直接内置到该语言中。 属性装饰器似乎是添加此类功能的自然之所。



是否有任何理由或动机?为什么不直接内置?

解决方案

这与两个因素结合在一起:首先,属性是使用描述符协议实现,其次,模块始终是



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


  1. 在类 mro 中搜索类型具有名称
  2. 的字典
  3. 如果第一个匹配项是数据描述符,则调用其 __ get __ 并返回其结果

  4. 如果名称在实例字典中,则返回其关联值

  5. 如果类字典,并且它是一个非数据描述符,则调用其 __ get __ 并返回结果

  6. 如果类字典,将其返回

  7. raise AttributeError

关键是数字3-如果在实例字典中找到了名称(与模块一样),那么它的值将只返回-不会测试其描述符性,并且不会调用其 __ get __ 。这会导致这种情况(使用Python 3):

 >> F类:
... def __getattribute __(self,attr):
... print('hi')
...返回对象.__ getattribute __(self,attr)
...
>> f = F()
>> f.blah = property(lambda:5)
>> f.blah
hi
<属性对象位于0xbfa1b0>

您可以看到 .__ getattribute __ 调用,但不是 f.blah 视为描述符。



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


tl;dr: How come property decorators work with class-level function definitions, but not with module-level definitions?

I was applying property decorators to some module-level functions, thinking they would allow me to invoke the methods by mere attribute lookup.

This was particularly tempting because I was defining a set of configuration functions, like get_port, get_hostname, etc., all of which could have been replaced with their simpler, more terse property counterparts: port, hostname, etc.

Thus, config.get_port() would just be the much nicer config.port

I was surprised when I found the following traceback, proving that this was not a viable option:

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

I knew I had seen some precedant for property-like functionality at module-level, as I had used it for scripting shell commands using the elegant but hacky pbs library.

The interesting hack below can be found in the pbs library source code. It enables the ability to do property-like attribute lookups at module-level, but it's horribly, horribly hackish.

# 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]

Below is the code for inserting this class into the import namespace. It actually patches sys.modules directly!

# 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)

Now that I've seen what lengths pbs has to go through, I'm left wondering why this facility of Python isn't built into the language directly. The property decorator in particular seems like a natural place to add such functionality.

Is there any partiuclar reason or motivation for why this isn't built directly in?

解决方案

This is related to a combination of two factors: first, that properties are implemented using the descriptor protocol, and second that modules are always instances of a particular class rather than being instantiable classes.

This part of the descriptor protocol is implemented in object.__getattribute__ (the relevant code is PyObject_GenericGetAttr starting at line 1319). The lookup rules go like this:

  1. Search through the class mro for a type dictionary that has name
  2. If the first matching item is a data descriptor, call its __get__ and return its result
  3. If name is in the instance dictionary, return its associated value
  4. If there was a matching item from the class dictionaries and it was a non-data descriptor, call its __get__ and return the result
  5. If there was a matching item from the class dictionaries, return it
  6. raise AttributeError

The key to this is at number 3 - if name is found in the instance dictionary (as it will be with modules), then its value will just be returned - it won't be tested for descriptorness, and its __get__ won't be called. This leads to this situation (using 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>

You can see that .__getattribute__ is being invoked, but isn't treating f.blah as a descriptor.

It is likely that the reason for the rules being structured this way is an explicit tradeoff between the usefulness of allowing descriptors on instances (and, therefore, in modules) and the extra code complexity that this would lead to.

这篇关于为什么仅为类定义属性Decorator?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆