一、函数的概念
什么是函数?
- 函数与过程的区别:过程没有返回值,函数有返回值。(过程就是没有返回值的函数,过程返回None) - 过程也是函数
python中函数可以嵌套定义,但是调用外层函数时不会执行内部定义的函数,函数只有在调用的时候才会去执行,要区分调用和定义。
函数返回值
- 如果函数返回值的数量为0 — 返回None
- 如果函数返回值的数量为1 — 返回object
- 如果函数返回值数量大于1 — 返回tuple
二、变量作用域
变量作用域指的是变量生效的范围,主要分为两类:局部变量和全局变量。
- 局部变量
局部变量是定义在函数体内部的变量,只在函数体内部生效。
def testA(): a = 100 print(a) testA() # 100 print(a) # 报错:name 'a' is not defined
变量a是定义在
testA
函数内部的变量,在函数外部访问则立即报错。
局部变量的作用:在函数体内部,临时保存数据,即当函数调用完成后,则销毁局部变量。——栈变量,自动析构。
- 全局变量
全局变量是在函数体内、外都能生效的变量。
# 定义全局变量a a = 100 def testA(): print(a) # 访问全局变量a,并打印变量a存储的数据 def testB(): print(a) # 访问全局变量a,并打印变量a存储的数据 testA() # 100 testB() # 100
如果在函数内修改全局变量的值,应该怎么做呢,请看下面的例子:
a = 100 def testA(): print(a) def testB(): ''' # global 关键字声明a是全局变量 global a a = 200 #这样表示在函数内修改全局变量 ''' a = 200 #定义了一个局部变量,不会修改全局变量的值 print(a) testA() # 100 testB() # 200 print(f'全局变量a = {a}') # 全局变量a = 100
在函数内操作的都是局部变量,生命周期只在函数内,不会影响全局变量,如果局部变量与全局变量同名,相当于重新创建了一个变量,它和全局变量没有任何关系。要想在函数内操作全局变量,需要加global关键字,加了global声明后,在函数体内部操作的与全局变量同名的变量都是指全局变量。
不加global声明的时候,对于可变对象,虽然在函数体内无法对全局变量重新赋值但是可以通过内部方法改变。
- global — 全局作用域中的变量
- nonlocal — 上一级作用域中的变量
g_var = 10 def change_var(): g_var = 11 global g_var print(g_var)
这样会报错,因为这样的话,函数体内会有两个重名的变量,一个是全局变量,一个是局部变量,它们俩都叫g_var。
三、函数的参数
型参与实参的区别:
- 形参变量只有在被调用时才会分配内存,在调用结束后,立即释放分配的内存。因此,形参只在函数内部有效。函数调用结束,返回主调函数之后,就不能再使用形参变量了。
- 实参可以是常量、变量、表达式、函数等等,无论实参是什么类型,在函数调用的时候,都必须指定确定的值,因为要把这个值传递给形参。
函数参数主要有以下几种
(1)位置参数
位置参数:调用函数时根据函数定义的参数位置来传递参数。
def user_info(name, age, gender): print(f'您的名字是{name}, 年龄是{age}, 性别是{gender}') user_info('Su', 20, '女')
使用位置参数传参的时候,传递和定义参数的顺序及个数必须一致。——实参与形参位置一一对应。
(2)关键字参数
函数调用,通过键值对的形式加以指定。可以让函数更加清晰、容易使用,同时也清除了参数的顺序需求。
def user_info(name, age, gender): print(f'您的名字是{name}, 年龄是{age}, 性别是{gender}') user_info('Su', age=20, gender='女') user_info('alex', gender='男', age=16)
注意:函数调用时,如果有位置参数时,位置参数必须在关键字参数的前面,但关键字参数之间不存在先后顺序。
(3)缺省参数
缺省参数也叫默认参数,用于定义函数,为参数提供默认值,调用函数时可不传该默认参数的值(注意:所有位置参数必须出现在默认参数前,包括函数定义和调用)。
def user_info(name, age, gender='男'): print(f'您的名字是{name}, 年龄是{age}, 性别是{gender}') user_info('TOM', 20) user_info('Rose', 18, '女')
函数调用时,如果为缺省参数传值则修改默认参数值;否则使用默认值。
(4)不定长参数
不定长参数也叫可变参数。用于不确定调用的时候会传递多少个参数(不传参也可以)的场景。此时,可用包裹(packing)位置参数,或者包裹关键字参数,来进行参数传递。
- *args – 位置参数,列表
- **kwargs – 关键字参数,字典,键值对
- def func(arg, *args, **kwargs) — 可接收一切参数
包裹位置传递
def user_info(*args): print(args) # ('TOM',) user_info('TOM') # ('TOM', 18) user_info('TOM', 18)
注意:传进的所有参数都会被args变量收集,它会根据传进参数的位置合并为一个元组(tuple),args是元组类型,这就是包裹位置传递。
包裹关键字传递
def user_info(**kwargs): print(kwargs) # {'name': 'TOM', 'age': 18, 'id': 110} user_info(name='TOM', age=18, id=110)
综上:无论是包裹位置传递还是包裹关键字传递,都是一个组包的过程。
(5)引用
在python中,值是靠引用来传递来的。
我们可以用id()
来判断两个变量是否为同一个值的引用。我们可以将id值理解为那块内存的地址标识。
# 1. int类型 a = 1 b = a print(b) # 1 print(id(a)) # 140708464157520 print(id(b)) # 140708464157520 a = 2 print(b) # 1,说明int类型为不可变类型 print(id(a)) # 140708464157552,此时得到是的数据2的内存地址 print(id(b)) # 140708464157520 # 2. 列表 aa = [10, 20] bb = aa print(id(aa)) # 2325297783432 print(id(bb)) # 2325297783432 aa.append(30) print(bb) # [10, 20, 30], 列表为可变类型 print(id(aa)) # 2325297783432 print(id(bb)) # 2325297783432
四、递归
递归是指函数内部自己调用自己,递归具有以下特性:
- 必须有一个明确的结束条件,否则就会无限递归;
- 每一次进入更深一层的递归时,问题的规模相比上次递归都应有所减小;
- 递归的效率较低,递归层次过多会导致栈溢出。在计算机中,函数调用是通过栈这种数据结构实现的,每当进入一个函数调用,栈就会增加一层栈帧,每当函数返回,栈就会减少一层栈帧,因为栈是有大小的,所以多层递归可能导致栈溢出。
五、匿名函数——lambda表达式
使用lambda关键字定义,匿名函数没有函数名 – 赋值给变量,通过变量调用匿名函数。
语法格式:lambda 形参:返回值
- lambda表达式的参数可有可无,函数的参数在lambda表达式中完全适用。
- lambda表达式能接收任何数量的参数但只能返回一个表达式的值。
lambda m_str:m_str+’…’ func = lambda m_str:m_str+’…’ func(‘hello’) #相当于 def func(m_str): return m_str+’…’
六、函数式编程
把函数作为参数传入,这样的函数称为高阶函数,高阶函数是函数式编程的体现。函数式编程就是指这种高度抽象的编程范式。
编程方法论有三种:面向过程、函数式编程、面向对象。
函数式编程: 函数式=编程语言定义的函数+数学意义的函数;函数式就是用编程语言去实现数学函数,这种函数内对象是永恒不变的,没有for循环和while循环,所有循环都由递归实现,也不需要变量去保存状态。
- 没有变量的定义和赋值操作即不需要变量去保存状态;
- 函数即变量,函数可以作为参数、返回值;
#函数做参数 def func1(): return ‘hello’ def func2(arg) print(‘arg is - %s’ %arg) func2(func1) func2(func1()) #函数做返回值 def func1(): pass def func2(): return func1 def func3(): return func1()
在python中,函数名加()就表示运行该函数。如果一个函数接收的参数或者返回值是函数名(是函数名,不是加括号的),那么这个函数就是高阶函数。一定要写函数名,不要加(),因为一加()就运行了。
尾调用
尾调用就是指在函数的最后一步(不是最后一行,是最后一步)调用另一个函数。
尾递归调用优化:在函数的最后一步去递归。
Python内置高阶函数
- map函数 - map(逻辑方法, 可迭代对象) - for循环遍历可迭代对象,并通过逻辑方法处理每一个元素。一般逻辑方法是加减乘除等操作。
print(‘内置函数map’, list(map(lambda x:x+1, [1, 2, 3, 4]))) - reduce函数
python2可直接使用,python3使用reduce需要导入
from functools import reduce
reduce(function, sequence, initial=None)
把列表sequence中的数据通过function压缩在一起成为一个数,可以指定基数initial。 - filter函数 - filter(逻辑方法, 可迭代对象) - 逻辑方法会得到一个bool值,将列表中为True的元素保留,也就是过滤出可迭代对象中符合条件的元素。一般逻辑方法是判断是否符合条件的一个判断表达式,表达式结果是bool。
- map和filter得到的都是一个可迭代对象-迭代器。都是对列表遍历进行操作,map不改变列表顺序和元素个数。reduce是将列表进行合并操作。
Python内置函数
- bytes(‘你好’, encoding=’utf-8’, decode(‘utf-8’)) 对’你好’进行编解码,如果不写,python默认使用utf-8编解码。(ascii/gbk/utf-8)
- chr() 获取ascii码 – ord()
- dir() 获取对象的属性和方法的名字
- divmod(10, 3) 获取10除以3的商和余数
- eval() 将字符串中的数据结构提取出来;得到字符串中的数学运算结果。
- hash() 得到一个哈希值 - 哈希值的长度固定,通过哈希值不能推出原来的变量值,在同一个程序中同一值的哈希值是不变的。(用途:软件在官网上本身就带一个哈希值,当下载软件后会把哈希值一块下载,下载后会计算一个哈希值,如果两个哈希值相同说明软件在中途没有被篡改,是安全的)
- 可哈希数据类型 - 不可变数据类型
- 不可哈希数据类型 – 可变数据类型
- globals() 打印全局变量
- locals() 打印局部变量
- zip() 把两个序列变成一一对应的关系,返回的结果是可迭代的
- max() 高级用法 – 只要是for可迭代,就能通过max取最大值
people = {“su”:17, “liu”:18, “fan”:19}
print(list(max(zip(people.values(), people.keys()))))
#对于键值对,默认是比较key,如果想比较value并打印出键值对
#可以通过zip把键值对翻过来,然后再比较 - list()相当于迭代,可以迭代列表中所有元素,print(list())可以迭代打印
- max()比较的时候是按照位置一个一个往后比较的,所以字典之间无法比较,因为字典是无序的。
- slice() 切片 l[start, end] → s=slice(start, ent) l[s]
- import() 通过字符串的方式导入模块
import 导入模块 import test #test就是一个py文件 test.func() #通过.来使用test.py提供的功能 m = __import__(‘test’) m.func()