14-3 函数基础
一、函数是什么
函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可。Python提供了许多内建函数,比如print()。也可以自己创建函数,这被叫做用户自定义函数。
函数一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的。编程中的函数在英文中也有很多不同的叫法。在BASIC中叫做subroutine(子过程或子程序),在Pascal中叫做procedure(过程)和function,在C中只有function,在Java里面叫做method。
意义:
- 代码重用
- 保持一致性
- 可扩展性
二、函数的创建
1. 格式
Python 定义函数使用 def 关键字,一般格式如下:
def 函数名(参数列表): 函数体 # 以下是一个实例: def hello(): print('hello') hello() # 调用
定义函数时,函数体是不执行的,只是一个声明(会读入内存)。在调用函数时,函数体才执行。
def f(arg, li=[]): li.append(arg) return li n1 = f(1) print(n1) # [1] n2 = f(2,[]) print(n2) # [2] n3 = f(3) print(n3) # [1, 3] # 解释器从上到下解释代码,解释到函数的定义时就创建了一个列表,且li指向此列表的内存地址 # f(2,[]) 是传入了一个新列表 # f(3) 还是用了解释函数时原列表,因为函数定义的过程并没有被重新做,而是已经在内存中了
2. 函数名的命名规则与变量命名规则相同
函数名其实就是个变量名,只是它里面存的是函数代码而已。
x = abs # 给变量 x 赋值 print(x(-1)) # 屏幕输出:1 print(id(abs), id(x)) # 屏幕输出:1608560 1608560
3. 形参和实参
形参:形式参数,不是实际存在,是虚拟变量。在定义函数和函数体的时候使用形参,目的是在函数调用时接收实参(实参个数,类型应与实参一一对应。
实参:实际参数,调用函数时传给函数的参数,可以是常量,变量,表达式,函数,传给形参。
区别:形参是虚拟的,不占用内存空间,形参变量只有在被调用时才分配内存单元,实参是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参。
英文词义: parameter 可数名词,词义是限制因素、决定因素。 argument 这里是可数名词用法,词义是论据、论点、依据。 在编程中: 1、用法规范 While defining method, variables passed in the method are called parameters. 当定义方法时,传递到方法中的变量称为参数. While using those methods, values passed to those variables are called arguments. 当调用方法时,传给变量的值称为引数. 2、简略描述为:parameter=形参(formal parameter), argument=实参(actual parameter) 3. 在不很严格的情况下,现在二者可以混用,一般用argument,而parameter则比较少用。
三、函数的参数
(一)参数的类型
1、定长参数
即在函数定义时进行了声明,在调用函数时必须传入的参数。相当于在函数参数输入处(即接口上)已经占好了坑,调用函数时必须要输入对应的实参。
(1)两种实参传入方式
-
- 基于相对位置的输入:须按声明的形参顺序,依次传入对应的实参,且传入的数量必须和声明时的一样。
- 基于参数名的输入:允许传入实参的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配到参数值。
def f(name, age, job): print('Name:%s Age:%d, Job:%s' %(name, age, job)) f('alex', 18, 'it') # 基于相对位置的输入 f(age=18, name='alex', job='it') # 基于参数名的输入
(2) 默认参数
调用函数时,缺省参数的值如果没有传入,则被认为是默认值。默认参数必须跟在非默认参数之后,否则报错。
def f(name, age=18, job='it'): print('Name:%s Age:%d, Job:%s' %(name, age, job)) f('alex', 18) # 输出 Name:alex Age:18, Job:it
如果默认参数是一个可修改的容器比如一个列表、集合或者字典,可以使用None作为默认值,就像下面这样:
# Using a list as a default value
def spam(a, b=None):
if b is None:
b = []
...
2、 不定长参数
(1)tuple型不定长参数(位置参数):形参(常用args,当然也可以换别的)前加*,允许你传入0个或任意个没有名字的实参,这些实参被自动组装为一个tuple,然后传入函数。
def f1(*args): print (args) f1(11, 22, 'jack') # 输出为:(11, 22, 'jack') li = [11, 22, 'jack'] f1(li) # 输出为:([11, 22, 'Jack'],) f1(*li) # 输出为:(11, 22, 'Jack')
(2)dict型不定长参数(关键字参数):形参(常用kwargs,当然也可以换别的)前加**,允许你传入0个或任意个含关键字的实参,这些关键字实参被自动组装为一个dict,然后传入函数。
def f1(**kwargs): print(kwargs) f1(name='Alex', age=9) # 输出为:{'name': 'Alex', 'age': 9} dic = {'name': 'Jack', 'age': 10 ,'city': 'beijing'} f1(i=dic) # 输出为:{'i': {'name': 'Jack', 'age': 10, 'city': 'beijing'}} f1(**dic) # 输出为:{'name': 'Jack', 'age': 10, 'city': 'beijing'}
(3)tuple型和dict型不定长参数组合——“万能参数”
def f1(*args, **kwargs): print(args, kwargs) f1(11, 22, 33, k1 = 'v1', k2 = 'v2') # 输出为 (11, 22, 33) {'k1': 'v1', 'k2': 'v2'}
3、各种类型参数的组合使用
def f(name,age=18,*args,**kwargs): # 不同类型参数的混用 print('Name:%s' % name) # 输出为 Name:alex print('age:%d' % age) # 输出为 age:18 print('args:',args) # 输出为 () print('kwargs:',kwargs) # 输出为 kwargs: {'hobby': 'girl', 'nationality': 'Chinese', 'ability': 'Python'} f('alex',hobby='girl',nationality='Chinese',ability='Python')
一个*参数只能出现在函数定义中最后一个位置参数后面,而 **参数只能出现在最后一个参数。有种例外,就是—— 强制关键字参数(Keyword-only arguments)。
强制关键字参数放到某个*参数或者单个*后面:
def recv(maxsize, *, block): # 放在单个*后面
pass
recv(1024, True) # TypeError
recv(1024, block=True) # Ok 读代码是看到block=True时,比只看到True,更容易清楚其意义。这也就是强制关键字参数的意义所在。
def mininum(*values, clip=None): # 放在*参数之后面
m = min(values)
if clip is not None:
m = clip if clip > m else m
return m
mininum(1, 5, 2, -5, 10) # Returns -5
mininum(1, 5, 2, -5, 10, clip=0) # Returns 0
(二)其他
1、给函数参数添加元信息
即给函数的参数写注解,提示程序员应该怎样正确使用这一函数。
def add(x:int, y:int) -> int: # 输入的x、y及返回的值都是int类型
return x + y
# 函数被调用时完全等价于:
def add(x, y):
return x + y
四、函数的返回值
要想获取函数的执行结果,就可以用return语句把结果返回
1. 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,so 也可以理解为2.return 语句代表着函数的结束
3. 如果未在函数中指定return,那这个函数的返回值为None
4. return多个对象,解释器会把这多个对象组装成一个元组作为一个一个整体结果输出。
五、作用域
1. 作用域介绍
在Python中,只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域。
python中的作用域分4种情况:
L:local 局部作用域,即函数中定义的变量; E:enclosing 嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的;当然,local和enclosing是相对的,enclosing变量相对上层来说也是local。 G:global 全局变量,就是模块级别定义的变量; B:built-in 系统固定模块里面的变量,比如int, bytearray等。
搜索变量的优先级顺序依次是:局部作用域>外层作用域>当前模块中的全局>python内置作用域,也就是LEGB。
x = int(2.9) # built-in g_count = 0 # global def outer(): o_count = 1 # enclosing def inner(): i_count = 2 # local print(o_count) # print(i_count) NameError: name 'i_count' is not defined inner() outer() # print(o_count) NameError: name 'o_count' is not defined
2. 作用域的修改
x=6 def f2(): print(x) # 报错 local variable 'x' referenced before assignment x=5 f2() ''' 报错的原因在于print(x)时,解释器会在局部作用域找x,发现在x=5处才定义的x,怎么能先去调用呢?所以报错 为什么能找到x=5?尽管函数还没执行到这一句,但是函数本身已经加载到内存了。 上例说明:局部作用域只能查询局部变量 '''
当内部作用域想修改外部作用域的变量时怎么办?就要用到global和nonlocal关键字了
(1)global 关键字
当修改的变量是在全局作用域(global作用域)上的,就要使用global先声明一下,代码如下:
x = 6 def f2(): global x print(x) x = 5 f2() print(x)
(2)nonlocal 关键字
global关键字声明的变量必须在全局作用域上,不能嵌套作用域上,当要修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量时,用nonlocal关键字。
def outer(): count = 10 def inner(): nonlocal count print(count) count = 20 inner() print(count) outer() #print(count) NameError: name 'count' is not defined
注意:对于一个变量,内部作用域变量先声明就会覆盖外部变量的声明,不声明可以直接使用作用域变量的值,但不能改。
浙公网安备 33010602011771号