在 Python 中定义函数使用 def 关键字,一般格式如下:
def
1234567
def 函数名(参数列表): 函数体def hello(): print('hello') hello()#调用
函数名的命名规则,与变量名类似:
函数名区分大小写。
函数名只能是 字母、数字 或 下划线 的任意组合,不能使用任何的标点符号。
函数名不能以数字开头。
函数名不能与 Python 的关键字同名。
任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
形参指的是形式参数,不是实际存在,是虚拟变量。在定义函数和函数体的时候使用形参,目的是在函数调用时接收实参(实参个数,类型应与实参一一对应)。
实参是指实际参数,调用函数时传给函数的参数,可以是常量,变量,表达式,函数,传给形参。
形参和实参的区别是,形参是虚拟的,不占用内存空间,形参变量只有在被调用时才分配内存单元,实参是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参。
12345678910
import timetimes = time.strftime('%Y-%m-%d')def f(time_str): print('Now time is : %s' % time_str)f(times)
位置参数也叫必备参数,是必须要传给函数的参数,并且在传参时必须以正确的顺序传入函数,调用时的数量必须和函数声明时的一样。
1234567891011
def f(name, age): print('I am %s,I am %d years old.' % (name, age))f('Tom', 18)f('Jerry', 16)'''I am Tom,I am 18 years old.I am Jerry,I am 16 years old.'''
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
def f(name, age): print('I am %s, I am %d years old.' % (name, age))# f(16,'Tom') #报错f(age=16, name='Tom')'''I am Tom, I am 16 years old.'''
默认参数也叫缺省参数,同时也是关键字参数,调用函数时缺省参数的值如果没有传入,则被认为是默认值。默认参数常用于多数情况下对象的值是固定的情况,例如一些女子学校中,学生的性别是固定的。
123456789
def echo_info(name, age, sex='male'): print('-'.center(15, '-')) print('Name:%s' % name) print('Age:%d' % age) print('Sex:%s' % sex)echo_info('Tom', 18)echo_info('Jerry', 40, 'female')
默认参数的陷阱: 在定义函数时,如果默认参数的值是可变数据类型,那么每一次调用函数的时候如果不传入值,就会公用这个数据类型的资源,即每次都操作的是同一个对象。例如:
12345678
def test(names=[]): names.append('Tom') print(names) test()test()test()test()
输出结果:
1234
['Tom']['Tom', 'Tom']['Tom', 'Tom', 'Tom']['Tom', 'Tom', 'Tom', 'Tom']
def atest(k, data={}): data[k] = 'v' print(data)atest(1)atest(2)atest(3)
123
{1: 'v'}{1: 'v', 2: 'v'}{1: 'v', 2: 'v', 3: 'v'}
有时候可能需要一个函数能处理比当初声明时更多的参数,这些参数就叫不定长参数,也叫动态参数。动态参数和位置参数、关键字参数不同,声明时不会命名。例如下面的加法器,能且仅能接收三个参数,并求出和:
12345
def add(x, y, z): print(x + y + z)add(1, 2, 3)
如果在变量名前面加了星号 * ,则变量会以元组的形式存放所有 未命名 的变量参数,例如:
*
1234567891011121314
def add(*args): print('args =', args) the_sum = 0 for i in args: the_sum += i print('the_sum =', the_sum)add(1, 2, 3, 4, 6, 9, 10)'''args = (1, 2, 3, 4, 6, 9, 10)the_sum = 35'''
下面的打印基本信息的函数,最多只能接收三个参数,如果再想添加其他参数就需要重新定义函数的参数:
def echo_info(name, age, sex='male'): print('Name : %s' % name) print('Age : %d' % age) print('Sex : %s' % sex)echo_info('Tom', 18)
而如果在变量名前面加了 ** , 则变量会以字典的形式存放 有命名 的变量参数,例如:
**
123456789101112131415
def echo_info(*args, **kwargs): print('args = ', args) print('kwargs = ', kwargs) for i in kwargs: print('%s : %s' % (i, kwargs[i]))echo_info('Tom', 18, 'male', Job='IT', Height='188')'''args = ('Tom', 18, 'male')kwargs = {'Job': 'IT', 'Height': '188'}Job : ITHeight : 188'''
定义函数时应该 *args 放左边, **kwargs 放右边,下面是错误的写法:
*args
**kwargs
1
def print_info(name, **kwargs, *args): # 报错
按照规范,定义函数时默认参数应该放在位置参数之后。
def test(name, age, *args, sex='fmale', **kwargs):def test(*args, sex='fmale'):# 不规范的写法:# def test(name, age, sex='fmale', *args, **kwargs):# def test(sex='fmale', *args):
调用函数进行传参时,关键字参数应该放在位置(必备)参数之后。
echo_info('Tom', 18, hobby='girl', nationality='Chinese', ability='Python')# echo_info(hobby='girl', 'alex', 18, nationality='Chinese', ability='Python') # 报错# echo_info('Jerry', hobby='girl', 18, nationality='Chinese', ability='Python') # 报错
函数在接收动态参数时,会把接收的参数聚合成一个元组,例如:
def func(*args): print('args =', args)func(1, 2, 3)func(['a', 'b', 'c'])'''args = (1, 2, 3)args = (['a', 'b', 'c'],)'''
而在函数中使用 *args 的方式调用参数时,会把聚合的元组打散,例如:
123456789101112131415161718
def func(*args): print('args =', args) print('*args =', *args)func(1, 2, 3)'''args = (1, 2, 3)*args = 1 2 3'''func(['a', 'b', 'c'])'''args = (['a', 'b', 'c'],)*args = ['a', 'b', 'c']'''
这种方式其实又还原成了最原始传入参数的方式,这种情况最常用的情形是在装饰器内:
def wrapper(funcname): def inner(*args, **kwargs): # 函数接收参数,聚合成一个元组 retval = funcname(*args, **kwargs) # 调用参数,打散元组,还原初始传入形式 print('Something') return retval return inner@wrapperdef func(s): print(s)func([1, 2, 3])
如果传给函数的参数是个列表或元组,还可以使用 * 将其打散之后传入:
def func(*args): print('args =', args)func([1, 2, 3])func(*[1, 2, 3]) # 将列表打散: *[1, 2, 3] < == > 1, 2, 3func(1, 2, 3)'''args = ([1, 2, 3],)args = (1, 2, 3)args = (1, 2, 3)'''
如果函数接收的是 **kwargs ,那么单纯地传入字典会被识别为位置参数,必须打散才能被接收。例如:
def func(**kargs): print('kwargs =', kargs)# func({'Name': 'Jerry'}) # 错误传参func(**{'name': 'Tom'})'''kwargs = {'name': 'Tom'}'''
要想获取函数的执行结果,就可以用 return 语句把结果返回。
return
函数在执行过程中只要遇到 return 语句,就会停止执行并返回结果,也可以理解为 return 语句代表着函数的结束。
如果未在函数中指定 return ,那这个函数的返回值为 None 。
None
return 可以接收多个对象,此时 Python 会将多个对象封装成一个元组,并将这个元组对象返回。
12345678910111213141516171819
#!/usr/bin/env python3# -*- coding: utf-8 -*-def get_os_release(fpath='/etc/redhat-release'): with open(fpath, 'rt', encoding='utf-8') as fr: return fr.readline().strip()x = get_os_release()y = get_os_release('/etc/issue')print(x)print(y)'''CentOS Linux release 7.6.1810 (Core)\S'''
定义了函数后,应该对整个函数作注释而不是逐行注释。以下是建议的注释方式:
def func(name,sex): ''' 函数的作用 参数1:数据类型,用途 参数2:数据类型,用途 ''' pass
从 python 解释器开始执行代码之后,就在内存中开辟了一个空间,每当遇到一个变量的时候,就把变量名和值之间的对应关系记录下来。
但是 当遇到函数定义的时候解释器只是象征性的将函数名读入内存 ,表示知道这个函数的存在了,至于函数内部的变量和逻辑解释器根本不关心。
等执行到函数调用的时候,Python 解释器会 再开辟一块内存来存储这个函数里的内容 ,这个时候才关注函数里面有哪些变量,而函数中的变量会存储在新开辟出来的内存中。函数中的变量只能在函数的内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。
命名空间是指存放名字与值的绑定关系的空间,分为三种:
代码在运行一开始创建的存储“变量名与值的关系”的空间叫做 全局命名空间 。
在函数的运行中开辟的临时的空间叫做 局部命名空间 。
内置命名空间 中存放了python解释器为我们提供的名字:input,print,str,list,tuple…。
这三种命名空间之间的加载顺序为:
内置命名空间(程序运行前加载) => 全局命名空间(程序运行中:从上到下加载) => 局部命名空间(程序运行中:调用时才加载)
取值顺序为:
在局部调用:局部命名空间 => 全局命名空间 => 内置命名空间
在全局调用:全局命名空间 => 内置命名空间
作用域就是作用范围,按照生效范围可以分为两类:
全局作用域:包含 内置命名空间、全局命名空间 ,在整个文件的任意位置都能被引用、全局有效 。
局部作用域: 局部命名空间 ,只能在局部范围 内 生效。
使用 globals() 和 locals() 方法则会以字典类型返回当前位置的全局变量和局部变量:
globals()
locals()
# 全局调用print(globals())print(locals())def func(): a = 12 b = 20 # 局部调用 print(locals()) print(globals())func()
python中的作用域分4种情况:
L:local,局部作用域,即函数中定义的变量;
E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的;
G:global,全局变量,就是模块级别定义的变量;
B:built-in,系统固定模块里面的变量,比如 int , bytearray 等。 搜索变量的优先级顺序依次是:局部作用域 => 外层作用域 => 当前模块中的全局 => Python 内置作用域,也就是 LEGB 。
int
bytearray
x = int(2.9) # int built-ing_count = 0 # globaldef outer(): o_count = 1 # enclosing def inner(): i_count = 2 # local print(o_count) # print(i_count) 找不到 inner()outer()# print(o_count) #找不到
并且, local 和 enclosing 是相对的, enclosing 变量相对上层来说也是 local 。
local
enclosing
在 Python 中只有模块( module ),类( class )以及函数( def 、 lambda )才会引入新的作用域,其它的代码块(如 if 、 try 、 for 等)是不会引入新的作用域的,如下代码中 if 并没有引入一个新的作用域, x 仍处在当前作用域中,后面代码可以使用:
module
class
lambda
if
try
for
x
if 2>1: x = 1print(x) # 1
但是函数 def 、 class 、 lambda 会引入新的作用域:
def test(): x = 2print(x) # NameError: name 'x' is not defined
12345678910111213
x = 6def f2(): print(x) x = 5f2()'''UnboundLocalError: local variable 'x' referenced before assignment'''
上述代码出现错误的原因在于 print(x) 时,解释器会先在局部作用区域找到变量 x 。这是因为在进行函数调用( f2() )之前,其前面的代码包括函数都会先加载到内存,因此执行到 print(x) 会先在局部区域找到 x = 5 ,但 x 的使用出现在 x 的声明前了,所以报错。
print(x)
f2()
x = 5
# x=6def f2(): print(x) # x=5f2()'''NameError: name 'x' is not defined'''
上述代码出现错误的原因在于 print(x) 时,解释器会在局部作用域找,找不到 变量 x 的声明,然后去全局作用域找也找不到,最后在 Python 的内置区域找也找不到,因此会报变量未声明的错误。
123456789101112
x = 6def f2(): x += 1f2()'''UnboundLocalError: local variable 'x' referenced before assignment'''
x += 1 即 x = x + 1 是先加再赋值,解释器会在局部作用域找,会找到 x += 1 (函数已经加载到内存),但 x 的加法运算出现在赋值前了,所以报错。
x += 1
x = x + 1
当内部作用域想修改外部作用域的变量时,就要用到 global 和 nonlocal 关键字了,当修改的变量是在全局作用域(global 作用域)上的,就要使用 global 先声明一下。例如:
global
nonlocal
count = 10def outer(): global count print(count) count = 100 print(count)outer()# 10# 100
关键字 global 声明的变量必须在全局作用域上,不能应用在嵌套作用域上,当要修改嵌套作用域( enclosing 作用域,外层非全局作用域)中的变量怎么办呢,这时就需要 nonlocal 关键字了:
def outer(): count = 10 def inner(): nonlocal count count = 20 print(count) inner() print(count)outer()# 20# 20