Python中的函数与方法 以及Bound Method和Unbound Method

简介:

Python中的函数与方法 以及Bound Method和Unbound Method


函数与方法的区别

随着我们越来越频繁使用Python, 我们难免会接触到类, 接触到类属性和方法.但是很多新手包括我, 不知道方法 和 函数 的区别,这次简单来讨论下, 如果有哪里认识不正确, 希望大神提点指教!

先来看两个定义吧:

function(函数) —— A series of statements which returns some value toa caller. It can also be passed zero or more arguments which may beused in the execution of the body.

method(方法) —— A function which is defined inside a class body. Ifcalled as an attribute of an instance of that class, the methodwill get the instance object as its first argument (which isusually called self).

从上面可以看出, 别的编程语言一样, Function也是包含一个函数头和一个函数体, 也同样支持0到n个形参,而Method则是在function的基础上, 多了一层类的关系, 正因为这一层类, 所以区分了function 和 method.而这个过程是通过 PyMethod_New实现的

 
 
  1. PyObject * 
  2. PyMethod_New(PyObject *func, PyObject *self, PyObject *klass) 
  3.     register PyMethodObject *im;   // 定义方法结构体 
  4.     im = free_list; 
  5.     if (im != NULL) { 
  6.         free_list = (PyMethodObject *)(im->im_self); 
  7.         PyObject_INIT(im, &PyMethod_Type);  // 初始化 
  8.         numfree--; 
  9.     } 
  10.     else { 
  11.         im = PyObject_GC_New(PyMethodObject, &PyMethod_Type); 
  12.         if (im == NULL
  13.             return NULL
  14.     } 
  15.     im->im_weakreflist = NULL
  16.     Py_INCREF(func); 
  17.      
  18.     /* 往下开始通过 func 配置 method*/ 
  19.     im->im_func = func; 
  20.     Py_XINCREF(self); 
  21.     im->im_self = self; 
  22.     Py_XINCREF(klass); 
  23.     im->im_class = klass; 
  24.     _PyObject_GC_TRACK(im); 
  25.     return (PyObject *)im; 

所以本质上, 函数和方法的区别是: 函数是属于 FunctionObject, 而 方法是属 PyMethodObject

简单来看下代码:

 
 
  1. def aa(d, na=None, *kasd, **kassd): 
  2.     pass 
  3. class A(object): 
  4.     def f(self): 
  5.         return 1 
  6. a = A() 
  7. print '#### 各自方法描述 ####' 
  8. print '## 函数     %s' % aa 
  9. print '## 类方法   %s' % A.f 
  10. print '## 实例方法 %s' % a.f  

输出结果:

 
 
  1. #### 各自方法描述 #### 
  2. ## 函数   <function aa at 0x000000000262AB38> 
  3. ## 类方法   <unbound method A.f> 
  4. ## 实例方法 <bound method A.f of <__main__.A object at 0x0000000002633198>>  

Bound Method 和 Unbound Method

method 还能再分为 Bound Method 和 Unbound Method, 他们的差别是什么呢? 差别就是Bound method 多了一个实例绑定的过程!

A.f 是 unbound method, 而 a.f 是 bound method, 从而验证了上面的描述是正确的!

看到这, 我们应该会有个问题:

方法的绑定, 是什么时候发生的? 又是怎样的发生的?

带着这个问题, 我们继续探讨.很明显, 方法的绑定, 肯定是伴随着class的实例化而发生,我们都知道, 在class里定义方法, 需要显示传入self参数, 因为这个self是代表即将被实例化的对象。

我们需要dis模块来协助我们去观察这个绑定的过程:

 
 
  1. [root@iZ23pynfq19Z ~]# cat 33.py 
  2. class A(object): 
  3.     def f(self): 
  4.         return 123 
  5. a = A() 
  6. print A.f() 
  7. print a.f() 
  8.   
  9. ## 命令执行 ## 
  10. [root@iZ23pynfq19Z ~]# python -m dis 33.py 
  11.   1           0 LOAD_CONST               0 ('A'
  12.               3 LOAD_NAME                0 (object) 
  13.               6 BUILD_TUPLE              1 
  14.               9 LOAD_CONST               1 (<code object A at 0x7fc32f0b5030, file "33.py", line 1>) 
  15.              12 MAKE_FUNCTION            0 
  16.              15 CALL_FUNCTION            0 
  17.              18 BUILD_CLASS         
  18.              19 STORE_NAME               1 (A) 
  19.   
  20.   4          22 LOAD_NAME                1 (A) 
  21.              25 CALL_FUNCTION            0 
  22.              28 STORE_NAME               2 (a) 
  23.   
  24.   5          31 LOAD_NAME                1 (A) 
  25.              34 LOAD_ATTR                3 (f) 
  26.              37 CALL_FUNCTION            0 
  27.              40 PRINT_ITEM           
  28.              41 PRINT_NEWLINE       
  29.   
  30.   6          42 LOAD_NAME                2 (a) 
  31.              45 LOAD_ATTR                3 (f) 
  32.              48 CALL_FUNCTION            0 
  33.              51 PRINT_ITEM           
  34.              52 PRINT_NEWLINE       
  35.              53 LOAD_CONST               2 (None) 
  36.              56 RETURN_VALUE  

dis输出说明: 第一列是代码的函数, 第二列是指令的偏移量, 第三列是可视化指令, 第四列是参数, 第五列是指令根据参数计算或者查找的结果

咱们可以看到 第4列 和第五列, 分别就是对应: print A.f() 和 print a.f()

他们都是同样的字节码, 都是从所在的codeobject中的co_name取出参数对应的名字, 正因为参数的不同, 所以它们分别取到 A 和 a,下面我们需要来看看 LOAD_ATTR 的作用是什么:

 
 
  1. //取自: python2.7/objects/ceval.c 
  2.         TARGET(LOAD_ATTR) 
  3.         { 
  4.             w = GETITEM(names, oparg);  // 从co_name 取出 f 
  5.             v = TOP();                  // 将刚才压入栈的 A/a 取出来 
  6.             x = PyObject_GetAttr(v, w); // 取得真正的执行函数 
  7.             Py_DECREF(v); 
  8.             SET_TOP(x); 
  9.             if (x != NULL) DISPATCH(); 
  10.             break; 
  11.         }  

通过 SET_TOP, 已经将我们需要真正执行的函数压入运行时栈, 接下来就是通过CALL_FUNCTION 来调用这个函数对象, 继续来看看具体过程:

 
 
  1. //取自: python2.7/objects/ceval.c 
  2. TARGET(CALL_FUNCTION) 
  3.         { 
  4.             PyObject **sp; 
  5.             PCALL(PCALL_ALL); 
  6.             sp = stack_pointer; 
  7. #ifdef WITH_TSC 
  8.             x = call_function(&sp, oparg, &intr0, &intr1); 
  9. #else 
  10.             x = call_function(&sp, oparg);  // 细节请往下看 
  11. #endif 
  12.             stack_pointer = sp; 
  13.             PUSH(x); 
  14.             if (x != NULL) DISPATCH(); 
  15.             break; 
  16.         } 
  17.        
  18. static PyObject * 
  19. call_function(PyObject ***pp_stack, int oparg)     
  20.     int na = oparg & 0xff;                // 位置参数个数 
  21.     int nk = (oparg>>8) & 0xff;           // 关键位置参数的个数 
  22.     int n = na + 2 * nk;                  // 总的个数和 
  23.     PyObject **pfunc = (*pp_stack) - n - 1;  // 当前栈位置-参数个数,得到函数对象 
  24.     PyObject *func = *pfunc;   
  25.     PyObject *x, *w; 
  26.     ... // 省略前面细节, 只看关键调用 
  27.     if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) { 
  28.             /* optimize access to bound methods */ 
  29.             PyObject *self = PyMethod_GET_SELF(func); 
  30.             PCALL(PCALL_METHOD); 
  31.             PCALL(PCALL_BOUND_METHOD); 
  32.             Py_INCREF(self); 
  33.             func = PyMethod_GET_FUNCTION(func); 
  34.             Py_INCREF(func); 
  35.             Py_SETREF(*pfunc, self); 
  36.             na++; 
  37.             n++; 
  38.         } else 
  39.             Py_INCREF(func); 
  40.         READ_TIMESTAMP(*pintr0); 
  41.         if (PyFunction_Check(func)) 
  42.             x = fast_function(func, pp_stack, n, na, nk); 
  43.         else 
  44.             x = do_call(func, pp_stack, na, nk); 
  45.         READ_TIMESTAMP(*pintr1); 
  46.         Py_DECREF(func); 
  47. }  

咱们来捋下调用顺序:

 
 
  1. CALL_FUNCTION -> call_function -> 根据函数的类型 -> 执行对应的操作 

当程序运行到call_function时, 主要有的函数类型判断有: PyCFunction, PyMethod, PyFunction

在这里, 虚拟机已经判断出func是不属于PyCFunction, 所以将会落入上面源码的判断分支中, 而它将要做的,就是分别通过 PyMethod_GET_SELF, PyMethod_GET_FUNCTION 获得self对象和func函数, 然后通过调用 Py_SETREF(*pfunc, self):

 
 
  1. // Py_SETREF 定义如下 
  2. #define Py_SETREF(op, op2)                       
  3.     do {                                         
  4.         PyObject *_py_tmp = (PyObject *)(op);   
  5.         (op) = (op2);                           
  6.         Py_DECREF(_py_tmp);                     
  7.     } while (0)  

可以看出, Py_SETREF是用这个self对象替换了pfunc指向的对象了, 而pfunc在上面已经提及到了, 就是当时压入运行时栈的函数对象. 除了这几步, 还有更重要的就是, na 和 n 都分别自增1

看回上面的 a.f(), 咱们可以知道, 它是不需要参数的, 所以理论上 na,nk和n都是0, 但是因为f是method(方法), 经过上面一系列操作, 它将会传入一个self,而na也会变成1, 又因为*pfunc已经被替换成self, 相应代码:

 
 
  1. if (PyFunction_Check(func)) 
  2.             x = fast_function(func, pp_stack, n, na, nk); 
  3.         else 
  4.             x = do_call(func, pp_stack, na, nk);  

所以它不再进入function的寻常路了, 而是走do_call, 然后就开始真正的调用;

其实这个涉及到Python调用函数的整个过程, 因为比较复杂, 后期找个时间专门谈谈这个

聊到这里, 我们已经大致清楚, 一个method(方法) 在调用时所发生的过程.明白了函数和方法的本质区别, 那么回到主题上 来说下 Unbound 和 Bound, 其实这两者差别也不大. 从上面我们得知, 一个方法的创建, 是需要self, 而调用时, 也会使用self,而只有实例化对象, 才有这个self, class是没有的, 所以像下面的执行, 是失败的额

 
 
  1. class A(object): 
  2.     def f(self): 
  3.         return 1 
  4. a = A() 
  5.   
  6. print '#### 各自方法等效调用 ####' 
  7. print '## 类方法 %s' % A.f() 
  8. print '## 实例方法 %s' % a.f() 
  9.   
  10. ## 输出结果 ## 
  11. #### 各自方法等效调用 #### 
  12. Traceback (most recent call last): 
  13.   File "C:/Users/Administrator/ZGZN_Admin/ZGZN_Admin/1.py", line 20, in <module> 
  14.     print '## 类方法 %s' % A.f() 
  15. TypeError: unbound method f() must be called with A instance as first argument (got nothing instead)  

错误已经很明显了: 函数未绑定, 必须要将A的实例作为第一个参数

既然它要求第一个参数是 A的实例对象, 那我们就试下修改代码:

 
 
  1. class A(object): 
  2.     def f(self): 
  3.         return 1 
  4. a = A() 
  5.   
  6. print '#### 各自方法等效调用 ####' 
  7. print '## 类方法 %s' % A.f(a)   #传入A的实例a 
  8. print '## 实例方法 %s' % a.f() 
  9.   
  10. ## 结果 ## 
  11. #### 各自方法等效调用 #### 
  12. ## 类方法 1 
  13. ## 实例方法 1  

可以看出来, Bound 和 Unbound判断的依据就是, 当方法真正执行时, 有没有传入实例, A.f(a) 和 a.f() 用法的区别只是在于, 第一种需要人为传入实例才能调用, 而第二种, 是虚拟机帮我们做好了传入实例的动作, 不用我们那么麻烦而已, 两种方法本质上是等价的。


作者:佚名

来源:51CTO

目录
打赏
0
0
0
0
16429
分享
相关文章
Python 中调用 DeepSeek-R1 API的方法介绍,图文教程
本教程详细介绍了如何使用 Python 调用 DeepSeek 的 R1 大模型 API,适合编程新手。首先登录 DeepSeek 控制台获取 API Key,安装 Python 和 requests 库后,编写基础调用代码并运行。文末包含常见问题解答和更简单的可视化调用方法,建议收藏备用。 原文链接:[如何使用 Python 调用 DeepSeek-R1 API?](https://apifox.com/apiskills/how-to-call-the-deepseek-r1-api-using-python/)
Python入门:8.Python中的函数
### 引言 在编写程序时,函数是一种强大的工具。它们可以将代码逻辑模块化,减少重复代码的编写,并提高程序的可读性和可维护性。无论是初学者还是资深开发者,深入理解函数的使用和设计都是编写高质量代码的基础。本文将从基础概念开始,逐步讲解 Python 中的函数及其高级特性。
Python入门:8.Python中的函数
随机的暴力美学蒙特卡洛方法 | python小知识
蒙特卡洛方法是一种基于随机采样的计算算法,广泛应用于物理学、金融、工程等领域。它通过重复随机采样来解决复杂问题,尤其适用于难以用解析方法求解的情况。该方法起源于二战期间的曼哈顿计划,由斯坦尼斯拉夫·乌拉姆等人提出。核心思想是通过大量随机样本来近似真实结果,如估算π值的经典示例。蒙特卡洛树搜索(MCTS)是其高级应用,常用于游戏AI和决策优化。Python中可通过简单代码实现蒙特卡洛方法,展示其在文本生成等领域的潜力。随着计算能力提升,蒙特卡洛方法的应用范围不断扩大,成为处理不确定性和复杂系统的重要工具。
70 21
Python3 自定义排序详解:方法与示例
Python的排序功能强大且灵活,主要通过`sorted()`函数和列表的`sort()`方法实现。两者均支持`key`参数自定义排序规则。本文详细介绍了基础排序、按字符串长度或元组元素排序、降序排序、多条件排序及使用`lambda`表达式和`functools.cmp_to_key`进行复杂排序。通过示例展示了如何对简单数据类型、字典、类对象及复杂数据结构(如列车信息)进行排序。掌握这些技巧可以显著提升数据处理能力,为编程提供更强大的支持。
35 10
Python中使用MySQL模糊查询的方法
本文介绍了两种使用Python进行MySQL模糊查询的方法:一是使用`pymysql`库,二是使用`mysql-connector-python`库。通过这两种方法,可以连接MySQL数据库并执行模糊查询。具体步骤包括安装库、配置数据库连接参数、编写SQL查询语句以及处理查询结果。文中详细展示了代码示例,并提供了注意事项,如替换数据库连接信息、正确使用通配符和关闭数据库连接等。确保在实际应用中注意SQL注入风险,使用参数化查询以保障安全性。
|
1月前
|
[oeasy]python057_如何删除print函数_dunder_builtins_系统内建模块
本文介绍了如何删除Python中的`print`函数,并探讨了系统内建模块`__builtins__`的作用。主要内容包括: 1. **回忆上次内容**:上次提到使用下划线避免命名冲突。 2. **双下划线变量**:解释了双下划线(如`__name__`、`__doc__`、`__builtins__`)是系统定义的标识符,具有特殊含义。
32 3
|
1月前
|
深入理解 Python 的 eval() 函数与空全局字典 {}
`eval()` 函数在 Python 中能将字符串解析为代码并执行,但伴随安全风险,尤其在处理不受信任的输入时。传递空全局字典 {} 可限制其访问内置对象,但仍存隐患。建议通过限制函数和变量、使用沙箱环境、避免复杂表达式、验证输入等提高安全性。更推荐使用 `ast.literal_eval()`、自定义解析器或 JSON 解析等替代方案,以确保代码安全性和可靠性。
48 2
[oeasy]python061_如何接收输入_input函数_字符串_str_容器_ 输入输出
本文介绍了Python中如何使用`input()`函数接收用户输入。`input()`函数可以从标准输入流获取字符串,并将其赋值给变量。通过键盘输入的值可以实时赋予变量,实现动态输入。为了更好地理解其用法,文中通过实例演示了如何接收用户输入并存储在变量中,还介绍了`input()`函数的参数`prompt`,用于提供输入提示信息。最后总结了`input()`函数的核心功能及其应用场景。更多内容可参考蓝桥、GitHub和Gitee上的相关教程。
18 0
|
2月前
|
Python中的函数是**一种命名的代码块,用于执行特定任务或计算
Python中的函数是**一种命名的代码块,用于执行特定任务或计算
67 18
Python-打印99乘法表的两种方法
本文详细介绍了两种实现99乘法表的方法:使用`while`循环和`for`循环。每种方法都包括了步骤解析、代码演示及优缺点分析。文章旨在帮助编程初学者理解和掌握循环结构的应用,内容通俗易懂,适合编程新手阅读。博主表示欢迎读者反馈,共同进步。

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等