一个在交流群里讨论过两轮的问题,答案竟然跟一个 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 “”, line 1, in
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..g’
不足之处
对于嵌套函数(以及在函数内部定义的类),由于无法从外部获得函数的命名空间,因此点式路径无法以动态编程的方式遍历。相比于空的__name__,它对于人类读者还是有些帮助的。

跟__name__属性一样,qualname 属性是静态计算的,不会自动地重新绑定。

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值