Python技术迷

如何判断一个对象是函数还是方法?

昨天晚上十一点多,在小区门口等外卖,手机震个不停,我们组那个小李在群里冒泡:“东哥,我打印个对象出来是 <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 不就行了?”不行,太粗暴了。

  1. 可调用不等于函数/方法任何实现了 __call__ 的对象都是可调用的,比如 functools.partial、你自己写的 class X: def __call__(self): ...。但它俩都不是“函数”或“方法”。上面的 who_are_you 已经给它们单拎出来了。

  2. 拿类属性做静态分析别触发绑定你要是写个框架做反射,经常需要“不触发描述符”的场景。用:

    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 全部免费领