文章目录
- 1 知识前提
- 2 进阶
- 2.1 浅拷贝和深拷贝
- 2.2 面向对象
- 2.3 MRO
- 2.4 内置函数
- 2.5 特殊属性方法
- 2.5.1 特殊属性:`__dict__`
- 2.5.2 反射
- 2.5.3 特殊方法:`__len__()`
- 2.5.4 特殊方法:`__iter__()`和`__next__()`
- 2.5.5 特殊方法:`__add__()`和`__radd__()`
- 2.5.6 特殊方法:`__str__()`和`__repr__()`
- 2.5.7 特殊方法:`__new__()`
- 2.5.8 特殊方法:`__getattr__()`
- 2.5.9 特殊方法:`__getitem__()`、`__setitem__()`和`__delitem__()`
- 2.5.10 特殊方法:`__call__()`
- 2.5.10 特殊属性:`__doc__`
- 2.5.11 特殊属性:`__slots__`
- 2.6 对象引用计数
- 3 模块
- 4 生成器和迭代器
- 5 异常
- 6 文件
- 7 函数和lambda表达式
- 8 变量作用域
- 9 函数装饰器
1 知识前提
2 进阶
2.1 浅拷贝和深拷贝
浅拷贝
使用[:]
对列表进行拷贝
L1 = [1,2,3,[1,2]]
L2 = L1[:}
id(L1) == id(L2) #False
id(L1[0]) == id(L2[0]) #True
此时为浅拷贝,即:
规则一:对于不可变对象且内部没有嵌套子对象,浅拷贝就是原对象,不拷贝。
规则二:对于不可变对象且内部嵌套可变对象,也是不拷贝。
规则三:不符合规则一的对象进行浅拷贝会创建了一个新的对象(地址不同),但是该对象内部的子对象指向的是同一个对象,只拷贝了外部。此时有两种情况:
A 改变子对象,且该子对象为不可变对象,不会影响到浅拷贝对象
B 改变子对象,且该子对象为可变对象,会影响到浅拷贝对象
浅拷贝:
L1[:]
L1.copy()
list(L1)
import copy;copy.copy(L1);
#调用标准库模块copy也是浅拷贝
注意:由于内部拷贝的是引用地址,当改变内部不可变对象时,改变不会影响到其他浅拷贝对象,当改变内部可变对象时会导致其他浅拷贝对象变化!
深拷贝
copy模块有一个深拷贝的方法:deepcopy(),和上述规则一样:
规则一:对于不可变对象且内部无嵌套子对象的深拷贝结果就是原对象,不拷贝。
规则二:对于不可变对象内部嵌套可变对象的深拷贝结果是不同的对象,且内部可变子对象也不同
规则三:对于可变对象,会创建一个新的(内存地址不同)对象,改变一个不影响其他。
2.2 面向对象
OOP:面向对象
定义一个类:class 类名(object):
,类名首字母要大写其余驼峰,其中object
表示继承
def __init__(self):
,构造第一个参数总是self,是一个特殊方法,创建实例后,该方法被自动调用
类方法def xxx(self):
,第一个参数总是self
self
表示调用该方法的那个对象
特殊的:对于方法体或类体可以使用pass
避开语法检查
class SomeA(object):
pass
# 定义类
class Dog(object):
def __init__(self,breed,name,age,health):
self.breed = breed
self.name = name
self.age = age
self.health = health
def run(self):
print("run")
# 实例化
Dog("黄狗","小黄",5,"good") # 执行了两步:1创建Dog实例,2调用__init__方法
print(Dog.name)
Dog.run()
2.2.1 实例属性
绑定实例属性值:(对于存在属性的为修改,不存在的为绑定)
类内部:self.属性名 = 属性值
类外部:实例.属性名=属性值
注意:python为动态语言,可以动态添加实例属性!
访问实例属性:
内部:self.属性名
外部:实例.属性名
注意:每个实例对象都有一个类对象的指针
实例.__dict__
:特殊属性,返回当前实例对象的所有方法和属性
dir(类)
:返回当前类所有的属性和方法
2.2.2 类属性
类属性和实例属性最大的区别是:没有self
前缀!!类属性是绑定在类上的,可以通过类或者实例进行访问,只能通过类进行修改。
设置
内部:属性名=属性值
外部:类.属性名 = 属性值
访问
内部:类.属性名
外部:类.属性名
或实例.属性值
实例对象也可以访问类属性!!访问属性的顺序:先查找实例,再查找类,即,同名的实例属性会屏蔽类属性
class MyDemo(object):
name = "pp"
def run(self):
print(MyDemo.name)
del MyDemo.age # 删除实例属性
2.2.3 实例方法
绑定在实例上的方法,类对象不可调用
调用格式:
类内部:self.方法(参数)
类外部:实例.方法(参数)
绑定:
类内部:def 方法(self,参数)
,该方式会给所有实例绑定
类外部:
使用MethodType
方法,称为动态绑定实例方法,只会绑定到当前实例
使用实例.方法(self,参数)
,会绑定所有实例对象(相当于在类中定义了实例方法)
# 动态绑定实例方法
from types import MethodType
def fun1(self):
pass
abc.f = MethodType(fun1,abc) # MethodType(方法名,类名),给某个类绑定实例方法
2.2.4 类方法
定义类方法必须使用:@classmethod
,第一个形参表示类对象,由系统自动传入
类方法可以被实例或类调用。调用格式:类.方法(参数)
,实例.方法(参数)
class My(object):
@classmethod
def a(cls,p1,p2):
print(p1,p2)
@classmethod
def b(cls):
My.a(1,2) # 在类方法中调用类方法
cls.a(1,2) # 在类方法中调用类方法
def c(self):
self.a(1,2) # 在实例方法中调用类方法
My.a(1,2) # 类外部直接调用
My().a(1,2) # 类外部创建实例调用
2.2.5 静态方法
和定义类方法定义和使用没有区别,除了第一个参数不需要传递了,且需要用:@staticmethod
外。
class My(object):
@staticmethod
def a(x,y):
print(x,y)
@classmethod
def b(cls):
My.a(1,2) # 在类方法中调用静态方法
cls.a(1,2) # 在类方法中调用静态方法
def c(self):
self.a(1,2) # 在实例方法中调用静态方法
My.a(1,2)
My().a(1,2)
2.2.6 访问控制
__
:添加在类或属性名前,则无法在类对象外部访问了
原因:名字被改掉了:原来:__a
,改成了_类__a
class My(object):
def __init__(self):
self.__a = 19
def __b(self):
print("abc")
def c(self):
self.__b()
My().__a # 无法访问
My().__b # 无法访问
特别注意:由于在类中定义的该方法会被改名,所以可以在外部定义相同名字的方法或属性,不会出现冲突
一般而言:以下划线开头的实例属性和方法不建议在外部直接访问,约定
2.2.7 封装
隔离复杂度,权限控制
class My(object):
def __init__(self):
self.__a = 19
def get_a(self):
return self.__a
def set_a(self,a):
if a > 0:
self.__a = a
else:
raise ValueError("必须大于0")
注意:raise
用于抛出异常
2.2.8 继承
提取共同点,抽象出来,便于复用
class Animal(object):
def eat(self):
print("eat")
class Dog(Animal):
pass
Dog().eat()
子类可以直接调用从父类继承过来的方法和属性,object
是所有类的父类或基类
注意:支持多继承,子类会继承所有直接和间接父类的属性和方法
class A(B,C,D):
pass
2.2.9 多态
程序动态决定在运行时调用对象的方法
def run(x):
x.show()
class a(object):
def show():
print("a")
class b(object):
def show():
print("b")
class c(a):
pass
run(a())
run(b())
run(c())
此时传递abc的实力给run方法,则会输出不同的结果,注意:当当前类中无此方法会往父对象中寻找
注意:python是动态语言,调用函数时不会检测类型!!静态语言多态实现:继承、重写、父类引用指向子类对象。
2.3 MRO
方法解释顺序:Method resolution order
如:对于直接继承的多个对象,如果含有相同的方法,则调用时需要根据py解释器顺序来确定调用哪个类中的方法,super()
同样符合该规则。
类.mro()
:返回对象继承树的一个有序列表,调用方法或访问属性时按照这个顺序进行访问,格式为列表
类.__mro__
:返回内容同上,格式为元组
如果想调用指定的父类方法,需要使用:super(类,实例)
,含义为:调用指定类后的类,类顺序是MRO
class D(B,C):
def run(self):
super(B,self).run()
D().run() # B后的类是C,所以这里会调用C中的run方法
2.4 内置函数
2.4.1 issubclass()和isinstance()内置函数
issubclass(类1,类2)
:类关系判断,判断类1是否是类2的子类,类2可以支持元组,表示满足其一即可,返回值为True|False
isinstance(实例,类)
:类和实例关系判断,判断实例是否是 类的实例 或 该类的子类 的实例,同样,类参数支持元组,返回值True|False
2.4.2 type内置函数
格式:type(对象)
如果是实例对象,返回类对象的类型
如果是类对象,返回<class 'type'>
,即类对象是type
的实例对象
如果是函数对象,返回<class 'function'>
,内置函数对象类型是内置函数类型
type(19) # print后为<class 'int'>
type(19) == int # True
type('abc') == str # True
type(func1) == function # False,因为除了基本数据类型,其他的不可以使用==判断
当对象为非基本数据类型,则需要使用types模块来处理:
import types
type(func1) == types.FunctionType # True
type(内置函数) == types.BuildinFunctionType # True
2.4.3 dir内置函数
获取制定对象中的所有属性和方法包括继承的,格式:dir(对象)
注意:类对象返回的不包括实例属性!!
2.5 特殊属性方法
object
中所有属性和方法都是以双下划线开头和结尾,双下划线包围的是特殊属性和特殊方法,属于预定义的,自定义的不要这样写!
在自定义类时,经常会重写特殊方法,该种方法会在特定场合下调用
2.5.1 特殊属性:__dict__
类.__dict__
:返回结果为字典:key为所有绑定的属性或方法名,含有很多内置继承的属性
实例.__dict__
:返回结果为字典,只包括手动定义的属性
默认情况下:访问实例的属性是通过访问该对象的__dict__
来实现的:obj.x
相当于obj.__dict__['x']
2.5.2 反射
以字符串形式操作对象属性和方法:增删改查
hasattr(object,name)
:判断指定对象中是否有指定属性和方法getattr(object,name[,default])
:获取制定对象中都某个属性或方法,如果不存在使用default代替,不存在且无default报错setattr(object,name,value)
:添加或修改制定对象中的某个属性或方法delattr(object,name)
:删除制定对象中的某个属性或方法
2.5.3 特殊方法:__len__()
类对象有个内置方法:len(内置类对象的实例对象)
,返回对象的长度
len([1,2,3]) # 3
len('abc') # 3
len({'a':1,'b':2}) # 2
对于非内置类型的自定义类,必须重写__len__()
才可以使用len()
class My(object):
def __len__(self):
return 18
len(My()) # 18
2.5.4 特殊方法:__iter__()
和__next__()
对于可迭代对象list,可以使用for in进行迭代
L = [1,2,3]
for item in L:
print(item)
对于自定义类,需要实现两个特殊方法才可以迭代:__iter__()
和__next__()
class My(object):
def __iter__(self):
return self
def __next__(self):
if 条件:
raise StopIteration() # 如果满足退出条件,则抛出停止迭代对象
else:
return 值 # 如果不满足退出迭代条件,则返回一个迭代后的值
for item in My():
逻辑
for in语句会首先调用__iter__()
返回一个可迭代的对象,然后不断调用__next__()
返回下一次迭代值。直到遇到StopIteration退出
只实现了__iter__()
的对象称为可迭代对象
__iter__()
和__next__()
都实现了,则为迭代器对象
2.5.5 特殊方法:__add__()
和__radd__()
对自定义类实现四则运算:
加:__add__()
和__radd__()
减:__sub__()
和__rsub__()
乘:__mul__()
和__rmul__()
除:__truediv__()
和__rtruediv__()
双除:__floordiv__()
和__rfloordiv__()
列表也实现了+和*运算对应的特殊方法
对于运算符右侧的特殊方法前面有个r开头。在运算中
- 会依次从左侧开始判断是否实现了相应运算(左侧调用非r方法,右侧带r)
- 如果左侧实现方法且结果不为
NotImplemented
则返回运算结果,否则判断右侧 - 如果左右都没实现,直接抛出TypeError异常
# obj1 + obj2,
class obj1(object):
def __add__():
return "xxx"
class obj2(object):
def __radd()__:
return "yyy"
obj1 + obj2 # xxx
2.5.6 特殊方法:__str__()
和__repr__()
内置函数str(对象)
:调用的是内置函数__str__()
内置函数repr(对象)
:调用的是内置函数__repr__()
两种特殊方法都没有实现:无论是print还是命令行直接输入该类都会直接打印出类类型和地址
当只实现__str__()
,调用print
或str()
会返回函数结果
当只实现__repr__()
,命令行直接输入或str()
,会返回函数结果
str()
优先使用__str__()
结果
repr()
只会使用__repr__()
结果
一般而言:str返回的是用户理解的,repr返回的是程序员理解的(比如转义或单引号
class obj(object):
def __str__(self):
return "xxx"
__repr__ = __str__
2.5.7 特殊方法:__new__()
创建实例对象分两步:1 调用__new__()
2 调用__init__()
__new__()
返回一个对象,该对象会传递给__init__()
作为参数
class P(object):
def __new__(cls,*args,**kwargs):
obj = super().__new__(cls)
return obj
class C(object):
def __init__(self,name):
pass
C() # 先在C中查找new特殊方法,没有的话会去父中查找,new的cls参数为C的类对象,返回结果为创建完的C类实例对象,会将该对象给C中的init方法的self参数值
2.5.8 特殊方法:__getattr__()
当访问实例对象的属性或方法时,如果该属性不存在,则会调用该__getattr__()
特殊方法!!
class My(object):
def __getattr__(self,name):
if name == 'd':
return "ddd"
else name == 'do_sth': # 如果是函数,可以返回一个函数
return print
raise AttributeError("属性不存在!")
My().d
My().do_sth(123) # 会打印出123
My().a
2.5.9 特殊方法:__getitem__()
、__setitem__()
和__delitem__()
实现了该方法的自定义类可以使用[]
来访问数据
__getitem__()
:触发方式:对象[key]
__setitem__()
:触发方式:对象[key]=value
__delitem__()
:触发方式:del 对象[key]
2.5.10 特殊方法:__call__()
实现了该方法的类的实例,可以直接加()来调用call方法
class My(object):
def __call__(self,*args,**kwargs): # args是元组格式 kwargs是字典格式
print(args,kwargs)
My()() # () {}
My()(1,2,x=3,y=4) # (1,2) {'x':3,'y':4}
callable(对象)
:判断一个对象是否是可调用的,True|False
实现了__call__()
的类,其实例也是可调用的
2.5.10 特殊属性:__doc__
与函数的文档字符串相似,位于类对象的第一行的字符串被称为文档字符串,通常用三个引号标识,用语简要描述功能,可以用工具自动生成文档
class My(object):
"""这是文档字符串"""
pass
访问文档字符串:
对象.__doc__
help(对象)
2.5.11 特殊属性:__slots__
python动态语言可以动态绑定属性和方法,如果想限定属性方法名可以重写该方法并给定范围
class My(object):
__slots__ = ("a","b","run") # 可以是列表或者元组
obj1 = My()
obj1.a = 1
obj2.b = 2
obj3.run = def abc: pass
注意:如果在类对象中定义类__slots__
,则不会创建__dict__
,而是为每个属性创建一个描述器,调用描述器快于访问__dict__
,由于不再创建__dict__
,不用创建hash表了,占用内存会减小
注意:该slot只对当前类实例对象起作用,其子类等无关。如果子类也定义了slot,则子类限定范围会加上父类的(范围更广)
2.6 对象引用计数
py在对象创建时会存在一个该对象的引用,只有当该对象的引用为0时,才会被销毁,释放内存。
引用+1的情况:
- 对象赋值给变量
- 变量赋值给变量
- 对象作为容器中一员
- 对象作为函数实参(函数结束自动-1)
import sys
sys.getrefcount(对象) # 获取对象的引用计数值,注意:该函数在执行过程中,传入的对象的引用计数会+1,结束后会-1
引用计数-1情况:
- 对象离开其作用域
- 引用被显式销毁
- 引用指向变更
- 引用在容器中删除
2.6.1 销毁对象或引用:__del__()
一般而言系统会自动清理内存和引用,但有时候需要在内存清理时或某些对象销毁时执行一些关闭前的清理工作:__del__()
,该方法在对象被销毁前会自动调用该方法。
del 对象
:手动删除销毁某个对象的引用,如果此时该引用指向的对象引用计数为0,在某些时刻会被垃圾回收,会在回收前调用__del__()
方法
3 模块
模块:就是一个py文件,定义在模块中的变量函数和类统称为模块的属性
优点:代码重用+避免属性名冲突
包:在某个目录下添加__init__.py
文件后,该目录就变成了包。即包含特定模块的特殊目录,该文件用于初始化模块,可以为空。包可以嵌套。
3.1 标准库
使用标准库必须用:import
进行导入,分为:整个模块导入和模块中属性导入
- 导入整个模块
import [包名.]模块名
访问模块中的属性:[包名.]模块名.属性名
起别名:import xxx as yyy
import os
print(os.environ) # 打印操作系统中的所有环境变量
print(os.getenv('PYTHON_HOME'))
- 导入模块中的属性
from [包名.]模块名 import 属性名[,属性名[,属性名]]
导入该属性后可以直接访问,无需再写包名模块前缀,可以一次导入多个属性,也可以给每个模块起别名
from os import environ
print(environ)
from os import environ,getenv
from os import environ as abc1,getenv as abc2
- 导入模块中的所有属性:
from [包名.]模块名 import *
不建议这样使用,效率低,可读性差,容易重名
3.2 第三方库
PyPI:Python Package Index,官方基于web的集中管理的第三方软件仓库。www.pypi.org
常用pip3工具从PyPI上下载第三方库。
pip:Package Installer for Python,该工具随Python一起安装,pip3
查看pip工具的安装路径:which pip3
or where pip3
安装第三方库:pip install 包名
第三方库安装目录:python安装目录下的\Lib\site-packages
使用第三方库:和标准库使用方式相同
3.2.1 pip使用
# 查看帮助信息
pip3
# 列出已安装库
pip3 list
# 模糊搜索在线库
pip3 search xxx
# 安装指定第三方库,不指定版本号默认最新版本
pip3 install xxx==版本号
# 升级第三方库
pip3 install --upgrade xxx
# 卸载第三方库,不指定版本号默认最新版本
pip3 uninstall xxx==版本号
# 查看某个命令帮助
pip3 命令 --help
3.2.2 发布自己的库
- 创建配置文件:
setup.py
from setuptools import setup
setup(
# 发布的包名
name = 'xiaopi_demo',
# 版本
version = '0.1',
# 描述
description = 'xxx'
# 如果当前项目包含子包,可以手动指定要包含的包或自动查询,如果不包含则可以不指定packages参数
packages = find_packages()
# 如果当前项目只有模块,则指定该关键字
py_modules = ['xiaopi3_demo_module']
)
关于如何使用setuptools可以参考官方文档
- 可选,创建其他文件
- 打包自己开发的库
将setup.py
和其他文件和自己的库放在同一个目录中,在该目录下执行:python3 setup.py sdist
,会在当前目录下生成一个dist
目录和其他目录,dist
目录下有一个包名-版本.tar.gz
包 - 将自己的库发布到PyPI或共享给别人
发布的话参考官方文档
安装本地离线库:在库文件所在目录下执行:pip3 install 库文件名
3.3 Anaconda
直接下载安装,并自动添加安装目录下的bin目录到系统path中
注意:安装完anaconda后使用python命令调用的是:anaconda目录/bin
下的python,pip3同理会安装到anaconda/lib/python3.x/site-packages
3.3.1 conda管理第三方工具
anaconda使用conda管理其第三方包,类似于pip3
# 查看帮助
conda
# 列出anaconda安装的所有第三方库
conda list
# 模糊搜索第三方库
conda search xxx
# 安装指定库,可以指定版本
conda install xxx=版本
# 升级指定库
conda update xxx
# 卸载指定库,可以指定版本
conda remove xxx
# 查看命令帮助
conda 命令 --help
3.4 模块导入
注意:直接运行某个模块时,该模块视为主模块,在最顶层,无法与项目其他模块形成相对关系,不能在当前运行的模块中使用相对导入。
问题: 导入的不同模块中存在相同属性
结论:后倒入的属性会覆盖前面导入的同名属性,建议使用as
进行重命名
3.4.1 import语句执行流程
使用import导入时,解释器会根据sys模块的modules属性值来查找模块是否已被导入
import pprint,sys
pprint.pprint(sys.modules) # pprint可以格式化打印字典
情况:
- 模块已被导入,什么也不做
- 模块没有导入:搜索模块–>将模块编译成pyc字节码(如果需要)–>执行字节码运行模块
3.4.2 模块搜索路径
搜索路径分成三个部分:
- 当前目录下
- 标准库目录
- 第三方库安装的目录
修改路径:
- 直接修改sys.path,其为一个列表,只是临时修改,运行环境结束就失效
import sys
sys.path.insert(0,'/xxx/xxx') # 修改导入模块搜索路径,在首位插入自定义的路径
import abc # 此时再导入时,第一搜索路径是自定义的路径
abc.run()
- 设置环境变量PYTHONPATH,该路径会被自动添加到搜索路径中,永久有效
3.4.3 编译模块成pyc
当搜索到的模块第一次被导入,会被编译成pyc文件,存放在模块同目录下的__pycache__/模块名.cpython-版本号.pyc
,如果该模块无变化,下次导入时会直接读取缓存的pyc,无需再次编译。
3.4.4 运行字节码文件
导入包中的模块会先运行包中的__init__.py
文件,且从最顶层的包开始运行,知道被导入模块那个包为止,导入模块就会立即运行模块!
3.4.5 查看模块的所有属性
dir(模块)
:可以查看该模块的所有属性,如果不带参数,则返回当前作用域下的所有属性(由于是解释性语言,所以当前作用域中包含的属性多少与调用顺序有关)
3.4.6 重新加载
使用import语句导入后,如果在这期间该模块做了修改,再次导入是无法获取最新修改值的,需要使用标准库importlib
中的:importlib.reload(模块)
来重新加载模块
3.4.7 __doc__
模块的文档字符串:用于描述和解释模块功能的字符串,使用"""
来包裹,位于模块的第一行,可以使用工具自动生成文档
print(__doc__) # 打印当前文档的文档字符串
import base64
print(base64.__doc__)
print(help(base64))
3.4.8 __name__
模块的__name__
:对于被导入的模块,该属性为模块名,对于被直接运行的模块,该属性为__main__
可以根据__name__
的值来判断是否执行模块测试代码
直接运行模块时执行测试代码,如果是被导入的,则不需要执行
print("运行a模块")
def add(x,y):
return x+y
if __name__ == '__main__':
print("1+2="+add(1,2))
3.4.9 模块内数据访问控制:_属性
、__all__
_属性
:如果模块内的属性前添加了但下划线,则无法使用from 模块 import *
进行属性导入,但使用import 模块
这种方式可以
__all__
:作用是限制使用from 模块 import *
进行属性导入时只能导入__all__
中定义的
__all__ = ['v1', 'v2']
v1 = 12
def v2():
pass
v3 = 14
注意:如果两种都使用了,则__all__
优先级高于_属性
4 生成器和迭代器
4.1 生成器
生成器是一个推算元素的一组代码,它不保存整个序列,而是在迭代中推算出下一个对象,如果需要创建一个大容量序列,为了节省空间可以考虑使用生成器!
列表、字典、集合生成式,无元组生成式(不可变)
创建一个生成器:
[i*i for i in range(1,7)] # [1, 4, 9, 16, 25, 36]
生成器如:(i*i for i in range(1,7))
,返回的就是一个生成器,该生成器对象每次调用next()
都会返回下一个可访问对象
ge = (i*i for i in range(1,7))
next(ge) # 1
next(ge) # 4
当调用next(生成器)
时无下一个对象,会抛出异常:StopIteration
也可以使用for in
语句来迭代生成器:
ge = (i*i for i in range(1,7))
for item in ge:
print(item)
4.1.1 生成器表达式
使用类似生成式语法:(i*i for i in range(1,7))
称为生成器表达式,如果推算方法比较复杂时,推荐使用生成器函数
4.1.2 生成器函数
对于普通函数来说:
def fib(n):
i=0
a=1
b=1
while i<n:
print(a)
tmp = a
a = b
b = tmp + a
i+=1
# 另一种写法
def fib2(n):
i = 0
a,b = 1,1
while i<n:
print(a)
a,b = b,a+b
i+=1
fib(6)
将这个普通函数改成生成器函数,即print
改成yield
即可,当调用next()
或者for in
迭代时,每次代码执行到该处时会挂起函数,下次从挂起处继续执行。
def fib2(n):
i = 0
a,b = 1,1
while i<n:
yield a
a,b = b,a+b
i+=1
f = fib2(3)
next(f) # 1
next(f) # 1
next(f) # 2
next(f) # StopIteration
4.2 迭代器
【可迭代对象】:可以使用for in
进行迭代的,如:range、列表、元组、字符串、字典、集合、生成器
isinstance(对象,可迭代类型)
可以判断一个对象是否是可迭代对象。(Iterable
)
from collections import Iterable
isinstance([1,2],Iterable) # True
【迭代器对象】:可迭代对象能作为next()
的实参,那么称为迭代器对象(Iterator
)
isinstance(对象,迭代器类型)
可以判断一个对象是否是可迭代对象。
from collections import Iterator
isinstance([1,2],Iterable) # False
可迭代转换成迭代器:iter(可迭代对象)
特殊的:如果一个对象同时实现了:__iter__()
和__next__()
,那么该对象是一个迭代器对象,用于for in 语句时,会首先调用前者,然后不断调用后者来返回下一次值
class MyIter(object):
def __init__(self):
self.data = 0
def __iter__(self):
return self
def __next__(self):
if self.data > 5:
raise StopIteration()
else:
self.data += 1
return self.data
for item in MyIter():
print(item)
5 异常
无语法错误的程序在运行期间产生的特定错误,每个特定错误都对应一个异常类,当抛出时抛出的是异常类对应的异常对象
如果对抛出的异常对象不进行捕获处理,则程序会停止运行,并打印堆栈跟踪和错误详情
所有异常对象的基类:Exception
5.1 异常捕获
格式:
try:
可能存在异常的代码
except 异常类1:
捕获到对应异常时的处理
except 异常类2 as err:
print(err)
print(type(err))
捕获到对应异常时的处理
except (异常类3,异常类4,异常类5):
捕获到对应异常时的处理
except :
捕获到对应异常时的处理
...
当有异常且被exception捕获时,会进入异常处理流程,否则有异常会直接终止程序,没有异常在执行完代码后不会执行异常处理。
注意:except匹配是按顺序的,匹配到后其他的except不会再去匹配了。建议在except中将所有异常尽量覆盖到,如:最后一个excep匹配Exception或不指定缺省。
5.2 异常从句else
在while和for in语句后的else从句执行流程:先执行循环,如果循环出现break,则直接结束跳到else语句后执行,否则执行完循环在执行else语句。
异常语句也可以加else
try:
...
except xxx:
...
else:
...
执行流程:先执行try,如果try出现异常:执行except,否则执行else,二选一
5.3 异常从句finally
finally从句无论何时都会被执行,常用来执行释放资源动作关闭连接池等。也可以同时添加else和finally
格式:
try:
...
except xxx:
...
else:
...
finally:
...
注意:else必须要在finally从句之前
5.4 手动抛出异常
格式:raise 异常类(参数)
如果没有参数,可以省略括号
可以原样抛出异常:
try:
...
except xxx:
raise # 此处会再次抛出这个异常
except xxx:
raise yyy # 此处抛出另外一个异常
5.5 自定义异常
继承任何一个异常类即可
class MyError(Exception):
def __init__(self,msg1,msg2):
self.msg1 = msg1
self.msg2 = msg2
raise MyError("abc","eee")
5.6 异常信息
异常调用堆栈
异常发生后,会打印异常调用信息,异常调用堆栈顺序为:最上层的为最外层的调用,最底层为异常产生处
异常处如果没有捕获异常,则会往上一层抛异常,知道最顶层python解析器捕获到
sys.exc_info()
对于sys模块的exc_info()方法,可以返回当前异常的三种信息:
- 第一位:ex_type,异常类型
- 第二位:ex_value,异常错误信息
- 第三位:ex_traceback,异常跟踪信息
import sys
import traceback
try:
raise ZeroDivisionError:
except Exception:
ex_type, ex_value, ex_traceback = sys.exc_info() # 返回值是一个元组,可以用这种方式接收
tb = traceback.extract_tb(ex_traceback)
print(tb)
for filename, linenum, funcname, source in tb: # 每一项是一个元组,可以这样取值
print(filename, linenum, funcname, source)
5.7 with语句
上下文管理器:如果类实现了:__enter__()
和__exit__()
,该类实体为上下文管理器
with语句会让上下文管理器创建一个运行时上下文,进入时调用enter,离开时调用exit
规则为:
- 如果执行语句体时无异常,则执行顺序为:enter、语句体、exit
- 如果执行语句体有异常,则顺序为:enter、语句体执行到异常处终止、exit,此时看exit返回如果为True则程序继续执行,否则程序终止执行(可以try except进行捕获)
在exit特殊方法中会自动传入异常相关信息:异常类型,错误信息,堆栈信息
格式:
with 上下文表达式 [as 变量]:
语句体
# 正常执行时
class MyContext(object):
def __enter__(self):
print("enter")
return self
def __exit__(self,e_type,e_val,e_tb):
print("exit")
def do(self):
print("do")
with MyContext() as mm:
mm.do()
# enter
# do
# exit
# 异常执行时
class MyContext(object):
def __enter__(self):
print("enter")
return self
def __exit__(self, e_type, e_val, e_tb): # 此处为异常的信息:异常类型,错误信息,堆栈信息,会自动被赋值
print("exit")
def do(self):
print("do1")
print(1 / 0)
print("do2")
# enter
# do1
# exit
# Traceback 。。。。
# 异常时,且exit返回True
class MyContext(object):
def __enter__(self):
print("enter")
return self
def __exit__(self, e_type, e_val, e_tb): # 此处为异常的信息:异常类型,错误信息,堆栈信息,会自动被赋值
print("exit")
return True
def do(self):
print("do1")
print(1 / 0)
print("do2")
with MyContext() as mm:
mm.do()
# enter
# do1
# exit
6 文件
内置函数打开文件:open()
,返回值是文件对象,该对象用于操作文件
open(path,mode)
# path:路径,绝对或相对
# mode:打开方式,默认r只读,不存在抛出FileNotFount异常。
# w只写,存在就清空,不存在就创建。
# a追加,存在则末尾追加,不存则在创建。
# x只写,存在抛出FileExistsError,不存在则创建。
# +读写。
# t文本(所有都是默认文本方式)。不可单独使用
# b二进制。不可单独使用
注意:在 Python 中,不同的字符所占的字节数不同,数字、英文字母、小数点、下划线以及空格,各占一个字节,而一个汉字可能占 2~4 个字节,具体占多少个,取决于采用的编码方式。例如,汉字在 GBK/GB2312 编码中占用 2 个字节,而在 UTF-8 编码中一般占用 3 个字节。
read中size按照字符计算,seek中按照字节计算
6.1 读文件
格式:read()
注意:每次读取读取指针会从上一次继续往后走,文件使用完必须关闭
read() # 一口气读到末尾
read(size) # 读取指定长度字符数,大于文件字符数或者小于0则直接读完(内存占用过大)
注意:不要一次将文件读完,这样内存有可能爆掉,建议一次读取小于默认缓冲区大小
# 默认缓冲区大小
import io
io.DEFAULT_BUFFER_SIZE # 8192
格式:readline()
读取一行数据
readline() # 读取一行
readline(size) # 读取一行中的指定字符数,大于改行长度或者小于0则直接读完该行
格式:readlines()
读取多行数据
readlines() # 读取到文件末尾,返回值为[]
readlines(size) # 从当前读取到指定字符数的行的行尾,大于剩余长度或小于0直接读完文件
6.2 写文件
格式:write(内容)
注意:会先将指定字符串写入缓存,调用flush或关闭文件或超过缓存上限才会真的写入文件
格式:writelines([内容,内容])
将一个内容序列写入文件
6.3 关闭文件
文件对象会占用操作系统资源,且操作系统某一时刻打开的文件上线是有限的
一般用try或者with来保证close文件方法总能被调用
file = open('a.txt','w')
try:
file.write('hello')
finally:
file.close()
with open('a.txt','a') as file:
file.write('pp')
6.4 文件指针
读写文件时文件指针会随之移动,追加方式打开,文件指针指向结尾位置,其他方式打开是起始位置。
获取文件指针的当前位置:tell()
with open('a.txt','a') as file:
print(file.tell()) # 19
with open('a.txt','w') as file:
print(file.tell()) # 0
手动移动文件指针:seek(offset[,whence])
,offset为偏移量,可为负数,whence为相对偏移位置:os.SEEK_SET:起始位置,值0,默认
os.SEEK_CUR:当前位置,值1
os.SEEK_END:结尾位置,值2
只有以二进制方式打开才支持三种,否则仅支持第一种
7 函数和lambda表达式
函数也是对象,可以被赋值给其他对象
def add(x,y):
return x+y
a = add
a(1,2)
作为参数传递
def run(add_func,y)
return add_func(1,y)
def add(x,y):
return x+y
run(add,2)
作为返回值传递
def run(num):
a = num
def add(x,y):
return x+y+a
return add
run(3)(1,2)
7.1 lambda
格式:lambda [参数]: 表达式
def add(x,y):
return x+y
func = lambda x, y: x + y
func(1,2)
7.2 偏函数
用于简化函数调用,转换后的函数称为转换前函数的偏函数,简单来说,偏函数相当于将原来的函数在调用前传参时包了一层
格式:partial(函数,参数1,参数2,...)
,partial(func,*args,**kwargs)
def add(x, y=1, z=0):
print("x =",x,", y =",y,", z =",z)
# 手动包一层
def add2(a):
return add(2, a)
add2(3)
# 自动包一层
from functools import partial
add3 = partial(add, 87) # 第一个参数为需要转换的函数,第二个参数:入参(从开头算起)
add3(z=3)
add3(3)
add3(y=3)
#### 结果
# x = 2 , y = 3 , z = 0
# x = 87 , y = 1 , z = 3
# x = 87 , y = 3 , z = 0
# x = 87 , y = 3 , z = 0
注意:在函数传参时,指定的参数必须位于不指定的参数后:(1,2,z=3)
,不能:(x=1,2,3)
def run(*args):
s=0
for n in args:
s+=n
return s
run2=partial(run,1,2)
print(run2(3,4,5))
注意:长度可变的形参定义方式:*args
,长度可变的关键字参数定义方式:**args
7.3 可变参数和关键字参数一起使用
偏函数转换时:指定的参数是前面若干位置参数,调用转换后的函数传递参数是剩余的参数和关键字参数
情景1:
情景2:
7.4 闭包
函数返回值为一个定义在该函数中的函数,且这个内部函数引用了外部函数中的变量,这样就构成了一个闭包。
通常而言,函数调用结束,函数内的变量就自动弹栈不再可用,而对于闭包,被内函数引用的变量依然会继续保持在内存中,保存在内函数的特殊属性中:__closure__
def a():
b=23
bbb=[11]
def c():
# nonlocal bbb
bbb[0] = bbb[0]+1
return b,bbb,10
return c
x=a()
print(x.__closure__[0].cell_contents) # 23
print(x.__closure__[1].cell_contents) # [11]
print(x()) # (23, [12], 10)
print(x.__closure__[0].cell_contents) # 23
print(x.__closure__[1].cell_contents) # [12]
注意:内函数无法对外函数中的变量做修改,但是变量是可变对象时可以改变(如列表中的值),否则任何的关于外部函数变量的赋值都是重新创建一个局部变量。如果在内部函数使用:nonlocal
声明外部函数变量后,表示当前作用域使用的不是局部变量,则可以对外部函数变量修改,且该修改在下次调用同一个内部函数对象时是有效的!
8 变量作用域
作用域有4种:
- Local局部作用域:每次创建函数会创建一个局部作用域,变量作用范围限制在该函数范围中定义处到函数结束,结束时局部作用域中的变量被销毁
- Enclosing嵌套作用域:调用外函数时会创建一个嵌套作用域,在外函数中的变量作用范围同局部作用域一样,只不过被内函数引用形成闭包的那部分变量在外函数调用结束也不会被销毁
- Global全局作用域:每次运行模块时都会创建全局作用域,其中定义的变量为全局变量,作用范围为定义变量处到模块结束,程序结束,全局变量会被销毁。
- Built-in内置作用域:加载python解释器会自动加载内置模块创建内置作用域
在python中搜索作用域为当前作用域范围以及更大的范围,没搜索到抛出NameError,作用域搜索遵循就近原则
特别注意:在更小的作用于范围内使用更大范围的变量时,可以直接使用,但是如果涉及到赋值给更大范围变量时默认是创建局部同名变量(如果是可变对象,可以改变可变对象的值),如果需要赋值给更大范围变量需要使用global 变量
进行声明
x=1
def a():
global x
x+=1
print(x)
a()
注意:对于流程控制语句和异常处理语句不会创建作用域,其中创建的变量,在之后依然是可用的
8.1 locals && globals 内置函数
命名空间:不仅是变量也包括类和函数
locals()
:返回其所在局部作用域的命名空间
globals()
:返回其所在全局作用域的命名空间
注意:locals()返回的是值的拷贝,修改该值不会影响命名空间,而globals返回的是实际命名空间
8.2 vars([object])
如果不传参数相当于:locals()
传参数相当于:object.__dict__
9 函数装饰器
函数装饰器也是一个函数,格式:
def 装饰器(函数):
def 内函数(*args, **kwargs):
一系列的附加操作
return xxx
return 内函数
# 使用时:在函数上加@装饰器即可,此处相当于:add = 装饰器(add)
@装饰器
def add(x,y):
return x+y
add(1,2) # 此时调用的是内函数,传入参数为1,2
示例:
def myDecorator(func):
def wrapper(*args,**kwargs):
print("函数%s被调用了" % func.__name__)
print("入参为:%s %s" % args,kwargs)
return func(*args,**kwargs)
return wrapper
@myDecorator
def add(x,y):
return x+y
print(add(1,2))
# 函数add被调用了
# 入参为:1 2 {}
# 3
问题:如果调用被装饰了的函数的__name__
属性,返回的会是装饰器内函数,为了让其返回被装饰函数自己的函数名需要处理一下
# 在内函数上加装饰器:@functools.wraps(函数)
import functools
def 装饰器(函数):
@functools.wraps(函数)
def 内函数(*args, **kwargs):
一系列的附加操作
return xxx
return 内函数
9.1 装饰器传参
如需传参需要在原无参装饰器外再套一层装饰器:
import functools
def 外装饰器(参数1,参数2):
def 装饰器(函数):
@functools.wraps(函数)
def 内函数(*args, **kwargs):
一系列的附加操作
return xxx
return 内函数
return 装饰器
#对于被装饰函数myFunc来说:
@外装饰器(1,2)
def myFunc(x,y):
pass
# 相当于:myFunc = 外装饰器(1,2)(myFunc(x,y))
# 调用
myFunc(1,2) # 相当于:myFunc = 外装饰器(1,2)(myFunc(1,2))