目录

  • 函数的定义
  • 注释增强与函数对象
  • 栈与栈帧(Stack Frame)
  • 函数的参数
  • 偏函数
  • LEGB访问规则


函数的定义

大一学C语言时,老师说函数就是一个功能,后来学习C#后,老师却把函数又叫做方法,这里要有一定的区分,定义在类中的函数称为方法,单独分离出来的称为函数
函数叫function,可以理解为一个可以复用的功能,比如内置电池中的len()

#举例:内置电池中的len对象就是一个函数,其完成的功能是返回输入对象的元素个数
li=[1,2,3]
tu=(1,3,5)
di={1:'w'}
print(len(li),len(tu),len(di))

在python中,函数定义灵活,一般形式为:

def myfunc(param1,param2):
    return param1+param2

直接写函数名得到的是对象本身,调用需要加括号与参数(),对于上面的函数myfunc,直接写函数名会得到对象本身

#>myfunc
#<function myfunc at 0x7fb9625941f0>

函数一定是有返回值的,尽管没有写关键字return,依然会返回一个NoneType类型对象:

function python用法 python的function_function python用法

注释增强与函数对象

在python3中,函数新增特性为注释增强,参数后加冒号‘:’,函数名后加‘->’,其对函数没有实际影响,单纯就是为了注释参数与函数返回的对象,举个例子:

def myfunc(param:'this is param')->'this is return note':
    return param

对于函数对象的理解,先使用dir(function)看一下函数包含的对象:

def myfunc(param:'this is param')->'this is return note':
    return param
    
print(dir(myfunc))

function python用法 python的function_字节码_02


注意到第一个__annotations__对象,其存储的就是注释增强这个特性,验证:print(myfunc.__annotations__) 得到结果为(以字典形式存储):

function python用法 python的function_字节码_03


扩展阅读部分:下面要提及到函数对象中更为重要的__code__,它是函数调用的关键

借助dir(myfunc.__code__)得到__code__下的对象:

function python用法 python的function_python_04


在__code__下有以下几个必要的对象,并有各自的指向:

co_name:函数名

co_object:__code__本身

co_code:函数的字节码(面向解释器的字节码)

co_lnotab:字节码的与源码行号的偏移

co_filename:函数所在的文件名

co_stacksize:函数占用的栈大小

栈与栈帧(Stack Frame)

在“第一课.Python入门与环境介绍”那一篇里提到过,CPython有2个重要的栈:执行栈(存储指令和操作数)和块栈(存储循环和异常信息)
对于函数而言,python的栈又可被分为:用户栈和系统栈
1.用户栈:函数被调用时,会在栈中分配内存以保存变量
2.系统栈:用于机器执行,保存着底层的字节码


额外补充
栈对于指令执行有重要作用,栈与线程绑定,函数的调用需要线程栈存储的上下文和执行状态


重点
那python和栈帧及栈的关系到底是怎样的?
简单理解,首先定义了一个函数,当触发调用函数时,会自动在栈中新建栈帧,然后再把函数的参数和对应代码复制到栈帧中执行,并返回值,执行完毕后,栈帧被回收,相关的对象也被释放
从上文看出,两次调用时,函数的栈帧是不一样的:

#stackframe可以在函数退出后返回栈帧
def stackframe():
    return id(locals())
#第一次调用
frame1=stackframe()
#第二次调用
frame2=stackframe()
print(frame1,frame2)

function python用法 python的function_python_05


sys._current_frames()可返回当前栈帧,并用字典保存:

function python用法 python的function_字节码_06

函数的参数

函数的参数分为6种,先从最简单的位置参数开始
1.位置参数
也叫定位参数,实际输入与定义时的顺序相匹配:

#位置参数:也叫定位参数,输入与参数一一匹配
def addfunc(x,y):
    return x**y

2.关键字参数
关键字参数在定义函数时就已经有赋值,因此在调用函数时可以省略,为了避免和位置参数混乱,位置参数一定在参数列表的最前面:

def addfunc(x,y=6):
    return x**y

关键字参数可以在调用时被覆盖,但最好加上参数名去覆盖:addfunc(2,y=8)3.可选位置参数
有时会发现,内置电池中,某些函数的参数为[…],比如round()中就有[,ndigital],用中括号[…]包含的参数属于可选位置参数,调用时可加可不加(不要和关键字参数混淆,关键字参数是有赋值的),我们自己定义函数时是不能使用可选位置参数的,它只存在于python的内置电池的函数中
4.参数中出现的/符号与*符号
同样在内置电池中会看到,有的参数列表为(param1,/,*,param2=None),一个单独的/符号与*符号有着特殊的意义:
符号/:在此之前的参数都被限制为位置参数
符号*:在此之后的参数都被限制为关键字参数
与可选位置参数一样,这两个符号的参数只属于内置电池的函数,我们是不能用它来定义函数的
5.不定长位置参数(*args)
不定长位置参数也称为可变位置参数,不定长位置参数指的是,不限制位置参数的个数,在函数定义中用*param 表示(习惯上会写成*args):

def addfunc(param,*args):
    print(args)
    return sum(args)+param
addfunc(1,2,3,6,8,23)

容易看出,param就是位置参数,所以不定长位置参数应该是剩下的2,3,6,8,23;

通过print(args)打印出此次调用中的不定长位置参数为:

function python用法 python的function_偏函数_07


可看出,不定长位置参数是用元组形式保存的

6.不定长关键字参数(**kwargs)

不定长关键字参数也称为可变关键字参数,有了前面不定长位置参数的理解,不定长关键字参数就比较容易了,不定长关键字参数可以不限制关键字参数的个数,在函数定义中用**param 表示(习惯上会写成**kwargs):

def information(**kwargs):
    print(kwargs)
information(name='t',tel=139,height=178)

打印print(kwargs)结果为:

function python用法 python的function_字节码_08


可见,不定长关键字参数是用字典形式保存的

不定长位置参数与不定长关键字参数综合实例

先补充一个原则,无论如何,只要有位置参数,位置参数一定在参数列表最前面

下面看一个综合不定长位置参数和不定长关键字参数的实例:

def func(*args,**kwargs):
    print(args)
    print(kwargs)
li=[1,2,3,4]
di={'name':'t','tel':139,'height':178}
func(*li,**di)

在调用时,出现了没见过的符号*和**,说明一下:*代表对列表或元组解包,**代表对字典解包,
相当于func(1,2,3,4,name='t',tel=139,height=178)扩展:函数签名
函数签名针对同一文件下,两个重名的函数,通过获取参数列表判断如果调用函数的话,会执行哪个函数:

#函数签名
from inspect import signature

def func(param1,param2='value'):
    return param1
def func(param1):
    return 0
#获取签名
func_sig=signature(func)
print(func_sig)

对于两个重名函数func,打印签名(参数列表)为:

function python用法 python的function_偏函数_09


很显然,调用func(val)将会使用第二个func函数


解释:如果不通过签名判断,由于第一个func的param2是关键字参数已赋值,是可省略的,这样将分不清func(val)到底使用哪一个函数


偏函数

偏函数:固定了部分参数的函数,说实话,偏函数没多大作用,但是了解一下也好
int()内有个参数base,指转换前的数为几进制,a=0x24,a是一个十六进制数,现要转换为十进制,如果每次都是十六进制转十进制,就每次都要写base=16,所以使用偏函数

import functools

a=0x24 #a是一个十六进制数,现要转换为十进制

hexint=functools.partial(int,base=16)
#下次调用直接用新函数hexint即可,hexint就是一个偏函数
hexint(str(a))

LEGB访问规则

E涉及嵌套函数,目前先说LGB三个空间,在以前的文章里大部分提到globals()和__builtins__,当在函数中时,会存在locals()空间(如果在函数外,locals()也存在,这时的locals()就是globals()空间)
假设有函数:

def func():
    val=5
    print(locals())

执行func(),可看到locals()空间保存了函数内部的对象:

function python用法 python的function_偏函数_10


locals()命名空间位于函数内,在最低层,因此,val变量也可以存在于G,B空间,但举个例子,如果B中有一个对象len,则locals(),globals()空间中就尽量不要再创建len对象,避免引起不必要的冲突;

因为访问规则体现在操作对象的顺序上,从locals()搜索到builtins,遇到对象直接就可以对其进行操作,这也是为什么执行时会先处理函数内部变量的原因

通过实例体会一下:

function python用法 python的function_字节码_11