乐趣区

关于python:一个在交流群里讨论过两轮的问题答案竟然跟一个-PEP-有关

Python 中有没有方法通过类办法找到其所属的类?

这个问题看起来不容易了解,我能够给出一个例子:

class Test:
    @xxx
    def foo(self):
        pass

当初有一个类和一个类办法,其中类办法上有一个装璜器。

咱们的问题就是要在装璜器代码中动静地取得 Test 这个类(类名 + 类对象)。

去年 11 月份的时候,我在微信读者群里提出了这个问题,过后引起了小范畴的探讨。

没想到在往年上个月的时候,群里又有人提了同样的问题(我在探讨完结后才看到),而且最终都找到了 stackoverflow 上一个同样的问题:

stackoverflow 上的问题提得很明确:Get defining class of unbound method object in Python 3。然而 unbound method 的叫法曾经不常见了,具体的探讨也就不开展了,感兴趣的同学能够去查阅。

这个问题的要害是要应用在 Python 3.3 中引入的__qualname__ 属性,通过它能够获取下层类的名称。

铺垫了这么多,开始进入本文的正题了:__qualname__ 属性是什么货色?为什么 Python 3 要特地引入它呢?

下文是 PEP-3155 的翻译摘录,分明地阐明了这个属性的前因后果。

残缺内容可在 Github 仓库查看:https://github.com/chinesehuazhou/peps-cn/blob/master/StandardsTrack/3155–%E7%B1%BB%E5%92%8C%E6%96%B9%E6%B3%95%E7%9A%84%E7%89%B9%E5%AE%9A%E5%90%8D%E7%A7%B0.md

——————- 摘录开始 ——————–

原理

始终以来,对于嵌套类的自省,Python 的反对很不够。给定一个类对象,基本不可能晓得它是在某个类中定义的,还是在顶层模块中定义的;而且,如果是前者,也不可能晓得它具体是在哪个类中定义的。尽管嵌套类通常被认为是不太好的用法,但这不应该成为不反对内层自省的理由。

Python 3 因为抛弃了以前的未绑定办法(unbound method),而受到了侮辱性的挫伤。

在 Python 2 中,给出以下定义:

class C:
    def f():
        pass

你能够从 C.f 对象中取得其所属的类:

>>> C.f.im_class
<class '__main__.C'>

这种用法在 Python 3 中曾经没有了:

>>> C.f.im_class
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'im_class'
>>> dir(C.f)
['__annotations__', '__call__', '__class__', '__closure__', '__code__',
'__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__get__', '__getattribute__',
'__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__',
'__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__']

这就限度了用户能够应用的自省能力。当将程序移植到 Python 3 时,它可能会产生一些理论的问题,例如在 Twisted 的外围代码中,就屡次应用到了这种自省办法。此外,这还限度了对 pickle 序列化的反对。

提议

本 PEP 提议在函数和类中增加 __qualname__ 属性。

对于顶层的函数和类,__qualname__ 属性等于__name__ 属性。对于嵌套的类、办法和嵌套函数,__qualname__ 属性蕴含一个点式门路(dotted path),通向顶层模块下的对象。函数的部分命名空间在点式门路中由名为 <locals> 的组件示意。

函数和类的 repr() 和 str() 被批改为应用__qualname__ 而不再是__name__。

嵌套类的示例

>>> class C:
...   def f(): pass
...   class D:
...     def g(): pass
...
>>> C.__qualname__
'C'
>>> C.f.__qualname__
'C.f'
>>> C.D.__qualname__
'C.D'
>>> C.D.g.__qualname__
'C.D.g'

嵌套函数的示例

>>> def f():
...   def g(): pass
...   return g
...
>>> f.__qualname__
'f'
>>> f().__qualname__
'f.<locals>.g'

不足之处

对于嵌套函数(以及在函数外部定义的类),因为无奈从内部取得函数的命名空间,因而点式门路无奈以动静编程的形式遍历。相比于空的__name__,它对于人类读者还是有些帮忙的。

跟__name__属性一样,__qualname__ 属性是动态计算的,不会主动地从新绑定。

探讨

去除模块名称

跟__name__一样,__ qualname__ 不蕴含模块的名称。这使得它不受制于模块别名和从新绑定,也得以在编译期进行计算。

复原 unbound 办法

复原 unbound 办法只能解决此 PEP 解决了的局部问题,而且代价更高(额定的对象类型和额定的间接寻址,不如用额定的属性)。

——————- 摘录完结 ——————–

后记

去年我在浏览 ddt 库对于参数化测试的源码 时,偶尔想到了文章结尾的问题,然而没有作进一步的梳理(仿佛感兴趣的人也不多)。没想到的是在群里又呈现了同样的探讨,这让我意识到这个问题是有价值的。

前几天,我偶然间发现__qualname__ 属性有一个专门的 PEP,所以我就抽空把它翻译进去了——既是一种常识梳理,也是给大家做一个“科普”吧。说不定什么时候,还有人会遇到同样的问题呢,心愿对大家有所帮忙。

更多的 PEP 中文翻译内容,可在 Github 查阅:https://github.com/chinesehua…

退出移动版