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

简介: 这个问题的关键是要使用在 Python 3.3 中引入的__qualname__ 属性,通过它可以获取上层类的名称。铺垫了这么多,开始进入本文的正题了:__qualname__ 属性是什么东西?为什么 Python 3 要特别引入它呢?

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

这个问题看起来不容易理解,我可以给出一个例子:

class Test:
    @xxx
    def foo(self):
        pass
复制代码

现在有一个类和一个类方法,其中类方法上有一个装饰器。

我们的问题就是要在装饰器代码中动态地获得 Test 这个类(类名+类对象)。

这个问题的关键是要使用在 Python 3.3 中引入的__qualname__ 属性,通过它可以获取上层类的名称。

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

原理

一直以来,对于嵌套类的自省,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),通向顶层模块下的对象。函数的局部命名空间在点式路径中由名为  的组件表示。

函数和类的 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 解决了的部分问题,而且代价更高(额外的对象类型和额外的间接寻址,不如用额外的属性)。

目录
相关文章
|
搜索推荐 定位技术
熟练掌握ChatGPT解决复杂问题——学会提问
熟练掌握ChatGPT解决复杂问题——学会提问
251 0
|
Go 索引
「连载」最最通俗易懂,并带小说情节的Go语言入门教程(连载四),没有之一,不接受反驳
「连载」最最通俗易懂,并带小说情节的Go语言入门教程(连载四),没有之一,不接受反驳
|
4月前
ChatGPT提问获取高质量答案的艺术PDF下载书籍推荐分享
**掌握ChatGPT高质量Prompt技巧的指南,教你艺术性提问以获取卓越答案。适用于各层次用户,提升内容创作效率。了解Prompt工程,作为对话模式触发器,有效引导ChatGPT生成人类般文本。点击获取PDF资源:[ChatGPT提问艺术](https://zhangfeidezhu.com/?p=334)**
52 0
ChatGPT提问获取高质量答案的艺术PDF下载书籍推荐分享
|
算法 关系型数据库 Java
数据库原理第四章课后题答案(第四版)
数据库原理第四章课后题答案(第四版)
251 0
|
Kubernetes Cloud Native JavaScript
「连载」最最通俗易懂,并带小说情节的Go语言入门教程(连载三),没有之一,不接受反驳
「连载」最最通俗易懂,并带小说情节的Go语言入门教程(连载三),没有之一,不接受反驳
|
Go Windows
最最通俗易懂,并带小说情节的Go语言入门教程(连载二),没有之一,不接受反驳
最最通俗易懂,并带小说情节的Go语言入门教程(连载二),没有之一,不接受反驳
|
程序员 Go
最最通俗易懂,并带小说情节的Go语言入门教程,没有之一,不接受反驳
最最通俗易懂,并带小说情节的Go语言入门教程,没有之一,不接受反驳
|
存储 数据库 数据安全/隐私保护
数据库原理第五章课后题答案(第四版)
数据库原理第五章课后题答案(第四版)
114 0
|
前端开发
带你读书之“红宝书”:第三章 语法基础(上)之 关键词后续
带你读书之“红宝书”:第三章 语法基础(上)之 关键词后续
94 0
带你读书之“红宝书”:第三章 语法基础(上)之 关键词后续
|
计算机视觉 Python
交流群里讨论热烈的题目 用Python轻松搞定它!
学编程,很多题目可能并不像看起来那样简单,实践出真知,动手才能发现问题,多思考才能解决问题,切忌眼高手低!
135 0
交流群里讨论热烈的题目 用Python轻松搞定它!

相关实验场景

更多