函数
1 函数基础
1.1 致命三问
- 1、什么是函数
函数就是盛放代码的容器,把实现某一功能的一组代码丢到一个函数中,就做成了一个小工具
具备某一功能的工具 ——–》函数
事先准备工具的过程 ——–》函数的定义
遇到应用场景拿来就用 ——-》函数的调用
2、为何要用函数
在没有用函数之前面临的问题:
1、代码冗余,程序组织结构不清晰、可读性差
2、扩展性差3、如何用函数
原则:先定义、后调用- 定义函数的语法:
1 | def 函数名(参数1,参数2,参数3,...): |
- **如何调用函数:**函数名(1,2,3)
- 先定义、后调用
1
2
3
4
5
6
7def func():
print("start...")
print("xxx")
print("stop...")
func()
func()
func()1.2 函数在定义阶段发生的什么事情
定义函数不执行函数体代码,但是会检测函数体语法1
2
3
4
5
6
7
8
9
10
11
12
13
14# x = 100
# print(x)
def func(): # func=函数的内存地址
print("start...")
x
# if True
# print('ok')
print("xxx")
print("stop...")
# 这个时候不会报错因为语法是正确的
func()
# 执行函数的时候就会报错了,因为x没有定义1.3函数在调用阶段发生的什么事情
先通过函数名找到函数的内存地址,然后函数的内存地址()会触发函数体代码的运行1
2
3
4
5
6def func(): # func=函数的内存地址
print("start...")
print("xxx")
print("stop...")
func()
练习:
1 | #案例1 |
2 函数参数
2.1 分类
- 1、形参:在函数定义阶段括号内定义的变量名,称之为形式参数,简称形参
- 2、实参:在函数调用阶段括号内传入的值,称之为实际参数,简称实参
2.2 形参与实参的关系
在调用函数时,实参的值会绑定给形参名,然后可以在函数内使用,函数调用完毕后,解除绑定1
2
3
4
5
6
7def func(x,y):
x=1
y=2
print(x+y)
m = 1
n = 2
func(m,n)2.3参数详解
2.3.1 位置形参
在函数定义阶段按照从左到右的顺序依次定义的形参,称之为位置形参 - 特点:必须被传值,多一个不行少一个也不行
1
2
3
4
5
6def func(x,y):
print(x+y)
func(1,2)
#下面两种方法都是错误的
func(1,2,3)
func(1,)2.3.2 默认形参
在函数定义阶段就已经为某个形参赋值了,称之为默认形参 - 特点: 在函数定义阶段就已经赋值了,意味着在调用阶段可以不用为其赋值
ps:可以混用位置形参与默认形参,但是位置形参必须在前
1 | def func(x,y=2): |
默认形参需要注意的问题是:
- 1、默认形参的值只在函数定义阶段被赋值一次
- 2、默认形参的值通常应该是不可变类型
案例1
1
2
3
4
5
6
7m = 100
def func(x,y=m): # y -> 100的内存地址
print(x,y)
m = 200
func(1)
#执行结果为100案例2
1
2
3
4
5
6
7
8
9m = [11,22,]
def func(x,y=m): # y -> [11,22,]的内存地址
print(x,y)
# m = 200
m.append(33)
func(11,m)
# 结果11 [11, 22, 33]1
2
3
4
5
6
7
8
9def register(name,x,hobbies=None): # hobbies = []内地址
if hobbies is None:
hobbies = []
hobbies.append(x)
print("%s 的爱好是 %s" %(name,hobbies))
register("egon",'read')
register("liu",'chou')
register("hxx",'tang')2.3.3 位置实参
在函数调用阶段按照从左到右的顺序依次传入的值,称之为位置实参
特点:按照顺序与形参一一对应
1 | def func(x,y): |
2.3.4 关键字实参
在函数调用阶段按照key=value的格式传入的值,称之为关键字实参
特点:可以打乱顺序,但是仍然能够为指定的形参赋值
ps:可以混用位置实参与关键字实参,但是
- 1、位置实参必须在关键字实参前
- 2、不能为同一个形参重复赋值
1
2
3
4
5
6
7
8def func(x,y):
print(x)
print(y)
func(y=2,x=1)
func(1,y=2)
func(y=2,1)
func(1,2,y=3)
2.3.5 *与**在形参与实参中的应用
可变长的参数:可变长指的是在函数调用阶段,实参的个数不固定,而实参是为形参赋值的,所以对应着必须要有一种特殊格式的形参能用来接收溢出的实参
形参中带*:
*会接收溢出的位置实参,然后将其存成元组,然后赋值给紧跟其后的变量名1
2
3
4
5
6
7
8
9
10
11
12
13
14def func(x,*args): # y=(2, 3, 4, 5, 6)
print(x)
print(args)
func(1,2,3,4,5,6)
def my_sum(*args):
res = 0
for num in args:
res += num
print(res)
my_sum(1,2,3,4,5)
my_sum(1,2)1
*后跟的必须是一个可以被for循环遍历的类型,*会将实参打散成位置实参
1
2
3
4
5
6def func(x,y,z):
print(x)
print(y)
print(z)
func(*[11,22,33]) # func(11,22,33)
func(*"hello") # func("h","e","l","l","o")形参中带
:****会接收溢出的关键字实参,然后将其存成字典,然后赋值给紧跟其后的变量名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28def func(x,**kwargs): #y = {'a':1,'b':2,'c':3}
print(x)
print(kwargs)
func(1,a=1,b=2,c=3)
func(1,2,3,a=1,b=2,c=3) # 报错
````
**后跟的必须是一个字典,会将实参打散成关键字实参
```python
def func(x,y,z):
print(x)
print(y)
print(z)
func(**{"x":1,"y":2,"z":3}) # func(z=3,x=1,y=2)
def index(x,y,z):
print(x,y,z)
def wrapper(*args,**kwargs): # args = (1,2,3,4,5,6,7) kwargs= {"a":1,"b":2,"c":3} 此处*和**的作用是聚合
# print(args)
# print(kwargs)
index(*args,**kwargs) # index(*(1,2,3,4,5,6,7),**{"a":1,"b":2,"c":3}) 此处 * 和 **的作用是打散
# index(1,2,3,4,5,6,7,a=1,b=2,c=3)
# wrapper(1,2,3,4,5,6,7,a=1,b=2,c=3)
# wrapper(1,2,3)
wrapper(1,z=3,y=2)命名关键字参数、函数对象、函数嵌套…
2.3.6 命名关键字参数
命名关键字参数:*后定义的参数,必须被传值(有默认值的除外),且必须按照关键字实参的形式传递,可以保证,传入的参数中一定包含某些关键字
1 | def func(x,n=3,*args,m=333,y,**kwargs): |
3 函数对象
函数对象:函数是第一类对象,函数是第一等公民
函数可以被当成变量去用
1 | def func(): # func = 函数的内存地址 |
- 1、可以被赋值
1
2
3
4y = x
f = func
print(f is func)
f() - 2、可以当做参数传给另外一个函数
1
2
3
4
5def foo(a):
print(a)
a()
foo(func) - 3、可以当做函数的返回值
1
2
3
4
5
6def foo(f):
return f
res = foo(func)
# print(res)
res() - 4、可以当做容器类型的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38l = [x,func]
# print(l[0])
print(l)
l[1]()
#示例
def login():
print('登录'.center(50,'*'))
def register():
print('注册'.center(50,'*'))
def transfer():
print('转账'.center(50,'*'))
def withdraw():
print('提现'.center(50,'*'))
func_dic = {
"1":["登录",login],
"2":["注册",register],
"3":["转账",transfer],
"4":["提现",withdraw],
}
while True:
print("0 退出")
for k in func_dic:
print(k,func_dic[k][0])
choice = input("请输入您的命令编号:").strip()
if choice == "0":
break
if choice in func_dic:
func_dic[choice][1]()
else:
print("输入命令错误")
4 函数的嵌套
4.1 函数的嵌套定义
1 | def f1(): |
4.2 函数的嵌套调用
1 | def max2(x,y): |
5 名称空间Namespaces
存放名字与其对应的内存地址的地方
5.1 名称空间分类
- 1、内置名称空间:存放的是python解释器自带的名字。生命周期:解释器启动则产生,解释器关闭则销毁
1
2print(len)
print(input) - 2、全局名称空间:存放的是顶级的名字。生命周期:py程序刚开始运行则立即产生,py程序结束
1
2
3
4
5
6x = 10
if True:
y = 2
def foo():
z = 3 - 3、局部名称空间:存放的是函数内的名字。生命周期:函数调用则产生,函数调用结束则销毁
1
2
3
4
5
6
7
8len = 111
def foo():
len = 222
# print(len)
foo()
print(len)5.2 LEGB
1
2
3
4
5
6
7
8
9
10
11
12
13
14LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__
- locals 是函数内的名字空间,包括局部变量和形参
- enclosing 外部嵌套函数的名字空间(闭包中常见)
- globals 全局变量,函数定义所在模块的名字空间
- builtins 内置模块的名字空间
**总结:**
- 1、名字的访问优先级:基于自己当前所在的位置向外一层一层查找,L-》E-》G-》B
- 2、名称空间的”嵌套”关系是函数定义阶段、扫描语法时生成的,与调用位置无关1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38x = 0
def f1():
def f2():
# x = 2222
def f3():
# x = 3333
print(x)
f3()
x = 1111
f2()
f1()
# # 案例1:
x = 666
def f1():
print(x)
x=111
f1()
# 案例2:
x = 666
def f1():
x = 1111
def f2():
print('f2===>',x)
return f2
f = f1()
def foo():
x=77777777777777777
f()
foo()6 闭包函数
6.1 闭包函数定义
闭函数:被封闭起来的函数=>定义函数内部的函数,闭函数的特点是只能在函数内用
1 | def outter(): |
包函数:该函数引用了一个名字,该名字是来自与e这一层的
总结:闭包函数指的是定义在函数内部的函数引用了一个来自于外层函数作用域中的名字
闭包函数升级:结合函数对象
1 | def outter(): |
6.2 为函数体传参有两种解决方案
- 方案一:直接以参数的形式传入
1
2
3
4def wrapper(x):
print(x)
wrapper(100) - 方案二:
1
2
3
4
5
6
7
8def outter(x):
# x = 100
def wrapper():
print(x)
return wrapper # 千万不要加括号
f = outter(100)
f()7 装饰器
7.1 装饰器致命三问
7.1.1 什么是装饰器
装饰器指的是装饰别人的工具,装饰指的则是为被装饰者添加新功能
但是实现装饰器必须遵循的原则是”开放封闭原则”
开放指的是对拓展新功能是开放的,封闭指的则是对修改源码以及调用方式是封闭的
综上装饰器指的是我们要创建一个工具,该工具可以在遵循原则1和2的前提下还能为被装饰对象添加新功能
- 原则1、不修改被装饰对象源代码
- 原则2、不修改被装饰对象调用方式
7.1.2 为何要用装饰器
7.1.3 如何实现装饰器
装饰器-》函数
被装饰者-》函数
准备被装饰者
1 | import time |
7.2 装饰器的实现
方案一:改变被装饰者的源代码
1 | import time |
方案二:装饰器的功能需要重复编写,代码冗余
1 | import time |
方案三:解决方案二的代码冗余问题,但是存在问题-》装饰器的功能写死了,只能装饰index函数
1 | import time |
方案四:装饰器的功能写活
1 | import time |
方案五:装饰器的功能写活
1 | import time |
方案六、装饰器正确写法
1 | import time |
7.3 装饰器模板
1 | def deco(func): |
案例
1 | #统计时间的装饰器 |
7.4 多装饰器调用
1 | def deco1(func1): # func1 = wrapper2的内存地址 |
7.5 有参装饰器
需求:实现一个用来为被装饰对象添加认证功能的装饰器
1 | #基本形式如下 |
如果我们想提供多种不同的认证方式以供选择,单从wrapper函数的实现角度改写如下
1 | def deco(func): |
函数wrapper需要一个mode参数,而函数deco与wrapper的参数都有其特定的功能,不能用来接受其他类别的参数,可以在deco的外部再包一层函数auth,用来专门接受额外的参数,这样便保证了在auth函数内无论多少层都可以引用到
1 | def auth(mode): |
此时我们就实现了一个有参装饰器,使用方式如下
1 | #先调用auth(mode='file'),得到@deco,deco是一个闭包函数,包含了对外部作用域名字mode的引用,@deco的语法意义与无参装饰器一样 |
7.6 wraps函数
可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性,但对于被装饰之后的函数,查看文档注释
1 |
|
在被装饰之后home=wrapper,查看home.name也可以发现home的函数名确实是wrapper,想要保留原函数的文档和函数名属性,需要修正装饰器
1 | def timer(func): |
按照上述方法实现保留原函数输赢过于麻烦,functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,用发如下
1 | from functools import wraps |
7.7 总结
装饰器模板
- 1、无参装饰器
1
2
3
4
5
6
7
8
9def outter(func):
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper
def index():
pass - 2、有参装饰器
1
2
3
4
5
6
7
8
9
10
11
12def outter2(xxx):
def outter(func):
def wrapper(*args,**kwargs):
xxx
res = func(*args,**kwargs)
return res
return wrapper
return outter
def index():
pass8 迭代器
8.1 迭代器致命三问
8.1.1 什么是迭代器
- 迭代器:迭代取值的工具。
- 迭代:迭代是一个重复的过程,但是每次重复都是基于上一次的结果而来
1
2
3
4
5
6
7names = ["egon","tom","lili"]
def func(items):
i = 0
while i < len(items):
print(items[i])
i += 18.1.2 为何要用迭代器
对于序列类型:字符串、列表、元祖,我们可以使用索引的范式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引测,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器8.1.3 如何用迭代器
- 可迭代的对象
- 内置有
__iter__
方法
- 内置有
- 迭代器对象
- 内置有
__iter__
方法 - 内置有
__next__
方法
调用: 可迭代对象.__iter__()
–> 返回迭代器对象
调用:迭代器对象.__next__()
–> 返回的是下一个值有了迭代器,我们就可以不依赖索引迭代取值了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#示例
#字符串、列表、元祖、字典、集合、文件都是可迭代对象,都有__iter__方法
"abc".__iter__()
[1,2,3].__iter__()
(1,2,3).__iter__()
{'k1':111}.__iter__()
{111,22}.__iter__()
open('b.txt',mode='wb').__iter__()
open('b.txt',mode='wb').__next__()
dic = {'name':'egon','age':18,'gender':'male'}
dic = "hello"
iter_dic = dic.__iter__()
print(iter_dic)
print(iter_dic.__iter__().__iter__().__iter__() is iter_dic) # 返回结果是True,对迭代器对象执行__iter__()方法,返回的结果是它本身
print(iter_dic)
print(iter_dic.__next__()) #等同于next(iter_dic)
print(iter_dic.__next__())
print(iter_dic.__next__())
print(iter_dic.__next__()) # StopIteration1
2
3
4
5
6
7
8
9
10
11dic = {'name':'egon','age':18,'gender':'male'}
len("hello") # "hello".__len__()
iter_dic = iter(dic) # dic.__iter__()
while True:
try:
print(next(iter_dic))
except StopIteration:
break
#这么写太丑陋了,需要我们自己捕捉异常、控制next,python这么牛逼,能不能帮我们解决呢?能,请看for循环
- 内置有
8.2 for循环
1 | #基于for循环,我们可以完全不再依赖索引去取值了 |
注意:
1 | dic = {'name':'egon','age':18,'gender':'male'} |
8.3 总结
迭代器对象优缺点:
- 优点:
- 提供了一种新的、统一的取值方式(可以不依赖于索引以及key的)
- 惰性计算,不耗费内存
- 缺点:
- 取值不够灵活
- 一次性的,只能往后取,无法预知数据的个数
9 生成器
9.1 生成器走起
函数内但凡出现yield关键字,再调用函数不会执行函数体代码,会得到一个生成器对象。生成器就是一种自定义的迭代器。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25#示例
def func():
print('hello1')
yield 1
print('hello2')
yield 2
print('hello3')
yield 3
print('hello4')
g = func()
# print(g)
# g.__iter__()
# g.__next__()
#g也有__iter__、__next__方法,所以生成器可以这么取值
res1 = next(g)
print(res1)
res2 = next(g)
print(res2)
res3 = next(g)
print(res3)
next(g)
实现一个可从1开始可以取到无穷大的生成器
1 | def func(): |
yiled VS return:
相同点:返回值层面都一样
不同点:return只能返回一次值函数就立即结束,而yield可以返回多次值
9.2 列表生成式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17nums = []
for i in range(1,6):
nums.append(i**2)
nums = [i**2 for i in range(1,6)]
nums = ['ok' for i in range(1,6)]
print(nums)
names = ['egon_nb',"lxx_sb","hxx_sb"]
new_l = []
for name in names:
if name.endswith("_sb"):
new_l.append(name)
new_l = [name for name in names if name.endswith("_sb")]
print(new_l)9.3 字典生成式
1
2
3
4
5res = {i:i**2 for i in range(5)}
print(res,type(res))
items = [('k1',111),('k2',222)]
print({k:v for k,v in items})9.4 集合生成式
1
2res = {i for i in range(5)}
print(res,type(res))9.5 生成器表达式
1
2
3
4
5
6
7
8
9res = (i for i in range(5))
print(res,type(res))
print(next(res))
print(next(res))
print(next(res))
print(next(res))
print(next(res))
print(next(res))统计文本文件a.txt中的字符串个数
1 | with open('a.txt',mode='rt',encoding='utf-8') as f: |
面向对象
面向过程编程是一种编写思想or编程范式
面向过程核心是“过程”二字,过程就是流程,流程指的就是先干什么、再干什么、后干什么
基于面向过程编写程序就好比在设计一条条流水线
- 面向过程的优缺点
优点:复杂的问题流程化、进而简单化
缺点:牵一发而动全身,扩展性差
函数递归调用、模块、匿名函数
10 函数的递归调用
10.1 定义
函数的递归调用是函数嵌套调用的一种特殊形式。
具体指的是在调用一个函数的过程中又直接或者间接地调用自己,称之为函数的递归调用
函数的递归调用其实就是用函数实现的循环
1 | #一下就是一个递归的例子 |
10.2 递归深度
1 | import sys |
一个递归的过程应该分为两个阶段:
- 1、回溯:向下一层一层调用
- 2、递推:向上一层一层返回
10.3 应用
- 1、推算年龄
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18## 分析过程:
age(5) = age(4) + 10
age(4) = age(3) + 10
age(3) = age(2) + 10
age(2) = age(1) + 10
age(1) = 18
n > 1 -> age(n) = age(n-1) + 10
n = 1 -> age(1) = 18
##实现
def age(n):
if n == 1:
return 18
return age(n-1) + 10
res = age(5)
print(res) - **2、打印出一个列表中的所有元素
1
2
3
4
5
6
7
8
9
10nums = [1,[2,[3,[4,[5,[6,[7,[8,]]]]]]]]
def func(nums):
for item in nums:
if type(item) is list:
func(item)
else:
print(item)
# func([1,[2,[3,[4,[5,[6,[7,[8,]]]]]]]]) - **3、利用二分法从nums中找出需要导入的元素
1 | nums = [-3,1,5,7,9,11,13,18,22,38,78,98] |
11 匿名函数
11.1 lambda
对比使用def关键字创建的是有名字的函数,使用lambda关键字创建则是没有名字的函数,即匿名函数,语法如下:lambda 参数1,参数2,...: expression
匿名函数与有名函数有相同的作用域,但是匿名意味着引用计数为0,使用一次就释放,所以匿名函数用于临时使用一次的场景,匿名函数通常与其他函数配合使用
1 | ##例1 |
11.2 filter、map、reduce
map、reduce、filter都支持迭代器协议,用来处理可迭代对象,我们以一个可迭代对象array为例来介绍它们三个的用法
array=[1,2,3,4,5]
#要求一:对array的每个元素做平方处理,可以使用map函数map函数可以接收两个参数,一个是函数,另外一个是可迭代对象,具体用法如下
11.2.1 map
1 | map(lambda x:x**2,array) res= |
11.2.2 reduce
要求二:对array进行合并操作,比如求和运算,这就用到了reduce函数
1 | #reduce函数可以接收三个参数,一个是函数,第二个是可迭代对象,第三个是初始值# reduce在python2中是内置函数,在python3中则被集成到模块functools中,需要导入才能使用 |
11.2.3 filter
要求三:对array进行过滤操作,这就用到了filter函数,比如过滤出大于3的元素
1 | filter(lambda x:x>3,array) res= |