函数使用
内置函数
内置函数是被内部定义好的一些函数,直接调用即可。
但是要严格传入函数所需要的参数,为此,你可以查看Python内置函数源代码,获得函数签名:
def bin(*args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__
"""
Return the binary representation of an integer.
>>> bin(2796202)
'0b1010101010101010101010'
"""
pass
自定义函数
对于自定义函数来说,与对象声明相同,必须先定义后使用。
以下是语法格式:
def 函数标识符(参数1, 参数2): # ❶
"""功能描述文档""" # ❷
逻辑体代码块 # ❸
return 返回值 # ❹
❶:def为关键字,用于定义一个函数。函数名即函数标识符,参照变量标识符的命名规则。参数是可选的,可以没有
❷:功能描述文档可有可无,这是提供给用户的帮助信息,可以使用help()函数通过传入函数标识符获得该功能描述文档
❸:函数的逻辑体代码块,用于具体实现某一功能
❹:用于返回给用户需要的结果,如果不返回默认返回None
如何使用
函数标识符指向的是函数对象在内存空间的引用,也就是函数体代码块:
>>> print
<built-in function print>
当加上括号后,将自动调用该函数,此时则根据函数签名需求传入对应参数即可:
>>> print("hello world")
hello world
也就是说,函数标识符不加括号不会调用,只是拿到函数的引用,而加括号后才会执行函数内部的逻辑代码。
定义函数
无参函数
无参函数大多数情况下只是希望该函数单纯的做一点什么事情而并不需要该函数给返回一个结果。
常用于一段特定功能的重复调用,如我想在某种特定的情况下打印出WARNING信息,此时就可以定义一个无参函数:
import sys
import datetime
def warning():
message = str.format(
"WARNING time:<{0}> file:[{1}] - line:[{2}]",
datetime.datetime.now(), # ❶
sys._getframe().f_code.co_filename, # ❷
sys._getframe().f_back.f_lineno # ❸
)
print(message)
warning()
print(datetime.datetime.now())
# WARNING time:<2021-05-17 12:58:18.598996> file:[/Users/Project/demo.py] - line:[13]
❶:获取当前时间
❷:获取被调用函数所在模块文件名
❸:获取被调用函数在被调用时所处代码行数
有参函数
函数的参数用于接收用户传入的对象,可以认为传入的对象是指“待加工原材料”。
当函数执行完成后,通过加工“原材料”将会返回一个加工后的“产品”。
以下示例中将计算2个值的相加结果:
def add(x, y):
res = x + y
return res # ❶
result = add(1, 2)
print(result)
# 3
❶:返回加工完成后的“产品”,该产品被外部使用result标识符进行接收
空函数
只定义函数名并未做具体实现的函数叫做空函数,空函数在逻辑构思中常被用到。
关键词:pass或者Ellipsis对象。
如下所示:
def add(x, y):
pass # ❶
def sub(x, y):
... # ❷
❶:代表省略
❷:Ellipsis对象,代表省略,仅在Python3中适用
函数调用
语句形式
在函数标识符后加上括号进行调用即可:
>>> print("hello world")
hello world
表达式形式
表达式形式可以将函数返回的对象赋值给一个标识符,如下所示:
def add(x, y):
res = x + y
return res
result = add(1, 2)
print(result)
# 3
也可以直接对这个函数返回的对象进行操作,如下所示:
def add(x, y):
res = x + y
return res
result = add(1, 2) * 10
print(result)
# 30
参数调用形式
函数可以作为参数传递给其他函数:
def add(x, y):
res = x + y
return res
result = add(add(1, 2), 3) # ❶
print(result)
# 6
❶:相当于add(3, 3)
函数返回值
默认返回值
Python中的函数一定具有返回值。
如果没有显式的使用return关键字指定返回值,则默认会返回None。
def add(x, y):
...
result = add(1, 2)
print(result)
# None
return返回值
return有2个作用:
- 返回函数的执行结果,可以返回1个,也可以返回多个,若返回多个则以元组形式返回
- 跳出所有的循环体(如果在多层的循环中使用return,将直接跳出所有循环,并且将结果进行返回)
如下示例, 获得指定范围区间的累加结果:
def accumulate(start, stop):
res = 0
for i in range(start, stop + 1):
res += i
return res
result = accumulate(1, 100)
print(result)
# 5050
返回多个值时,将以元组形式进行返回,如下示例中输入一个ASCII码表的字符,返回其ASCII码表中对应的大小写编号(a - z具有2个编号,符号只有1个编号):
def getAscii(char):
asciiTable = {chr(i) : i for i in range(0, 128)}
lowerKey = char.lower()
capitalKey = char.upper()
return asciiTable[lowerKey], asciiTable[capitalKey]
lowInt, capitalInt = getAscii("a")
print(lowInt, capitalInt)
# 98 66
函数执行
函数必须先定义后使用,否则无法执行:
func()
def func():
print("run ...")
# NameError: name 'func' is not defined
嵌套调用
Python是解释性语言,解释一行执行一行。
当碰见def关键字开头的语句,内部会检测一下该代码块的语法,如果没有出现问题则代表该函数定义好了。
只有碰到函数名加括号的情况下才会去执行该函数。
所以,函数也可以进行嵌套调用,不过我个人不太喜欢这种调用方法,我更倾向于传参调用,在代码维护时更加的方便:
def func1():
print("run ... func1")
def func2():
print("run ... func2")
func1()
print("run stop")
func2()
# run ... func2
# run ... func1
# run stop
内部逻辑
当定义一个函数时,会发生3件事情:
- 申请内存空间,保存函数体代码
- 将内存地址编号绑定给函数标识符(函数名)
- 定义函数并不会执行函数体内部代码,但是会检测函数体的语法
当调用一个函数时,也会发生3件事情:
- 通过函数标识符找到函数在内存中存在的位置
- 加上括号是一种语法格式,代表执行函数体内部的代码
- 调用系统调用栈,创建一个函数的栈帧片段并压入栈中
我们以函数嵌套的例子来分析它的定义和执行流程:
def func1():
print("run ... func1")
def func2():
print("run ... func2")
func1()
print("run stop")
func2()
定义函数
在Python解释器解释到到第1行、第3行的时候,会加载函数体代码,检查函数体语法,若无误则将其编译为字节码:
随后会加载函数标识符,func1与func2,并且会在内存中将函数体代码进行保存,让函数标识符与在内存中保存的函数体代码进行绑定:
如上图所示,现在还未开始执行函数,内存中已有func1,func2了。
执行函数
开始分析第9行的执行函数func2(),当Python解释器发现函数标识符加了一个括号后,知道这是要调用函数,会顺着函数名找到在内存中的函数体代码:
当开始真正执行func2()函数体代码后,系统调用栈会创建出一个独一无二的函数栈帧,函数栈帧会将函数的字节码、谁调用的它、以及函数内部定义的变量(如参数)等关键信息进行保存。
在第6行,发现又执行func1(),这个func1()是在func2()内部执行的所以Python解释器会在func2()栈帧的基础上运行func1()的代码,同理也会创建func1()函数的栈帧:
当func1()执行完毕后,发现没有返回值,则加载常量None并做为默认的返回结果,此时会一并弹出func1()的栈帧,常量None将返回给func1()的调用处:
然后func2()也执行完毕,发现没有返回值,加载常量None并做为默认的返回结果,此时会一并弹出func2()的栈帧,常量None将返回给func2()的调用处: