@classmethod与@staticmethod装饰的方法不被识别为FunctionType的原因及优雅判断方案咨询
为什么@classmethod/@staticmethod不是FunctionType?
其实这个问题很好理解:@classmethod和@staticmethod本质是包装器类,而非函数的子类。当你给函数加上这些装饰器时,它们会把原始函数包裹成对应类的实例(比如classmethod对象或staticmethod对象)——这个实例内部确实持有原始函数,但它本身的类型是包装器类,而非types.FunctionType。
举个直观的例子:当你写@classmethod def cls_method(cls): ...,Python实际执行的逻辑大概是这样的:
def cls_method(cls): ... cls_method = classmethod(cls_method)
最后存入类字典的是classmethod类的实例,不是原始的函数对象,所以isinstance(cls_method, types.FunctionType)自然返回False,但isinstance(cls_method, classmethod)会返回True。
优雅的判断方案:不用穷举装饰器类型
你担心穷举所有装饰器类型不优雅、难维护,这个顾虑完全合理。这里推荐用Python标准库inspect模块里的inspect.isroutine()函数——它专门用来判断一个对象是否是“可调用的例程”,能涵盖普通函数、类方法、静态方法、生成器函数、lambda表达式等所有你想排除的方法类型。
修改你的代码如下:
import inspect import types class MyMeta(type): def __new__(cls, clsname, bases, clsdict): field_names = [ name for name, value in clsdict.items() # ...其他条件... and not inspect.isroutine(value) ] # ...后续逻辑...
inspect.isroutine()的判断逻辑很全面:只要对象是types.FunctionType、types.BuiltinFunctionType、classmethod、staticmethod,或是实现了__call__方法的生成器对象,都会返回True。这样不管未来出现什么新的标准装饰器(只要符合“可调用例程”的定义),这个判断都能生效,彻底解决穷举类型的痛点。
如果你的场景更特殊,需要直接获取包装器内部的原始函数,还可以用inspect.unwrap()函数解开多层装饰器拿到最底层的函数对象,但对你的需求来说,inspect.isroutine()已经足够简洁好用了。
内容来源于stack exchange

