如何判断一个对象是函数还是方法?
昨天晚上十一点多,在小区门口等外卖,手机震个不停,我们组那个小李在群里冒泡:“东哥,我打印个对象出来是 <built-in method append of list object at 0x...>,这玩意到底算函数还是方法啊?我脑袋嗡的一下——行吧行吧,正好有空,聊两句。就是那个…怎么说呢…Python 里“函数”和“方法”这俩词儿,听着像亲兄弟,其实有几个细小的点,分不清就容易踩坑。
先抛个“能跑”的结论
别急着背概念,直接给你一个小工具,丢到 REPL 里就能用。你们看着像不像探照灯,一照就知道对象的真实身份——函数、方法、还是别的可调用。
import inspect
defwho_are_you(obj):
"""
返回 'function' | 'bound_method' | 'class_method' | 'static_function'
| 'builtin_function' | 'builtin_method' | 'method_descriptor'
| 'callable_object' | 'not_callable'
并尽量补点关键信息,别太死板。
"""
# 不是可调用,直接否
ifnot callable(obj):
return {"kind": "not_callable"}
# 1) 纯 Python 绑定方法:instance.foo / classmethod 绑定到类
if inspect.ismethod(obj):
# classmethod 的 __self__ 指向类;普通绑定方法指向实例
if inspect.isclass(obj.__self__):
return {"kind": "class_method", "bound_to": obj.__self__}
return {"kind": "bound_method", "bound_to": obj.__self__}
# 2) 纯 Python 函数(包括 lambda、模块级函数、定义在类里但“通过类访问”的函数)
if inspect.isfunction(obj):
# 通过 __qualname__ 看看是不是定义在类体里
qn = getattr(obj, "__qualname__", "")
# Foo.bar 这种会带个点
if"."in qn.split("<locals>", 1)[0]:
# 注意:通过类访问到的是“函数”,还没绑定,不是方法
return {"kind": "static_function"if isinstance(
# staticmethod 访问类属性时解包成 function,这里给个提示
inspect.getattr_static(obj.__module__ and
__import__(obj.__module__),
getattr(obj, "__name__", ""), obj),
staticmethod) else"function_in_class"}
return {"kind": "function"}
# 3) C 实现的内建可调用:len、[].append 这种
if inspect.isbuiltin(obj):
# 内建“绑定方法”(如 [].append)有 __self__
self_obj = getattr(obj, "__self__", None)
if self_obj isnotNone:
return {"kind": "builtin_method", "bound_to": self_obj}
return {"kind": "builtin_function"}
# 4) method descriptor:如 list.append(还没绑定时)
if inspect.ismethoddescriptor(obj):
return {"kind": "method_descriptor"}
# 5) 其它实现了 __call__ 的对象(functools.partial、自定义可调用类…)
if hasattr(obj, "__call__"):
return {"kind": "callable_object", "type": type(obj).__name__}
# 兜底
return {"kind": "not_callable"}
我当时让小李把这段一粘,挨个儿试,心里就稳了。等外卖那会儿风呼呼的,我缩着脖子在楼下说:“你就把常见的都扫一遍,理解一下 why 就彻底不怕了。”
我就拿办公室那台老电脑上写过的一段小样儿,改了改给你们看看。你在 Python 3.x 随便跑。
classFoo:
definst(self):# 普通实例方法
return"inst"
@classmethod
defclz(cls):# 类方法
return"clz"
@staticmethod
defstat():# 静态方法(拿到的是函数,不会绑定)
return"stat"
defmod_func():# 模块级函数
return"mod"
import math
import functools
f = Foo()
# 逐个识别
print("f.inst ->", who_are_you(f.inst)) # bound_method, bound_to=<Foo实例>
print("Foo.inst->", who_are_you(Foo.inst)) # function_in_class(未绑定)
print("Foo.clz ->", who_are_you(Foo.clz)) # class_method, bound_to=<class Foo>
print("Foo.stat->", who_are_you(Foo.stat)) # static_function(本质就是 function)
print("mod_func->", who_are_you(mod_func)) # function
print("len ->", who_are_you(len)) # builtin_function
print("[].append->", who_are_you([].append)) # builtin_method, bound_to=<list实例>
print("list.append->", who_are_you(list.append)) # method_descriptor
print("math.sin->", who_are_you(math.sin)) # builtin_function
print("partial ->", who_are_you(functools.partial(mod_func))) # callable_object
你看这输出就明白八九不离十了:带 __self__ 的,一般是“已经绑上某个对象”的方法;没有的,多半就是“纯函数”。而 staticmethod 虽定义在类里,但访问到的依旧是“函数”;classmethod 则一拿就是绑定到类自身的“方法”。内建那拨儿(C 写的)稍微绕点,但原则一样。
就是…Python 里“函数”是“可调用对象”的一种,“方法”是“函数 + 绑定对象”这个组合。类体里写的那个 def foo(self),本质是个函数,只不过它挂在类上当属性时,会通过“描述符”机制,被访问的瞬间自动变成“绑定方法”(给你把 self 绑上),这就像自动配对的充电磁吸头,靠近了就“咔哒”一下。
所以:
obj.foo→ 绑定方法(__self__指向实例)。Cls.foo→ 还是函数(还没绑),除非它是@classmethod。@staticmethod→ 无论从类还是实例拿,拿到的都是函数,不绑。内建类型( list.append)从类上拿到的是 method descriptor;从实例上拿到的是 内建绑定方法。
两个容易误判的小坑
我在公司楼下抽烟那会儿,隔壁楼有个哥们还问:“那 callable 不就行了?”不行,太粗暴了。
可调用不等于函数/方法任何实现了
__call__的对象都是可调用的,比如functools.partial、你自己写的class X: def __call__(self): ...。但它俩都不是“函数”或“方法”。上面的who_are_you已经给它们单拎出来了。拿类属性做静态分析别触发绑定你要是写个框架做反射,经常需要“不触发描述符”的场景。用:
import inspect
raw = inspect.getattr_static(Foo, "inst") # 直接拿到函数对象,不会变成方法这样看“原貌”,再决定怎么处理,省得一不小心就给绑上了。
想更稳?再给你个“是函数 / 是方法”的布尔判断
有些场景就要 True/False,别整那么花哨。我把内建情况也兜住了,包含 classmethod/内建绑定方法。
import inspect
defis_function_like(obj):
"""模块级函数、lambda、staticmethod 解包后的函数、通过类访问到的函数"""
return inspect.isfunction(obj) or (inspect.isbuiltin(obj) and getattr(obj, "__self__", None) isNone)
defis_method_like(obj):
"""
绑定到实例或类的“方法”:
- 纯 Python bound method(types.MethodType)
- @classmethod 访问到的是 bound method(__self__ 为类)
- 内建绑定方法([].append 这种,isbuiltin 且有 __self__)
- method descriptor 也算“方法的一种形态”(类属性层面)
"""
if inspect.ismethod(obj) or inspect.ismethoddescriptor(obj):
returnTrue
if inspect.isbuiltin(obj) and getattr(obj, "__self__", None) isnotNone:
returnTrue
returnFalse
你要是把这两个配上前面的 who_are_you,差不多就能覆盖 99% 现场情况了。剩下 1%…呃,八成是你同事写了个“会调用但不是函数”的怪东西,回头把他的代码拎出来问两句就知道。
现场排查的小心法
就是那个…你们知道吧,我一般这么看:
先 callable(x)判个有无意义。再看 hasattr(x, "__self__")且不为None,这多半是已经绑定了——方法相当稳。不行再上 inspect.ismethod / isfunction / isbuiltin / ismethoddescriptor,把边角料抠干净。真要“看原件”, inspect.getattr_static(cls, name)这招很好使。
行了行了,外卖到了我先吃一口,冷了就难以下咽…哦对,记得把这些小函数丢到你们项目的 utils/inspectors.py 里,下次再有人问“这是函数还是方法”,你就复制粘贴结果给他看,别跟我一样在楼下冻脚了。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领