python函数
python函数
函数引入
当我们正常情况下需要统计列表中的数据之个数
name_list = ['jason', 'kevin', 'oscar', 'jerry']
print(len(name_list))
当len方法不可以使用后
count = 0
for i in name_list:
count += 1
print(count)
当我们需要在多出进行计数时,就需要将这段代码在不同的位置执行多次
那么这个时候我们就需要用到函数
PS:循环是在同一个位置重复执行代码
函数是在不同位置重复执行代码
我们可以将函数视为可以自动帮我们完成重复执行代码的工具
def my_len():
count = 0
for i in name_list:
count += 1
print(count)
my_len()
函数的语法结构
def 函数名(参数):
”’函数注释”’
函数体代码
return 返回值
1.def
定义函数的关键字
2.函数名
命名等同于变量名
3.参数
可有可无 主要是在使用函数的时候规定要不要外界传数据进来
4.函数注释
类似于使用说明书
5.函数体代码
是整个函数的核心 主要取决于程序员的编写
6.return
使用函数之后可以返回给使用者的数据 可有可无
函数的分类
- 空函数
空函数
空函数的函数体代码为空,一般使用pass或者…补全
空函数主要用于项目前期的功能框架搭建
def register():
“””注册功能”””
pass
- 无参函数
定义函数的时候括号内没有参数
def index():
print(‘from index function’)
- 有参函数
有参函数
定义函数的时候括号内写参数,调用函数的时候括号传参数
def func(a):
print(a)
函数的返回值
1.什么是返回值
调用函数之后返回给调用者的结果
2.如何获取返回值
变量名 赋值符号 函数的调用
res = func() # 先执行func函数 然后将返回值赋值给变量res
3.函数返回值的多种情况
3.1.函数体代码中没有return关键字 默认返回None
3.2.函数体代码有return 如果后面没有写任何东西还是返回None
3.3.函数体代码有return 后面写什么就返回什么
3.4.函数体代码有return并且后面有多个数据值 则自动组织成元组返回
3.5.函数体代码遇到return会立刻结束
函数的参数
形式参数
在函数定义阶段括号内填写的参数,简称’形参’
实际参数
在函数调用阶段括号内填写的参数,简称’实参’
形参与实参的关系
形参类似于变量名,在函数定义阶段可以随便写,但是最好做到见名知意
def register(name,pwd):
pass
实参类似于数据值,在函数调用阶段与形参临时绑定,函数运行结束立刻断开
register(‘jason’,123) 形参name与jason绑定 形参pwd与123绑定python函数之参数
A(函数参数)–>B(位置参数)
A–>C(关键字参数)
A–>D(默认参数)
A–>E(可变长参数)
A–>F(命名关键字参数)
B–>1(位置形参)
B–>2(位置实参)
C–>3(关键字形参)
C–>4(关键字实参)
D–>|本质|3
E–>5(可变长形参)
E–>6(可变长实参)
一.位置参数
位置形参
def func(a,b,c): pass
在上述代码中,a,b,c就是位置形参,即在函数定义阶段,括号中从左往右依次填写的变量名
def func(a,b,c):pass 上述代码的书写格式也可以是这样的,当子代码很简单且只占一行时,可以将子代码跟随在父代码之后
位置实参
def func(a,b,c): pass func(1,2,3)
在上述代码中,a,b,c就是位置实参,即在函数调用阶段,括号中从左往右依次填写的数据值
PS:在位置参数中,型参与实参的数量要保持一致,否则系统会报错
def func(a,b,c): pass func(a=1,b=2,c=3)
上述代码我们称之为关键字传参,即通过形参关键字与实参数据值绑定进行参数传递,这也就是关键字实参
PS:关键字传参时,带有关键字的实参应当跟随在正常的位置实参之后,否则系统报错
错误示例
def func(a,b,c): pass func(b=1,2,3) # 在参数使用过程中,遵循越长越复杂的参数位置越靠后的原则
当我们使用参数时,同一个形参不可以绑定多个实参进行多次赋值
def func(a,b,c): pass func1(1, a=2, b=3) # 当同一个形参绑定多个实参多次赋值时,系统会报错
而实参在实际使用过程中是没有固定含义的,即可以用来传递数据值,也可以用来传递绑定有数据值的变量名
def func(name,password): pass name = 'jason' pwd = 123 func1(name, pwd) # 以上操作等同于 func1(a=name, b=pwd)
二.默认参数
默认参数的本质即关键字形参,默认参数的定义也遵循短的简单的靠前 长的复杂的靠后
def register(name, age, gender='male'): print(f""" --------学员信息---------- 姓名:{name} 年龄:{age} 性别:{gender} ------------------------- """) register('jason', 18) register('kevin', 28) register('lili', 28, 'female') register('lili', 28, gender='female')
在以上代码中,函数定义阶段就已经限定了性别为’male’,而在函数调用阶段,如果我们不设定’gender’对应的实参,就会默认为’male’,我们也可以通过位置传参或者关键字传参来修改默认值为’female’
三.可变长形参
可变长形参可以分为两种:’*’和’**’
可变长形参’*’
当只有可变长参数’*’使用时
def func1(*a): print(a) func1() # 输出() func1(1) # 输出(1,) func1(1,2) # 输出(1, 2)
当位置形参与可变长形参’*’混合使用时
def func2(b, *a): print(a, b) func2() # 程序会报错,因为函数至少需要有一个实参给到形参b func2(1) # 输出() 1 func2(1, 2, 3, 4) # 输出(2, 3, 4) 1
由以上代码我们可以得知,可变长形参’*’的作用是收集函数调用过程中多余的位置实参并将其组织成为元组
可变长形参’**’
当只有可变长形参’**’使用时
def func3(**k): print(k) func3() # 输出{} func3(a=1) # 输出{'a': 1} func3(a=1, b=2, c=3) # 输出{'a': 1, 'b': 2, 'c': 3}
当位置形参与可变长形参’**’混合使用时
func4() # 系统报错,因为函数至少需要有一个实参给到形参a func4(a=1) # 输出 1 {} func4(a=1, b=2, c=3) # 输出 1 {'b': 2, 'c': 3} func4(a=1, b=2, c=3, x='jason', y='kevin') # 输出 1 {'b': 2, 'c': 3, 'x': 'jason', 'y': 'kevin'}
由以上代码我们可以得知,可变长形参’**’的作用是收集函数调用过程中多欲出的关键字实参并将他们的关键字作为键,数据值为值组成键值对后组织成字典
可变长形参’*’与可变长形参’**’联用
def func5(*a, **k): print(a, k) func5() # () {} func5(1, 2, 3) # (1, 2, 3) {} func5(a=1, b=2, c=3) # () {'a': 1, 'b': 2, 'c': 3} func5(1, 2, 3, a=1, b=2, c=3) # (1, 2, 3) {'a': 1, 'b': 2, 'c': 3} # 当这两种可变长形参联用时,几乎可以接收所有的实参 # 由于*和**在函数的形参中使用频率很高 后面跟的变量名推荐使用 *args **kwargs def index(*args,**kwargs):pass def func5(n, *a, **k): print(n, a, k) func5() # 系统报错,函数至少需要有一个实参给到形参n func5(1, 2, 3) # 输出 1 (2, 3) {} func5(111,a=1, b=2, c=3) # 输出 111 () {'a': 1, 'b': 2, 'c': 3} func5(n=111,a=1, b=2, c=3) # 输出 111 () {'a': 1, 'b': 2, 'c': 3} func5(a=1, b=2, c=3, n=111) # 输出 111 () {'a': 1, 'b': 2, 'c': 3} func5(1, 2, 3, a=1, b=2, c=3) # 输出 1 (2, 3) {'a': 1, 'b': 2, 'c': 3}
四.可变长实参
可变长实参’*’
def index(a, b, c): print(a, b, c) l1 = [11, 22, 33] t1 = (33, 22, 11) s1 = 'tom' se = {123, 321, 222} d1 = {'username': 'jason', 'pwd': 123, 'age': 18} index(l1[0], l1[1], l1[2]) index(*l1) # index(11, 22, 33) index(*t1) # index(33, 22, 11) index(*s1) # index('t','o','m') index(*se) # index(321 123 222) index(*d1) # index('username','pwd','age')
可变长实参中’*’的作用类似于for循环,将所有可遍历的数据类型中的数据值一个个取出来一次性传递给函数并赋值给形参,由于字典的特殊性,遍历出来的只有K键
可变长实参’**’
def index(username, pwd, age): print(username, pwd, age) d1 = {'username': 'jason', 'pwd': 123, 'age': 18} index(**d1) # index(username='jason',pwd=123,age=18) index(username=d1.get('username'), pwd=d1.get('pwd'), age=d1.get('age')) # 上一步操作等同于这一步操作
可变长实参中’**’的作用相当于将字典中的键值对转化为关键字实参,K为关键字,V为数据值
五.命名关键词参数
def index(name, gender='male', *args, **kwargs): print(name, args, gender, kwargs) index('jason', 1, 2, 3, 4, 'female', b=2)
按照越复杂的越靠后的原则排列形参,但是在如上的代码环境下,我们得到的结果是
jason (2, 3, 4, 'female') 1 {'b': 2}, ,
这种情况下,输出的结果并不是我们所需要的,且性别选项被改为了1,为了防止以上情况出现,我们可以采取如下操作
def index(name, *args, gender='male', **kwargs): print(name, args, gender, kwargs) index('jason', 1, 2, 3, 4, 'female', b=2)
当形参必须像上方一样,按照关键字参数传递数据值时,我们称之为命名关键词参数
名称空间
在计算机中,数据存储的原理是在内存中开辟一块空间存储数据,并用变量名绑定该数据,在之后通过调用变量名来查找数据值,而名称空间就是在内存中申请专门的存储单元用来存放变量名与数据值的绑定关系
名称空间可以分为三个类别
1.内置名称空间:python自带的名称存放的空间,当python解释器启动时,内置名称空间启动,我们经常使用的input、print、len、del等等都属于内置名称
2.全局名称空间:当python工程文件打开时启动的名称空间,对应的名称是整个py文件中所包含的文件级别的名称,例如while、if、and等在文件中使用到的名称
3.局部名称空间:当函数体代码或者类体代码运行时产生的名称空间,仅仅局限于函数体或类体中所使用的名称
名称空间存活周期及作用范围(作用域)
名称空间存活周期
内置名称空间:
当python解释器启动时生成,python解释器停止运行后销毁
全局名称空间:
当python工程文件运行时生成,工程文件停止运行后销毁
局部名称空间:
函数体代码运行时创建,当函数体代码运行结束后销毁
名称空间作用范围(作用域)
内置名称空间:
解释器级别的全局有效(只要解释器可以运行,在整个运行环境内有效)
全局名称空间:
python工程文件级别的全局有效(只要py文件可以执行,整个py文件内有效)
局部名称空间:
仅在函数体代码范围内有效
名称查找的顺序
当我们查找名称时,首先我们需要确定我们所处的名称空间
当我们在局部名称空间中的时
局部名称空间 >>> 全局名称空间 >>> 内置名称空间
当我们在全局名称空间中的时
全局名称空间 >>> 内置名称空间
基本上的规律是从内而外,但是在某些条件下,该规律是可以打破的
名称查找顺序案例
相互独立的名称空间无法互相访问
def func1(): name = 'jason' print(age) def func2(): age = 18 print(name)
如以上两段代码所示,两段代码分别为不同的局部名称空间,在这种情况下是无法互相访问的,相对于两个py文件也是一样的,两者之间是无法互相访问的
局部名称空间嵌套:
先从自己的局部名称空间查找 之后由内而外依次查找
函数体代码中名字的查找顺序在函数定义阶段就已经固定了 x = '干饭了' def func1(): x = 1 def func2(): x = 2 def func3(): print(x) x = 3 func3() func2() func1()
graph TD
A(func1)–>B(x=1)
B–>C(func2)
C–>D(x=2)
D–>E(func3)
E–>|正常情况下|F(x=3)
F–>|正常情况下|G(print’x’)
G–>|正常情况下|H(x=3)
E-.->|代码所写|G
G-.->|代码所写|F如图所示,当代码开始执行,查找x,但是由于代码中print(x)与’x=3’的位置发生了颠倒,这样在解释器的查找逻辑中就无法拿到本应该存在的x=3,也就无法执行输出命令,系统就会报错
money = 100 def index(): money = 666 print(money) # 输出的值为100,print命令不属于函数体代码,直接在全局查找 money=100
money = 100 def func1(): money = 666 def func2(): money = 888 func2() print(money) # 输出的值为100,print命令不属于函数体代码,直接在全局查找 money=100
global与nonlocal
global
money = 666 def index(): global money '''借助于global关键字声明 在局部名称空间中的money修改全全局名称空间中的money''' money = 123 index() print(money)
global关键字的作用就是在局部名称空间中对全局名称空间中的变量名绑定关系进行修改
nonlocal
def index(): name = 'jason' def inner(): nonlocal name '''借助nonlocal关键字声明 在内层局部名称空间中对外层index函数结构中的name进行修改 ''' name = 'kevin' inner() print(name) index()
nonlocal关键字的作用是在函数的嵌套中,在内层的局部名称空间中,对外层的局部名称空间中的变量名绑定关系进行修改
函数名称的多种用法
函数名所绑定的是一个内存地址,在这个内存地址中,存放着函数体代码,当我们的调用函数时,函数名本身并没有特殊含义,其之后跟随的()是调用函数的标志,根据这一特性,函数名也有着多种用法
1.当作变量名赋值
def index():pass res = index res()
在以上代码中,函数名绑定了函数体代码,然后res绑定了函数名,相当于将函数名绑定的函数体代码再次绑定到了res,就像同一个数据值可以绑定多个变量名的原理相同,res = index中,函数名index就相当于函数体代码绑定的第一个变量名
2.当作函数的参数
def index(): print('from index') def func(a): print(a) a() func(index)
在以上代码中,先定义了函数index,然后在函数func中,index被作为实参传递给形参a,就相当于将index所绑定的函数体代码临时绑定给了a
3.可以当作函数的返回值
def index(): print('哈哈哈') def func(): print('嘿嘿嘿') return index res = func() print(res)
在以上代码中,首先定义了函数index,然后定义了函数func,当调用函数func后,设定返回值为index,所返回的内容就是index函数体代码所在的内存地址
4.可以当作容器类型中存储的数据
容器类型即可以存储多种数据的数据类型,我们所使用的列表、元组、字典、集合等都属于容器类型
def register(): print('注册功能') def login(): print('登录功能') def withdraw(): print('提现功能') def transfer(): print('转账功能') def shopping(): print('购物功能') # 定义功能编号与功能的对应关系 func_dict = { '1': register, '2': login, '3': withdraw, '4': transfer, '5': shopping } # 将编号与函数名编辑为字典 while True: print(""" 1.注册功能 2.登录功能 3.提现功能 4.转账功能 5.购物功能 """) # 输出功能编号列表 choice = input('>>>:').strip() # 获取用户输入的编号 if choice in func_dict: # 如果用户输入的编号在字典中存在 func_name = func_dict.get(choice) # 调用编号对应的函数名 func_name() # 运行函数体代码 else: print('功能编号不存在')
在以上代码中,首先定义出了多个不同功能所对应的代码,并按照编号与函数名的形式组织成字典,这样当我们需要通过用户输入来完成相对应的函数时,我们只需要在字典中找到对应的的键,取出作为数据值的函数名,然后加上括号调用函数名所绑定的函数体代码即可
在之前,为了写出功能列表中的各项功能,我们需要采用if…elif…else…的结构写出冗长的代码,而在上述代码的方法中,有效减少了冗余代码,使得代码更加精简
闭包函数
闭包函数的定义是在函数的嵌套中,在内层的函数调用时,用到了外层函数中的名称
一级函数:
函数体代码
二级函数:
函数体代码
def index(): x=('哈哈哈') def func(): print(x)
在以上代码中,一级函数的名称为index,函数体代码为x=’哈哈哈’,二级函数为func,函数体代码为输出x,这样就符合了闭包函数的定义,二级函数调用时,输出以及函数中的名称x
而闭包函数也可以视为一种函数传参的方式
给函数传参的方式1:传统的位置传参或关键字传参
def register(name,age): print(f""" 姓名:{name} 年龄:{age} """) register('jason', 18)
给函数传参的方式2:
def outer(name, age): # name = 'jason' # 将变量名name与age以参数的方式定义在函数中 # age = 18 def register(): print(f""" 姓名:{name} # 在内层函数中使用变量名name与age 年龄:{age} """) return register # 将函数register的返回值设置为他的函数名 res = outer('jason', 18) # res接收函数register的返回值,将res与函数名register绑定的函数体代码绑定 # 将数据值jason,18作为实参传递给函数outer中的变量名name、age res() # res就相当于函数register res() res = outer('kevin', 28) # 当传参时的实参改变后,内层函数中引用的变量名所对应的数据值改变 res() res()
在以上代码中,绑定关系为:传参时的实参–>一级函数中的形参–>二级函数中的变量名
当一级函数与传参时的实参的绑定关系解除后,一级函数中的形参与二级函数中的变量名的绑定关系并没有接触,所以当第一次传参后,传进去的数据值依旧可以使用,在之后的函数调用中,我们不传新的参数进去时,依旧会使用之前的数据值进行输出
装饰器简介
1.概念
在不改变被装饰对象原代码和调用方式的情况下给被装饰对象添加新的功能
2.本质
并不是一门新的技术,而是由函数参数、名称空间、函数名的多种用法、闭包函数这些我们所学过的知识组合到一起的结果
3.口诀
对修改封闭(不可以直接修改函数体代码,不改变调用方式) 对扩展开放(可以在上一个要求基础上添加功能)
4.储备知识时间相关操作 import time # 调用时间相关模块 print(time.time()) # 时间戳(距离1970-01-01 00:00:00所经历的秒数) time.sleep(3) # 关键字sleep要求时间在此停顿,括号中的数字代表秒数 print('哈哈哈哈哈')
时间相关操作实例
count = 0 # 循环之前先获取时间戳 start_time = time.time() # 记录开始时的时间戳 while count < 100: #计数器 print('哈哈哈') count += 1 # 计数器累计自增 end_time = time.time() # 记录程序结束时的时间戳 print('循环消耗的时间:', end_time - start_time) # 两者相减得出程序运行所消耗的时间
装饰器推导流程
import time time.sleep(3) print('from index')
- 如上代码,当我们想要计算程序运行所用的时间时,我们需要在代码前后分别添加一个时间戳,最后还需要添加代码来计算代码运行所需的时间
import time start_time = time.time() time.sleep(3) print('哈哈哈') end_time = time.time() print('运行代码所用时间为:', end_time-start_time)
- 这样我们达到了计算程序运行所需时间的目的,但是如果当我们需要在多个地方计算的话,就需要在多个地方重复执行这段代码,那么我们可以用函数将这段代码包起来,减少代码的冗余
import time def index(): # 定义函数将代码包起来 start_time = time.time() time.sleep(3) print('哈哈哈') end_time = time.time() print('运行代码所用时间为:', end_time-start_time) index()
- 但是当函数运行时,我们只能计算这一段函数代码的运行时间,当我们需要计算多个函数代码运行的时间时,我们可以将函数名作为参数,通过传参来实现
def index(): # 定义需要进行传参的函数,并将函数名作为参数 print('哈哈哈') def home(): print('嘿嘿嘿') def get_time(xxx): # 定义一个有参函数,参数为xxx start_time = time.time() xxx() '''将参数与变量名xxx绑定,这样变量名与传入的参数绑定, 当上面的函数名作为参数传递到这里时,加上括号后直接运行函数名所绑定的函数体代码''' end_time = time.time() print('函数的执行时间为>>>:', end_time - start_time) get_time(index) get_time(home) # 将函数名作为参数传参
- 虽然实现了函数的可切换功能,但是并不符合装饰器中不改变源代码调用方法的要求,所以我们只能考虑使用闭包函数
def index(): # 定义需要进行传参的函数,并将函数名作为参数 print('哈哈哈') def home(): print('嘿嘿嘿') def outer(xxx): # 定义形参为xxx的函数,传参时与函数名绑定 xxx = index # 将形参与变量名绑定,上一行中的参数已经完成这一步,可以省略 def get_time(): # 定义函数执行计算函数运行时间的函数 start_time = time.time() xxx() # 变量名xxx与形参绑定,同时与函数名绑定 end_time = time.time() print('函数的执行时间为>>>:', end_time - start_time) return get_time # 返回计算程序运算时间的函数名 res = outer(index) '''res与最外层函数的outer的运行结果绑定, 而outer的运行结果为get_time函数的返回值也就是get_time的函数名 也就等同于将res与get_time函数名绑定''' res() # 等同于get_time() res1 = outer(home) res1()
- 上述代码虽然做到了闭包,但是最终的函数调用方式仍不愿符合要求,所以我们需要采用变量名复制绑定,index和home作为变量名来绑定
def index(): # 定义需要进行传参的函数,并将函数名作为参数 print('哈哈哈') def home(): print('嘿嘿嘿') def outer(xxx): # 定义形参为xxx的函数,传参时与函数名绑定 xxx = index # 将形参与变量名绑定,上一行中的参数已经完成这一步,可以省略 def get_time(): # 定义函数执行计算函数运行时间的函数 start_time = time.time() xxx() # 变量名xxx与形参绑定,同时与函数名绑定 end_time = time.time() print('函数的执行时间为>>>:', end_time - start_time) return get_time # 返回计算程序运算时间的函数名 index = outer(index) # 赋值符号左侧为变量名,变量名没有实际意义,可以随便书写 index() home = outer(home) home()
这样的情况下,我们就实现了不改变源代码,也不改变源代码的调用方式,需要注意的是,现在所用的变量名并不是最初的函数名了,现在的绑定关系为index = get_time,而最初index绑定的函数代码已经由变量名xxx绑定
但是在以上代码中,最初提供的函数名所绑定的函数都是无参函数,当它们为有参函数时,我们就需要做出相应的调整
def func(a): time.sleep(0.1) print('from func', a) def func1(a,b): time.sleep(0.2) print('from func1', a, b) def func2(): time.sleep(0.3) print('from func2') func(123) func1(1,2) # 当使用有参函数时,我们就需要按照形参的个数进行传参 func2() def outer(xxx): def get_time(a, b): #当我们知道有几个参数是,相应的就需要在代码中添加几个参数 start_time = time.time() xxx(a, b) end_time = time.time() print('函数的执行时间为>>>:', end_time - start_time) return get_time func1 = outer(func1) func1(1, 2)
- 但是在我们不知道有多少个参数的时候,就无法确定函数体代码中需要填写的参数的个数,这个时候,我们可以借助于可变长参数来完成所有参数的接收
def func(a): time.sleep(0.1) print('from func', a) def func1(a,b): time.sleep(0.2) print('from func1', a, b) def outer(xxx): def get_time(*args, **kwargs): # get_time(1,2,3) args=(1,2,3) start_time = time.time() xxx(*args, **kwargs) # xxx(*(1,2,3)) xxx(1,2,3) end_time = time.time() print('函数的执行时间为>>>:', end_time - start_time) return get_time func = outer(func) func(123) func1 = outer(func1) func1(1, 2)
- 解决了函数的参数问题,我们就需要考虑函数的返回值的问题,当函数中有返回值时
def func(a): time.sleep(0.1) print('from func', a) return 'func' def func1(a,b): time.sleep(0.2) print('from func1', a, b) return 'func1' def outer(xxx): def get_time(*args, **kwargs): start_time = time.time() res = xxx(*args, **kwargs) # 用res接收变量名绑定的函数的返回值 end_time = time.time() print('函数的执行时间为>>>:', end_time - start_time) return res # get_time函数调用结束返回res return get_time # 整体函数调用结束后返回函数名get_time func = outer(func) func(123) '''当函数运行结束后,返回值为get_time的返回值,而get_time的返回值为res res所接收的是原函数在闭包函数中运行时产生的返回值,所以最终得到的仍是原函数的返回值 '''
这样,我们就得到了完整的装饰器结构
装饰器模板
def index(a,b): print('hello world') # 定义原函数 return '哈哈' def outer(func): # 利用变量名(形参)与原函数名绑定 def inner(*args, **kwargs): # 定义函数接收原函数中的参数并用可变长参数进行处理 pass # 执行被装饰对象之前可以做的额外操作 res = func(*args, **kwargs) # 用res接收原函数运行结束后的返回值 # 将经过可变长参数处理的实参还原为最初接收时的版本 pass # 执行被装饰对象之后可以做的额外操作 return res # 将返回值设置为res,也就是原函数的返回值 return inner # 将返回值设置为嵌套中内层的函数名 index = outer(1,2) # 用变量名index接收函数inner的函数名 index() # 调用函数
装饰器语法糖
语法糖结构:@函数名 def outer(func_name): def inner(*args, **kwargs): print('执行被装饰对象之前可以做的额外操作') res = func_name(*args, **kwargs) print('执行被装饰对象之后可以做的额外操作') return res return inner """ 语法糖作用:语法糖会自动将下面紧挨着的函数名当做第一个参数自动传给@函数调用 """ @outer # func = outer(func) def func(): print('from func') return 'func' @outer # index = outer(index) def index(): print('from index') return 'index' func() index()
多层语法糖
def outter1(func1): print('加载了outter1') def wrapper1(*args, **kwargs): print('执行了wrapper1') res1 = func1(*args, **kwargs) return res1 return wrapper1 def outter2(func2): print('加载了outter2') def wrapper2(*args, **kwargs): print('执行了wrapper2') res2 = func2(*args, **kwargs) return res2 return wrapper2 def outter3(func3): print('加载了outter3') def wrapper3(*args, **kwargs): print('执行了wrapper3') res3 = func3(*args, **kwargs) return res3 return wrapper3 @outter1 @outter2 @outter3 def index(): print('from index')
graph TD
A(index函数名)–>|语法糖outer3|B(index绑定函数outer3的形参)
B–>C(输出加载outter3)
C–>D(定义函数wrapper3)
D–>|跳过wrapper3函数体代码执行|E(返回函数名wrapper3)
E–>|函数名wrapper3通过语法糖outter2|F(函数名wrapper3绑定函数outter2的形参)
F–>G(输出加载outter2)
G–>H(定义函数wrapper2)
H–>|跳过wrapper2函数体代码执行|I(返回函数名wrapper2)
I–>|函数名wrapper2通过语法糖outter1|J(函数名wrapper2绑定函数outter1的形参)
J–>K(定义函数wrapper1)
K–>|输出执行了wrapper1|L(执行函数体代码)
L–>M(res1 = wrapper2运行后的返回值)
M–>N(装饰器outter2中的wrapper2执行)
N–>|输出执行了wrapper2|Q(执行函数体代码)
Q–>R(res2 = wrapper3运行后的返回值)
R–>S(装饰器outter3中的wrapper3执行)
S–>|输出执行了wrapper3|T(执行函数体代码)
T–>U(真正的index执行)
U–>|输出from index|V(执行完毕获取返回值res3)
以上为多层语法糖的运行逻辑,我们可以看出,多层语法糖的加载顺序是自下而上的
每一次执行结束后,如果返回值中有函数名,这个函数名会直接由上一层的语法糖传递给上一层的装饰器
如果继续向上不再有语法糖,则直接运行当前所在函数体代码变形为 index=outter1(wrapper2)
有参装饰器
def func(func_name): del funci(*args, **kwargs): # 函数执行前进行的操作 res = funcname(*args, **kwargs) # 函数执行后进行的操作 return res return func1 @func del index(): pass
上述代码是我们所使用的装饰器模板,可以满足我们大多数需求,但是如果我们需要在函数运行前或运行后添加一些需要提供参数的操作时,现有的代码中是无法提供参数的,而现有的代码也无法将参数修改并为这些操作提供操作时,我们就需要在装饰器外再包一层函数,来为这些操作提供参数
del outter(a): del func(func_name): del func1(*args, **kwargs) # 函数执行前进行的操作 res = func_name(*args, **kwargs) # 函数执行后进行的操作 return res return func1 @outter(A) del index(): pass
当我们将装饰器外再包一层函数后,语法糖@outter(A)括号中的实参会传递到函数outter的形参,当传参完成后,index函数名会继续传参到函数func(func_name),之后的代码执行依旧与原来的装饰器执行一致,但是在最外层的outter函数中,就可以提供我们进行其他操作所需要的参数A了
def outer(mode): # 接收参数1 def login_auth(func_name): # 接收函数名index def inner(*args, **kwargs): # 接收并处理index传递进来的参数 username = input('username>>>:').strip() # 获取用户输入内容 password = input('password>>>:').strip() if mode == '1': # 验证参数并执行相应操作 print('数据直接写死') elif mode == '2': print('数据来源于文本文件') elif mode == '3': print('数据来源于字典') elif mode == '4': print('数据来源于MySQL') func_name(*args, **kwargs) # 还原之前处理过的参数并执行被装饰的函数 return inner # func_name的返回值即inner的返回值 return login_auth # inner的返回值即login_auth的返回值 @outer('1') # 先传递实参1到函数outer的形参,然后将函数名传递到login_auth的形参 def index(): print('from index') index() @outer('2') def func(): print('from func') func()
在函数结构中,函数名加括号的执行优先级最高,所以在有参装饰器中,先完成括号中参数的传递,然后再继续语法糖的正常执行
装饰器修复技术
def func(func_name): del funci(*args, **kwargs): # 函数执行前进行的操作 res = funcname(*args, **kwargs) # 函数执行后进行的操作 return res return func1 @func # index = func1(index) del index(): pass
在装饰器模板中,我们只是用变量名绑定了函数func1,而func1就相当于真正的index运行后的返回值,所以index = func(index)
而如果此时我们输出index,得到的结果是
<function func..func1 at 0x00000222C3007160>,这意味着虽然变量名仍然是index,但是他所绑定的已经是存放func函数代码的内存地址
而装饰器修复技术,就是从函数功能性工具中导入包装工具,使得装饰器的包装功能做到以假乱真
from functools import wraps # def outer(func_name): @wraps(func_name) # 装饰器包装代码,使装饰器以假乱真 def inner(*args, **kwargs): """我是inner 我擅长让人蒙蔽""" res = func_name(*args, **kwargs) return res return inner @outer def func(): """我是真正的func 我很强大 我很牛 我很聪明""" pass
在使用这些功能后,我们在输出func,得到的是<function func at 0x000002320E6A7160>
这样就做到了以假乱真的效果
递归函数
函数的递归调用
函数的递归调用就是函数直接或者间接的调用了函数自身
直接调用:
def index(): print('from index') index() index()
间接调用:
def index(): print('from index') func() def func(): print('from func') index() func()
当函数直接或者间接调用自己本身时,这种状态我们称之为函数的递归调用,但是递归调用是由次数限制的,我们称之为最大递归深度,当递归调用达到一定次数后,python解释器就会报错,如果不加以限制,一直反复调用,类似于死循环一样,会造成内存和CPU负荷过高,所以这相当于python解释器的一种应急安全措施
count = 0 def index(): global count count += 1 print(count) index() index()
通过以上代码验证,我们大致得出最大递归深度在1000次左右,而官网的说明是1000次
递归函数
递归函数的条件限制:
1.函数直接或者间接调用自身
2.每一次调用都应该比上一次调用更简单
3.有明确的中止条件
递归函数的运行大致分为两个阶段
1.递推:一层一层向下推进
2.回溯:基于终止条件一层一层向上返回
def get_age(n): # 定义函数并接收实参5 if n == 1: # 判定条件当参数==1 return 18 # 当达到条件,返回值为18 return get_age(n-1) + 2 # 没有达到条件时参数减1,函数的返回值加2 res = get_age(5) # 当n==1时,一共进行了四次加2的操作 # 而此时正好满足n==1的条件,返回值为18,res = 18+2+2+2+2 print(res) # 最终输出结果为26
以上代码就是一个简单的递归函数
算法简介及二分法
算法的定义
算法可以通俗的理解为,解决问题的有效方法,但是我们需要知道的是,算法并不全是完美的,有高效率的算法,也有低效率的算法,唯一共同点就是可以有效地帮助我们解决我们所遇到的问题
在日常生活中,算法的应用非常广泛,比如我们在刷短视频时,当我们对某一类视频的观看频率非常高的时候,算法就会产生作用,推送大量相同类型的视频给我们的帐户,类似于这样的操作,就是算法在我们日常生活中的应用
在一些互联网大厂中一般会有专门的算法部门,因为算法工程师的待遇极高,条件要求也很高,所以规模较小的公司是负担不起算法部门的消耗的
二分法
二分法是算法中最为简单的一种,但是二分法的使用是有限制的,二分法所查找的数据必须是排列有序的,这与二分法的运行规则有关
l1 = [12, 21, 32, 43, 56, 76, 87, 98, 123, 321, 453, 565, 678, 754, 812, 987, 1001, 1232] # 查找列表中某个数据值 # 可采用的方式1:for循环,虽然可以查找到我们所需要的数据,但是需要经过多次便利 # 方式2:二分法,不断的对数据集做二分切割,即不断地对目标列表进行一分为二,分别与我们需要的目标值进行对比,然后在最靠近目标数据的区域中重复该操作,然后不断重复这个过程,直到查找到我们所需要的数据 '''代码实现二分法''' # 1.定义我们想要查找的数据值 target_num = 987 # 2.对列表进行分割 midle_index = len(l1)//2 # 3.找到列表索引值的中数作为分割依据 if target_num < l1[midle_index]: # 4.如果分割列表时,索引值中数对应的数据值小于目标值 left_list = l1[:midle_index] # 5.利用切片分割中间数据值的左半边(列表切片顾头不顾尾) if target_num > l1[midle_index]: # 6.如果分割列表时,索引值中数对应的数据值大于目标值 right_list = l1[midle_index+1:] # 7.利用切片分割中间数据值的右半边 # 8.在没有得到目标数据之前,根据判定条件,选择符合条件的新分割出的列表重复以上操作 """ 根据该操作的特性,我们可以采用递归函数来编写代码 """ def get_nun(l1, target_num): # 定义函数,形参为需要进行分割的列表和我们需要的数据值 if len(l1) == 0: print('对不起,目标数据不存在') return # 当分割到最后不可再分割时,意味着数据值不存在,输出数据值不存在,并中止函数 midle_index = len(l1)//2 # 查找索引值中数 if target_num > l1[midle_index]: right_list = l1[midle_index=1:] # 如果目标数据大于中间数据,切割右侧并保存为新的列表 return getnum(right_list, target_num) # 返回值继续调用函数在新的列表中进行切割查找数据 elif target_num < l1[midle_index]: left_list = l1[midle_index=1:] # 如果目标数据小于中间数据,切割左侧并保存为新的列表 return getnum(right_list, target_num) # 返回值继续调用函数在新的列表中进行切割查找数据 else: print('查找到目标数据') # 查找到数据输出查找成功
二分法可以以更少的运行次数查找到目标数据,但是二分法的缺陷在于,如果我们所需要查找的数据位于列表的开头或者结尾,它的效率是没有for循环和索引取值高的
三元表达式
三元表达式的主要作用是用来优化我们的代码,使我们的代码更加简洁,美观
三元表达式语法结构:
数据值1 if 条件 else 数据值2
条件成立则使用数据值1,条件不成立则使用数据值2name = 'jason' # 正常代码写法: if name == 'jason': print('老师') else:print('学生') # 三元表达式写法 res = '老师' if name == 'jason' else '学生' print(res) # res接收三元表达式的结果,输出res为'老师'
三元表达式推荐在二选一的情况使用,不建议嵌套使用
部分生成式\表达式\推导式
列表生成式
name_list = ['jason', 'kevin', 'oscar', 'tony', 'jerry','jack'] # 给列表中所有人名的后面加上_NB的后缀 # 方式1:for循环 new_list = [] # 定义空列表 for name in name_list: # 遍历源列表中每个数据 data = f'{name}_NB' # 为遍历出来的数据添加后缀 new_list.append(data) # 添加过后缀的数据添加到新的列表 print(new_list) # 输出新列表验证结果 # 方式2:列表生成式 new_list = [name + "_NB" for name in name_list] print(new_list) # 列表生成式中,先看for循环,每次for循环完成之后再执行for关键字前面的操作,最终结果全新的数据值保留在这个新的列表中,列表生成 # 复杂情况 new_list = [name + "_NB" for name in name_list if name == 'jason'] print(new_list) # 先执行for循环,然后根据if条件,对便利出的数据值中符合条件的执行for关键字之前的操作 new_list = ['大佬' if name == 'jason' else '捞得很' for name in name_list if name != 'jack'] print(new_list) # 先执行for循环,然后执行for循环之后的条件,符合条件的数据在根据for关键字之前的判定条件进行相应的操作 #输出结果:['大佬', '捞得很', '捞得很', '捞得很', '捞得很']
字典生成式及集合生成式
# 字典生成式 s1 = 'hello world' for i,j in enumerate(s1,start=100): # enumerate关键字的作用是将数据类型中的所有数据值打上编号,这段代码的意思是将字符串中的每个字符都关联相应的编号 print(i,j) # 输出所有编号与字符的组合 d1 = {i: j for i, j in enumerate('hello')} # 将字符的编号作为K键,字符作为数据值生成键值对存放到新的字典中 print(d1) # 集合生成式 res = {i for i in 'hello'} # 遍历字符串中的字符,然后将遍历出的字符作为数据值存放到新的集合 print(res)
在生成器中并没有元组生成器,输出的结果是生成器类型以及它所绑定的内存地址
匿名函数
匿名函数即没有函数名的函数,由于它没有函数名,所以我们需要用到关键字lambda
# 匿名函数语法结构 lambda 形参:返回值 # 使用场景 lambda a, b:a+b
匿名函数因为它独特的结构及传参方式,一般并不单独使用,而是结合一些内置函数使用
常见内置函数
1.map()映射
l1 = [1, 2, 3, 4, 5] # 当我们使用定义函数的方式实现列表中每个数据加1的需求时 def func(a): return a + 1 # map关键字的使用 res = map(lambda x:x+1, l1) print(list(res)) # 相当于将l1中的每个数据取出来,经过匿名函数处理后,映射出处理后的数据
2.max()\min()
l1 = [11, 22, 33, 44] res = max(l1) # max()的作用是找出列表数据中的最大值,min()的作用是找出列表数据中的最小值 d1 = { 'zj': 100, 'jason': 8888, 'berk': 99999999, 'oscar': 1 } def func(a): return d1.get(a) """res = max(d1, key=lambda k: d1.get(k))""" res = max(d1, key=func) print(res) # 当max()或者min()对字典使用时,如果只是用关键字与字典名的话,那么只会根据字典的K键进行取值比对,而在字符编码中A-Z对应65-90,a-z对应97-122,所以如果直接使用max(d1),得到的结果是zj,因为在这个过程中只有字典的键参与,而z对应可了最大值,而在语法结构中,max()括号中除了变量名之外,还可以指定一个key # max(d1, key=func),这段代码中,key的作用就是调用函数取出每个键所对应的值作为max取最大值的依据,但是最后得到的结果仍然是键,而不是数据值
3.reduce
from functools import reduce l1 = [11, 22, 33, 44, 55, 66, 77, 88] res = reduce(lambda a, b: a + b, l1) # reduce(匿名函数, 列表名) print(res) # reduce的使用需要调用模块,它的作用是去除列表中的每个数据值,经过临时函数处理后,得到所有数据值的总和
4. 数据类型
- bool : 布尔型(True,False)
- int : 整型(整数)
int() # 将数字转换为整型
- float : 浮点型(小数)
float() # 将数字转换为浮点型
- str() 将数据转化成字符串
str(0) # 将数据类型转换为字符串类型
- bytes() # 将字符转换为二进制类型
bs=bytes("今天吃饭了吗",encoding="utf-8") # 使用该方法需要定义好原字符的编码方式 print(bs)#b'\xe4\xbb\x8a\xe5\xa4\xa9\xe5\x90\x83\xe9\xa5\xad\xe4\xba\x86\xe5\x90\x97'
5. 进制转换
- bin() 将给的参数转换成二进制
print(bin(10)) # 十进制转二进制:0b1010
- otc() 将给的参数转换成八进制
print(hex(10)) # 十进制转十六进制:0xa
- hex() 将给的参数转换成十六进制
print(oct(10)) # 十进制转八进制:0o12
6. 数学运算
- abs() 返回绝对值
print(abs(-2)) # 绝对值:2
- divmode() 返回商和余数
print(divmod(20,3)) # 求商和余数:(6,2) """ divmod使用示例 """ 总数据 每页展示的数据 总页码 100 10 10 99 10 10 101 10 11 page_num, more = divmod(9999, 20) print(divmod(99, 10)) # (9, 9) if more: page_num += 1 print('总页码为:', page_num) # 总页码为: 500 # 当我们需要根据数据总量以及每页放置的数据数量来排布网页页数时,就可以利用divmod函数,如果输出有余数,说明还需要在原有的整除数的基础上加一
- round() 四舍五入
print(round(4.50)) # 五舍六入:4 print(round(4.51)) # 5
- pow(a, b) 求a的b次幂, 如果有三个参数. 则求完次幂后对第三个数取余
print(pow(10,2)) # 10的2次方 100 print(pow(10,2,3)) # 如果给了第三个参数,表示最后10的二次方除三并取余:1
- sum() 求和
print(sum([1,2,3,4,5,6,7,8,9,10])) # 求和:55
- min() 求最小值
print(min(5,3,9,12,7,2)) # 求最小值:2
- max() 求最大值
print(max(7,3,15,9,4,13)) # 求最大值:15
7. 序列
(1)列表和元组
- list() 将一个可迭代对象转换成列表
print(list((1,2,3,4,5,6))) # [1,2,3,4,5,6]
- tuple() 将一个可迭代对象转换成元组
print(tuple([1,2,3,4,5,6])) # (1,2,3,4,5,6)
(2)相关内置函数
- reversed() 将一个序列翻转, 返回翻转序列的迭代器
lst="你好啊" it=reversed(lst) # 不会改变原列表,而是会返回一个迭代器 print(list(it)) # ['啊','好','你']
- slice() 列表的切片
lst=[1,2,3,4,5,6,7] print(lst[1:3]) # 根据提供的起始索引以及结束索引进行切片,但是切片结果不包含最后的数据值[2,3]
- ord() 输入字符找找到字符编码的位置
print(ord('a')) # 字母a在编码表中的码位:97 print(ord('中')) # '中'字在编码表中的位置:20013
- chr() 输入字符编码中位置数字找出对应的字符
print(chr(65))#已知码位,求字符是什么:A print(chr(19999))#丟
- ascii() 是ascii码中的返回该值 不是就返回u
print(ascii("@")) # '@'
- repr() 返回一个对象的string形式
s="今天\n吃了%s顿\t饭"%3 print(s) # 今天#吃了3顿饭 print(repr(s)) # 原样输出,过滤掉转义字符\n\t\r不管百分号% #'今天\n吃了3顿\t饭'
8. 数据集合
- 字典:dict() 创建一个字典
- 集合:set() 创建一个集合
9. 相关内置函数
- len() 返回一个对象中的元素的个数
lst=[5,7,6,12,1,13,9,18,5] print(len(lst)) # 9
- sorted() 对可迭代对象进行排序操作 (lamda)
lst=[5,7,6,12,1,13,9,18,5] res = sorted(lst) # 等同于 lst.sort,是列表中的内置方法 print(res) # [1,5,5,6,7,9,12,13,18]
- enumerate() 获取集合的枚举对象
lst=['one','two','three','four','five'] for l in enumerate(lst,1): print(l) """ 将列表内每个数据编号,默认编号从0开始,可以通过括号内数字更改 然后将编号与数据组织成元组 输出值 (1, 'one') (2, 'two') (3, 'three') (4, 'four') (5, 'five') """
- all() 可迭代对象中全部是True, 结果才是True
print(all([1,'hello',True,9])) # True
- any() 可迭代对象中有一个是True, 结果就是True
print(any([0,0,0,False,1,'good']))#True
- zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个元组,然后返回由这些元组组成的列表,如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同
lst1=[1,2,3,4,5,6] lst2=['醉乡民谣','驴得水','放牛班的春天','美丽人生','辩护人','被嫌弃的松子的一生'] lst3=['美国','中国','法国','意大利','韩国','日本'] print(zip(lst1,lst1,lst3)) # <zipobjectat0x00000256CA6C7A88> forelinzip(lst1,lst2,lst3): print(el) #(1,'醉乡民谣','美国') #(2,'驴得水','中国') #(3,'放牛班的春天','法国') #(4,'美丽人生','意大利') #(5,'辩护人','韩国') #(6,'被嫌弃的松子的一生','日本')
- locals() 返回当前作用域中的名字 globals() 返回全局作用域中的名字
deffunc(): a=10 print(locals())#当前作用域中的内容 print(globals())#全局作用域中的内容 print("今天内容很多") func() # {'a':10} # {'__name__':'__main__','__doc__':None,'__package__':None,'__loader__': # <_frozen_importlib_external.SourceFileLoaderobjectat0x0000026F8D566080>, # '__spec__':None,'__annotations__':{},'__builtins__':<module'builtins' # (built-in)>,'__file__':'D:/pycharm/练习/week03/new14.py','__cached__':None, # 'func':<functionfuncat0x0000026F8D6B97B8>} # 今天内容很多
10.和迭代器生成器相关
- range() 生成数据
for i in range(15,-1,-5): # 第一个数字代表起始数字,第二个数字是结束数字,第三个数字代表间隔 # 如果只输入一个数字,则代表生成从零到该数字所有数字 print(i) #15 #10 #5 #0
- next() 迭代器向下执行一次, 内部实际使⽤用了__ next__()⽅方法返回迭代器的下一个项目
- iter() 获取迭代器, 内部实际使用的是__ iter__()⽅方法来获取迭代器
lst=[1,2,3,4,5] it=iter(lst) # __iter__()获得迭代器 print(it.__next__()) # 1 print(next(it)) # 2__next__() print(next(it)) # 3 print(next(it)) # 4
11.输入输出
- print() : 打印输出
print("hello","world",sep="*",end="@") # hello*world@ # sep:打印出的内容用什么连接,end:以什么为结尾
- input() : 获取用户输出的内容
input('请输入您要输入的内容') # 可以在括号中添加提示信息
12.内存相关
hash() 哈希加密,将字符串以类似乱码的形式加密
print(hash('jason')) # -1075018093423725077
13.文件操作相关
- open() : 用于打开一个文件
f=open('file',mode='r',encoding='utf-8') f.read() f.close() with open(r'file','r',encoding='utf-8') f.read()
14.帮 助
- help() : 函数用于查看函数或模块用途的详细说明
print(help(str)) # 查看字符串的用途
15.调用相关
- callable() : 用于检查一个对象是否是可调用的,如果返回True, object有可能调用失败, 但如果返回False. 那调用绝对不会成功
a=10 print(callable(a)) # False 变量不能被调用 # def f(): print("hello") print(callable(f)) # True函数是可以被调用的
16.查看内置属性
- dir() : 查看对象的内置方法
print(dir(tuple)) # 查看元组的方法 """ __add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] """
17.值查看
- id() 查看某个数据值在内存中的值,相当于身份证号
A = 123 print(id(A)) # 140719068550624
18.类型查看及类型判断
- type() 查看某个对象的数据类型
type(1) # <class 'int'>
- isinstance() # 判断某个对象的数据类型
B = (1,2,3,4,5,6) A = isinstance(1, int) C = isinstance(B, list) print(A) # True print(C) # False
19.字符串类型代码执行
- eval() 执行字符串类型的代码
- exec() 执行字符串类型的代码
s1 = 'print("哈哈哈")' eval(s1) # 哈哈哈 exec(s1) # 哈哈哈 s2 = 'for i in range(100):print(i)' eval(s2) # 报错 exec(s2) # 输出每个数据值 # eval只能识别简单的python代码 具有逻辑性的都不行 # exec可以识别具有一定逻辑性的python代码
可迭代对象
1.可迭代对象
# 对象内置有__iter__方法的都称为可迭代对象 """ 1.内置方法即通过xxx.的方式能够调用的方法 2.__iter__专业术语:双下iter方法 """ 2.可迭代对象的范围 不是可迭代对象 int float bool 函数对象 是可迭代对象 str list dict tuple set 文件对象 3.可迭代的含义 """ 迭代:更新换代(每次更新都必须依赖上一次的结果) """ 可迭代在python中可以理解为是否支持for循环
迭代器对象
1.迭代器对象
是由可迭代对象调用双下iter方法产生的
迭代器对象判断的本质是看是否内置有双下iter和双下next
2.迭代器对象的作用
提供了一种不依赖于索引取值的方式
正因为有迭代器的存在 我们的字典 集合才能够被for循环
3.迭代器对象实操s1 = 'hello' # 可迭代对象 res = s1.__iter__() # 迭代器对象 print(res.__next__()) # 迭代取值 # 一旦双下next取不到值 会直接报错 4.注意事项 可迭代对象调用双下iter会成为迭代器对象,迭代器对象如果还调用__iter__不会有任何变化,还是迭代器对象本身
for循环的本质
for 变量名 in 可迭代对象: 循环体代码 """ 1.先将in后面的数据调用__iter__转变成迭代器对象 2.依次让迭代器对象调用__next__取值 3.一旦__next__取不到值报错 for循环会自动捕获并处理 """
异常捕获/处理
1.异常
异常就是代码运行报错,俗称bug
代码运行中一旦遇到异常会直接结束整个程序的运行,所以我们在编写代码的过程中需要尽可能避免
2.异常分类
语法错误
语法错误是不允许出现的,一旦出现需要立刻改正
逻辑错误
允许出现的 因为它一眼发现不了 代码运行之后才可能会出现
3.异常结构
错误位置
错误类型
错误详情所以当我们遇到报错时不需要慌,根据报错内容定位到出错的位置,进行修改即可