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

探讨Python3中如何通过__qualname__属性解决获取类方法所属类的问题,及该属性的引入背景与作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

<think>嗯,用户刚刚问完关于获取当前时间的问题,现在接着问PEP8规范,看来他们可能是在学习Python的基础知识,想要遵循最佳实践来写代码。首先,我需要确认用户对PEP8的了解程度。他们可能已经知道一些基本规范,但想深入了解或者复习。 用户之前得到了关于时间的详细回答,所以可能期望同样详细的结构。我需要分点解释,用例子说明,让内容更清晰。同时,用户可能想知道为什么PEP8重要,以及如何在实际中应用。比如,代码缩进、命名约定、空格使用这些常见问题。 另外,用户可能遇到一些常见的PEP8错误,比如行长度超过79字符,或者导入顺序不对,可以给出检查工具的建议,比如flake8或autopep8,帮助他们自动化处理。这部分实用信息对他们来说应该很有帮助。 还要注意用户可能是在团队协作中遇到代码风格问题,或者准备开源项目,所以强调一致性的重要性。同时,要提到例外情况,比如遵循项目已有的风格,这样用户知道规范不是死板的,需要灵活应用。 最后,总结部分用表格对比,方便用户快速回顾。可能用户需要的是快速查阅,所以结构清晰很重要。要确保回答全面,但不过于冗长,保持信息量适中,用例子和实用工具来增强实用性。</think>感谢你的鼓励!关于 **PEP 8**(Python Enhancement Proposal 8),它是 Python 官方定义的代码风格指南,旨在提高代码的可读性和一致性。以下是其核心要点和常见应用场景: --- ### **1. 代码布局** #### **缩进** - 每级缩进使用 **4 个空格**(不用 Tab)。 - 续行缩进需与包裹元素对齐: ```python # 正确:参数垂直对齐 def long_function_name( var_one, var_two, var_three, var_four): pass # 正确:悬挂缩进(多一层缩进) result = some_function( arg1, arg2, arg3, arg4) ``` #### **行长度** - **最大行宽 79 字符**(宽松场景可放宽至 99)。 - 使用括号、反斜杠或 `\` 隐式换行: ```python # 正确:运算符后换行 total = (first_value + second_value - third_value) ``` #### **空行** - 函数/类定义之间用 **2 个空行**分隔。 - 类内部方法之间用 **1 个空行**分隔。 --- ### **2. 命名规范** | **类型** | **命名风格** | **示例** | |----------------|------------------|-------------------| | 变量/函数/方法 | 小写 + 下划线 | `calculate_total` | | 类 | 驼峰式(首字母大写)| `ClassName` | | 常量 | 全大写 + 下划线 | `MAX_SIZE` | | 受保护成员 | 单下划线开头 | `_internal_var` | | 私有成员 | 双下划线开头 | `__private_var` | --- ### **3. 空格使用** - **运算符两侧**:加空格(除单目运算符): ```python x = y + z # 正确 x=y+z # 错误 ``` - **逗号/冒号后**:加空格: ```python dict = {1: 'a', 2: 'b'} # 正确 ``` - **函数参数**:等号两侧不加空格: ```python def func(a=0, b=1): # 正确 def func(a = 0): # 错误 ``` --- ### **4. 导入规范** - 分多行导入不同库: ```python # 正确:标准库、第三方库、本地库分组,每组按字母排序 import os import sys from flask import Flask import pandas as pd from my_module import MyClass ``` - 禁止通配符导入: ```python from module import * # 避免! ``` --- ### **5. 注释与文档** - **行内注释**:与代码间隔至少 2 空格: ```python x = x + 1 # 补偿边界值 ``` - **文档字符串(Docstring)**: - 使用三引号 `"""` 描述函数/类的用途、参数和返回值。 ```python def add(a, b): """返回两个数的和。 Args: a (int): 第一个数 b (int): 第二个数 Returns: int: a + b 的结果 """ return a + b ``` --- ### **6. 其他规则** - **避免无关的括号**: ```python if x and y: # 正确 if (x and y): # 仅在需要明确优先级时使用 ``` - **比较运算符**:优先用 `is`/`is not` 而非 `==` 比较 `None`: ```python if value is None: # 正确 ``` --- ### **7. 工具支持** - **自动检查**:使用 `flake8`、`pylint` 或 IDE 插件(如 PyCharm 的 PEP8 检查)。 - **自动格式化**:使用 `black`(严格遵循 PEP8)、`autopep8` 或 `yapf`。 --- ### **总结:核心原则** - **可读性优先**:代码是给人读的,其次才是给机器执行。 - **一致性**:团队或项目中需统一风格。 - **灵活调整**:若与 PEP8 冲突但能提升可读性,可适当调整(例如长字符串换行)。 掌握这些规则后,代码将更易维护和协作!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值