Python中的函数与方法 以及python类机制

函数与方法的区别

随着我们越来越频繁使用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实现的

PyObject *

PyMethod_New(PyObject *func, PyObject *self, PyObject *klass)

{

register PyMethodObject *im;  // 定义方法结构体

im = free_list;

if (im != NULL) {

free_list = (PyMethodObject *)(im->im_self);

PyObject_INIT(im, &PyMethod_Type);  // 初始化

numfree--;

}

else {

im = PyObject_GC_New(PyMethodObject, &PyMethod_Type);

if (im == NULL)

return NULL;

}

im->im_weakreflist = NULL;

Py_INCREF(func);

/* 往下开始通过 func 配置 method*/

im->im_func = func;

Py_XINCREF(self);

im->im_self = self;

Py_XINCREF(klass);

im->im_class = klass;

_PyObject_GC_TRACK(im);

return (PyObject *)im;

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

简单来看下代码:

def aa(d, na=None, *kasd, **kassd):

pass

class A(object):

def f(self):

return 1

a = A()

print '#### 各自方法描述 ####'

print '## 函数    %s' % aa

print '## 类方法  %s' % A.f

print '## 实例方法 %s' % a.f

输出结果:

#### 各自方法描述 ####

## 函数 

## 类方法 

## 实例方法 >

Bound Method 和 Unbound Method

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

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

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

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

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

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

[root@iZ23pynfq19Z ~]# cat 33.py

class A(object):

def f(self):

return 123

a = A()

print A.f()

print a.f()

## 命令执行 ##

[root@iZ23pynfq19Z ~]# python -m dis 33.py

1          0 LOAD_CONST              0 ('A')

3 LOAD_NAME                0 (object)

6 BUILD_TUPLE              1

9 LOAD_CONST              1 ()

12 MAKE_FUNCTION            0

15 CALL_FUNCTION            0

18 BUILD_CLASS

19 STORE_NAME              1 (A)

4          22 LOAD_NAME                1 (A)

25 CALL_FUNCTION            0

28 STORE_NAME              2 (a)

5          31 LOAD_NAME                1 (A)

34 LOAD_ATTR                3 (f)

37 CALL_FUNCTION            0

40 PRINT_ITEM

41 PRINT_NEWLINE

6          42 LOAD_NAME                2 (a)

45 LOAD_ATTR                3 (f)

48 CALL_FUNCTION            0

51 PRINT_ITEM

52 PRINT_NEWLINE

53 LOAD_CONST              2 (None)

56 RETURN_VALUE

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

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

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

//取自: python2.7/objects/ceval.c

TARGET(LOAD_ATTR)

{

w = GETITEM(names, oparg);  // 从co_name 取出 f

v = TOP();                  // 将刚才压入栈的 A/a 取出来

x = PyObject_GetAttr(v, w); // 取得真正的执行函数

Py_DECREF(v);

SET_TOP(x);

if (x != NULL) DISPATCH();

break;

}

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

//取自: python2.7/objects/ceval.c

TARGET(CALL_FUNCTION)

{

PyObject **sp;

PCALL(PCALL_ALL);

sp = stack_pointer;

#ifdef WITH_TSC

x = call_function(&sp, oparg, &intr0, &intr1);

#else

x = call_function(&sp, oparg);  // 细节请往下看

#endif

stack_pointer = sp;

PUSH(x);

if (x != NULL) DISPATCH();

break;

}

static PyObject *

call_function(PyObject ***pp_stack, int oparg)

{

int na = oparg & 0xff;                // 位置参数个数

int nk = (oparg>>8) & 0xff;          // 关键位置参数的个数

int n = na + 2 * nk;                  // 总的个数和

PyObject **pfunc = (*pp_stack) - n - 1;  // 当前栈位置-参数个数,得到函数对象

PyObject *func = *pfunc;

PyObject *x, *w;

... // 省略前面细节, 只看关键调用

if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {

/* optimize access to bound methods */

PyObject *self = PyMethod_GET_SELF(func);

PCALL(PCALL_METHOD);

PCALL(PCALL_BOUND_METHOD);

Py_INCREF(self);

func = PyMethod_GET_FUNCTION(func);

Py_INCREF(func);

Py_SETREF(*pfunc, self);

na++;

n++;

} else

Py_INCREF(func);

READ_TIMESTAMP(*pintr0);

if (PyFunction_Check(func))

x = fast_function(func, pp_stack, n, na, nk);

else

x = do_call(func, pp_stack, na, nk);

READ_TIMESTAMP(*pintr1);

Py_DECREF(func);

}

咱们来捋下调用顺序:

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

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

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

// Py_SETREF 定义如下

#define Py_SETREF(op, op2)

do {

PyObject *_py_tmp = (PyObject *)(op);

(op) = (op2);

Py_DECREF(_py_tmp);

} while (0)

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

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

if (PyFunction_Check(func))

x = fast_function(func, pp_stack, n, na, nk);

else

x = do_call(func, pp_stack, na, nk);

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

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

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

class A(object):

def f(self):

return 1

a = A()

print '#### 各自方法等效调用 ####'

print '## 类方法 %s' % A.f()

print '## 实例方法 %s' % a.f()

## 输出结果 ##

#### 各自方法等效调用 ####

Traceback (most recent call last):

File "C:/Users/Administrator/ZGZN_Admin/ZGZN_Admin/1.py", line 20, in

print '## 类方法 %s' % A.f()

TypeError: unbound method f() must be called with A instance as first argument (got nothing instead)

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

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

class A(object):

def f(self):

return 1

a = A()

print '#### 各自方法等效调用 ####'

print '## 类方法 %s' % A.f(a)  #传入A的实例a

print '## 实例方法 %s' % a.f()

## 结果 ##

#### 各自方法等效调用 ####

## 类方法 1

## 实例方法 1

可以看出来, Bound 和 Unbound判断的依据就是, 当方法真正执行时, 有没有传入实例, A.f(a) 和 a.f() 用法的区别只是在于, 第一种需要人为传入实例才能调用, 而第二种, 是虚拟机帮我们做好了传入实例的动作, 不用我们那么麻烦而已, 两种方法本质上是等价的。我有建立一个python学习交流群,在群里我们相互帮助,相互关心,相互分享内容,这样出问题帮助你的人就比较多,群号是301,还有056,最后是051,这样就可以找到大神聚合的群,如果你只愿意别人帮助你,不愿意分享或者帮助别人,那就请不要加了,你把你会的告诉别人这是一种分享。如果你看了觉得还可以的麻烦给我点个赞谢谢

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容