Python 复习
https://docs.python.org/zh-cn/3/tutorial/stdlib2.html:这里有一些标准库,但这么久了好像都没怎么用到。
*args:直接传参的时候会传递给这个形参
**kwargs:关键字传参的时候会传递给这个形参
func(1, 2, '3', '4', client="John Cleese")
# 上面调用中,1,2,3,4都会传递给args
# client会传递给kwargs
运算符优先级
运算符说明 | Python 运算符 | 优先级 | 结合性 |
---|---|---|---|
小括号 | ( ) | 19 | 无 |
索引运算符 | x[i] 或 x[i1: i2 [:i3]] | 18 | 左 |
属性访问 | x.attribute | 17 | 左 |
乘方 | ** | 16 | 右 |
按位取反 | ~ | 15 | 右 |
符号运算符 | +(正号)、-(负号) | 14 | 右 |
乘除 | *、/、//、% | 13 | 左 |
加减 | +、- | 12 | 左 |
位移 | >>、<< | 11 | 左 |
按位与 | & | 10 | 右 |
按位异或 | ^ | 9 | 左 |
按位或 | | | 8 | 左 |
比较运算符 | ==、!=、>、>=、<、<= | 7 | 左 |
is 运算符 | is、is not | 6 | 左 |
in 运算符 | in、not in | 5 | 左 |
逻辑非 | not | 4 | 右 |
逻辑与 | and | 3 | 左 |
逻辑或 | or | 2 | 左 |
逗号运算符 | exp1, exp2 | 1 | 左 |
- 所谓结合性,就是当一个表达式中出现多个优先级相同的运算符时,先执行哪个运算符:先执行左边的叫左结合性,先执行右边的叫右结合性。
类的常用内置属性:
- __dict__:类的属性字典
- __bases__:类的所有父类构成的元组
- __doc__:类的文档字符串(类的介绍)
- __name__:类名
- __module__:类定义所在的模块
类的常用内置方法:
__str__:如果定义,那么在 print 输出这个类的时候会输出这个方法的返回值。
__repr__:如果__str__未定义,那么 print 输出的时候,会再找 repr
- repr 面向的是开发人员,在控制台交互模式下,直接输出类对象就会输出 repr 的返回值
__call__:当一个类实例化出一个对象,默认情况下是不能直接加括号进行调用的
- 需要实现__call__函数,当实例化的对象以函数形式调用就会调用 call
__setitem__:当实例通过.属性,或者[]设置值或者对象的时候会调用这个方法。
- 只有在这个方法内进行赋值,才会真正的复制到 dict 中
- 但函数默认是不支持[]列表形式赋值的,需要自己实现
__getitem__:当实例获取属性的时候会调用这个方法
- 当被迭代或 in 的时候,也会返回这里返回的内容
- 也就是可以被迭代,需要终止条件,要不然就会死循环
- 主动定义返回的内容,结束主动抛出异常即停止
__delitem__:当实例删除属性的时候会调用这个方法
类实例的切片操作:同样是会在__getitem__,__delitem__,__setitem__内部被拦截,
- 在 getitem 的 key 中传入的就是切片范围以及步长
__bool__:这个方法返回的内容,会被比较操作的时候作为参数(比如 if 比较操作)
__iter__:被迭代的时候优先调用 iter,iter 没有定义才会去找 getitem。
- 返回一个迭代器即可
- 比如:
return iter([1,2,3,4,5])
- 每次被迭代的时候,会先调用 iter 获取迭代器
- 如果类本身已经定义__next__:那么简单的返回 self 即可。
__next__:当对象被迭代或者用 next()调用的时候,自动调用这个方法
- 需要终止条件,以抛出异常为终止
- 当 iter 获取到迭代器的时候,每次迭代调用这个方法
迭代器:需要同时实现 next 和 iter 方法,或者用 getitem 配合 iter
__getattribute__:描述器相关操作
- 当属性实现了描述器的__get__方法,那么直接调用
- 如果没有实现,则按内部的查找顺序进行查找
__enter__:上下文进入后主动调用
__exit__:上下文结束后主动调用
类实例大于,小于,不等于,等操作的实现
- __eq__:相等 and 不相等,接受的参数:other 代表另外一个参数
- _ne__:只实现 eq 就会自动实现相反的操作,但是 ne 代表的就是!=
- __gt__:大于
- __ge__:大于等于
- __lt__:小于
- __le__:小于等于
- 如果在定义的时候只定义了大于,但是调用的时候调用了小于,那么解释器会自动调换参数,来调用大于
元类:
- 比如 int,str 的类都继承自 type
- 除了用 class 关键字,也可以用 type 进行类的实例化
- 查找机制:
- __metaclass__:指定实例化的元类
- 先查找本身是否有指定实例化的元类
- 没有后再查找父类是否有指定
- 最后查找模块是否有指定
- 如果都没有,那就以 Python 内部定义的元类为准
私有属性:(Python 的私有都是假私有)
- _a:单下划线,属性只能在模块内部访问,外部导入需要用__all__方法进行声明
- __a:双下划线,属性只能在类内部访问,外部可以通过某种手段访问,但不推荐
私有属性的设置和获取
- 虽然可以定义一个方法进行获取以双斜杠开头的属性,但是每次都要调用一个函数来获取就增加麻烦
- 可以使用装饰器,将函数指定为一个属性
- @property:将一个属性删,改,查关联到一个函数内
class T:
def __init__(self):
self.__age = 123
# property直接将函数改为属性,无须执行即可获取结果
# 获取属性,每个的函数名字必须都一样
@property
def age(self):
return self.__age
# 设置属性
@age.setter
def age(self, value):
self.__age = value
# 删除属性
@age.deleter
def age(self):
del self.__age
# 下面为第二种写法
# 实现三个操作函数,当外界以属性的方式调用的时候,自动调用相应的函数
age = property(age_get,age_set,age_del,'第四个参数为描述器的描述')
a = T()
a.age = 2313
print(a.age)
私有属性设置方法二:
# 当通过.属性的方式或者[]进行赋值以及取值操作的时候,都会走这个方法,
# 但函数默认是不支持[]列表形式赋值的,需要自己实现
# 在这个属性内部,才会真正的修改数据到__dict__
def __setattr__(self, key, value):
# 需要限定的话可以进行如下操作
if key == 'age' and key in self.__dict__.keys():
# 如果判断是age,并且存在于keys中,不进行操作
pass
else:
# 不是进行赋值,这里不能以.属性进行赋值,否则会死循环
self.__dict__[key] = value
私有方法:
- 私有方法的规则和私有属性的规则相同
描述器:将类定义成一个属性:
- 描述一个属性操作的对象
- 描述器的定义方式之一就是用装饰器:@property
- 但这种方式赋值的私有属性还是可以被修改。
- 所以就引入了第二种方式
class Age:
# 定义设置,获取,删除操作
def __set__(self, instance, value):
print(instance, value)
print('set')
def __get__(self, instance, value):
print('get')
def __delete__(self, instance, value):
print('delete')
class T:
# 实例化描述器
age = Age()
def __init__(self):
# 这里操作表示的是操作描述器,而不是定义一个新的属性
self.age = 2
# 以.属性的方式,直接调用
t = T()
t.age = 1
"""
__getattribute__:描述器相关操作
+ 当属性实现了描述器的__get__方法,那么直接调用
+ 如果没有实现,则按内部的查找顺序进行查找
"""
装饰器:
正常实现装饰器:
import functools
def pr(func):
# 保留元信息
@functools.wraps(func)
def inner():
print('装饰器内部')
func()
return inner
@pr
def func():
print('函数内输出')
# 执行被装饰器装饰的函数
func()
# 不带参数的可以理解为
func = pr(func)
func()
带参数的普通装饰器:
# 带参数的普通装饰器
import functools
def ar(arg):
def pr(func):
# 保留元信息
@functools.wraps(func)
def inner():
print('装饰器内部')
func()
print('传入的参数是:', arg)
return inner
return pr
# 装饰器
@ar('123')
def func():
print('函数内输出')
# 执行被装饰器装饰的函数
func()
# 带参数的函数执行流程
# 先执行函数,传入参数
f = ar('123')
# 这时候上面的ar会返回一个函数,再将被装饰的函数传入
f(func) # 这里还是会返回一个函数
# 然后最终返回一个新的函数,当调用原始的函数的时候这个函数就会被执行
f()
将类当作装饰器:
class Pr:
# 实例化的时候,会将装饰器下面的函数输出到init方法
def __init__(self, func):
self.func = func
# 并且会自动执行实例,也就是自动调用这个方法
def __call__(self, *args, **kwargs):
print('类装饰器内部执行')
self.func()
# 将类当作装饰器
@Pr
def func():
print('函数内输出')
# 当类被当作装饰器的时候执行和普通的装饰器一样
# 初始化,然后调用
# 初始化写init方法,调用写call方法
# 所以实现一下即可
将类当作装饰器 并且支持参数
class Pr:
def __init__(self, arg):
self.arg = arg
def __call__(self, func):
def wrapper(*args, **kwargs):
print('装饰器参数:', self.arg)
func(*args, **kwargs)
print('函数执行完毕')
return wrapper
@Pr(777)
def func1():
print('函数内输出')
内存管理机制
- Python 是万物皆对象
- 使用引用来进行后续处理(根据类型的不同,开辟不同的空间进行存储)
- 可以使用 id 获取十进制内存地址,hex 获取十六进制内存地址
- Python 对小整形,以及部分短浮点型会创建一个对象的引用,不会创建多个相同的对象。
- 内存泄露:a 对象引用 b 对象,b 对象引用 a 对象。并且没有任何其他对象指向这两个对象可以供进行操作。
- 就导致了互相存在内存中无法被释放
上下文管理器
class T:
def __init__(self):
self.t = 666
def __enter__(self):
print('上下文管理器开始!')
# 这里直接返回类的本身,with后面就是类实例
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('上下文管理器开始!')
async def __aenter__(self):
print('异步上下文管理器开始!')
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print('异步上下文管理器结束!')
# with后面的as就是在enter中返回的对象
with T() as t:
print(t.t)
async with T() as t:
print(t.t)
# 当上下文没有调用之后,自动调用exit方法
三大特效
封装:
- 将一写属性和方法相关的方法封装在一个对象中,对外隐藏内部具体实现细节
继承
- 一个类“拥有”另外一个类的“资源”的方式之一
- 拥有:并不是复制资源变成两份,而是资源的使用权
- 资源:指的是非私有的属性和方法
- 继承分为单继承和多继承。
- 从创建的角度来说,任何数据类型都由 type 创建(包括 object)
- 从继承的角度来说,任何类都继承自 object(包括 type)
- 以双下划线开头的属性无法被子类继承(或许是可以继承,但访问需要特殊方式)
- 如果 A 里面有 age=10,B 继承自 A,那么在 B 只有 A 里面 age 的使用权限。如果使用 B.age=9
- 属于是给 B 定义了一个新的 age=9,并不会修改 A 内的 age
MRO 继承链:
单继承:最简单的继承,依次向上查找即可(从下往上即可)
无重叠的多继承:多继承,但每个类都是依次继承不同的类
- d(a,b,c):前面的多继承链,在查找时会先找 a,再找 b,再找 c。
- 并且会一直找到继承链的顶端之后,才会接下去找
有重叠的多继承:多继承,继承多个类,并且这多个类还继承自同一个类
- d(a,b),并且 a 和 b 同时继承自 c:查找流程就是先查找 a,再找 b。最后才会去查找到共同继承的 c
多继承的资源访问顺序:
- 当一个类继承了多个类之后,比如每个类都有相同的 age 属性,那么按照查找顺序返回 age。
- 并不是说,优先级高的类,把优先级低的类给覆盖了,而是查找顺序优先返回了。
- 方法的也是相同的,就相当于优先级高的重写优先级低的
- cls 和 self 问题:在继承的关系中,self 和 cls 代表调用者本身,不会因为向上查找就变为父类。
- 因为查找顺序的原因初始化方法都是会默认调用 init 方法,如果子类重写了 init 方法那么系统就不会自动去调用父类的 init 方法,导致父类的 init 内部值无法被初始化。所以当父类的初始化方法中有需要初始化的值,并且子类也要重写 init 方法的时候,需要用 super 来调用父类的初始化方法
- super:起着代理的作用,帮助我们沿着 MRO 链条,找到下一级的节点,调用对应方法
- 参数一:查找这个参数的下一个节点
- 参数二:沿着哪个对象的 MRO 链条往下找
- 使用的是第二个参数进行(self,cls)调用
class A:
def __init__(self):
self.a = 'a'
print(self.a)
@classmethod
def get_a(cls):
print('a')
class B(A):
def __init__(self):
# 沿着B的查找下一个节点,因为B继承自A,所以找到的就是A
# 沿着self(实例)的MRO链条往下找,所以找到了A
# 因为上面的是沿着B往下找,所以这里的MRO链条,需要能找到B
# 因为self是B的实例,所以self往上找就可以找到B。
# 最终调用了A的init方法
super(B, self).__init__()
self.b = 'b'
print(self.b)
@classmethod
def get_a(cls):
# 沿着B的查找下一个节点,因为B继承自A,所以找到的就是A
# 使用的是第二个参数进行(self,cls)调用
# 所以传入到A中的get_a方法的cls是(cls)B
super(B, cls).get_a()
print('b')
b = B()
b.get_a()
# 正常情况下推荐以下写法,系统会自动传入当前的类对象
# 并且将方法的第一个参数作为参数传入
class B(A):
def __init__(self):
super().__init__()
多态:
- 一个类延伸的多种形态,调用时的多种形态
- 同一个方法,不同的对象调用,产生不同的形态
- 但是在 Python 中没有真正意义上的多态,也不需要多态。
抽象:
- 抽象类:并不是某一个具化的类。不能直接创建抽象类的实例——会报错
- 抽象方法:不具备具体实现,不能直接调用,子类必须要实现这个抽象方法,否则报错。
- 比如动物:
- 动物是一个抽象类,是一个统称,并不存在动物这个类型。
- 但狮子,老虎都是属于动物。
- Python 中并不能直接实现抽象类,需要使用 abc 模块。
与或非复习
if __name__ == '__main__':
set1 = {1, 2, 3, 4}
set2 = {4, 5, 6, 7}
# and 运算符是左边和右边都要成立,所以肯定会返回右边的内容
# 先检测左边,再检测右边。最终返回右边内容。
print('我是and的结果:', set1 and set2)
# 我是and的结果: {4, 5, 6, 7}
# or 就是左边和右边要有一个成立,如果左边成立就不会检测右边了。
# 所以左边set1有值,直接返回左边的内容。
print('我是or的结果', set1 or set2)
# 我是or的结果 {1, 2, 3, 4}
# 与(交集) 运算符 输出set1 与 set2中 都有的元素
# 内存比较是:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0
# 也就是说上面和下面比,最终只有4是两个都为1,所以最终只有返回一个4.
print('与 运算结果:', set1 & set2)
# 与 运算结果: {4}
# 或(并集) 运算符:两个位中,只要有一个位置为1,那么这个结果就为一
# 所以,两个集合的内容都会输出,重复的只会输出一次
print('或 运算结果:', set1 | set2)
# 或 运算结果: {1, 2, 3, 4, 5, 6, 7}
# 异或(补集) 运算符:两个集合不是共同拥有的内容
# 内存比较:set1 和 set2 中不一样的位置才为1
print('异或 运算符:', set1 ^ set2)
# 异或 运算符: {1, 2, 3, 5, 6, 7}
# 差集 简单理解就是输出前面那个独有的
# 比如下面就是set1中独有的内容
print('差集:set1中独有的:',set1 - set2)
# 差集:set1中独有的: {1, 2, 3}
参数注解
类型注解并不代表强制要求了函数的传参,比如一个函数指定了传入的类型是 str,但你还是可以传入一个列表,这注解是给程序员看的
并且有一个好处就是,可以让编译器知道是什么类型,就会有相应的代码提示:
那些 int,str,List 就不用写了:写稍微复杂一丢丢的:
- 列表内的每个元素都是字符串:
arg:List[str]
- 字典 key 是字符串,value 是数字类型:
Dict[str,int]
- 函数的返回值是列表,列表内是小数:
def func(arg: Dict[str,int]) -> List[float]:
- 函数参数可以是字典,也可以是元组,返回是集合:
def func(arg: Union[dict, Tuple]) -> Set:
- 空元组可以写为:
Tuple[()]
- Union 联合类型必须是其中的一个。
- Union[int]:代表的就是 int 本身
Union[int,str]
代表可以是int
可以是str
- 空元组可以写为:
- 还有:
Iterable:可迭代类型
Iterator:迭代器类型
Generator:生成器类型
- arg 是一个可以被调用的类型:
def func(arg: Callable):
- arg 是一个可以被调用的对象,并且调用需要传入一个列表,和一个浮点数:
def func(arg: Callable[list, float]):
- 可选类型:
def func(arg: Optional[int] = None) -> None:
:arg 可以是 int,或者 None - 自定义类型:
class MyType:
def __init__(self):
self.__age = 18
def get_age(self):
return self.__age
# 函数li代表 用户定义的类 并且返回值也是用户定义的类
# 在内部调用也会出现代码提示。
def func(li: MyType) -> MyType:
li.get_age()
return li
官方文档笔记
- 字典直接 in 是得到的 key,需要解包键值对可以用:
dict.items()
:同时取出对应的键和值。 - 复制一个对象可以用 obj.copy()
- enumerate:同时取出索引,和对应的值。返回一个的是一个迭代器;
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
print(list(enumerate(users)))
# [(0, 'Hans'), (1, 'Éléonore'), (2, '景太郎')]
- 浏览器备注所写的注释,可以用 func__doc__:取出来
def func(*args, **kwargs):
""" 文档字符串位置。。。。 """
print(args, kwargs)print(func.__doc__)
列表:
使用列表方法实现堆栈非常容易,最后插入的最先取出(“后进先出”)。
然而,列表作为队列的效率很低。因为,在列表末尾添加和删除元素非常快,但在列表开头插入或移除元素却很慢(因为所有其他元素都必须移动一位)。
使用队列可以用deque:https://docs.python.org/zh-cn/3/library/collections.html#collections.deque
列表转置:
matrix = [ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12],]
print([[row[i] for row in matrix] for i in range(len(matrix) + 1)])
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
*号解包:
print(*[[1, 2], [5, 6], ])
# [1, 2] [5, 6]print(*[1, 2]) # 1 2
直接按行读取
with open('1.txt', 'r') as fp:
for line in fp:
print(line, end='')
# list(f) 或 f.readlines() 也可以进行相同的操作
JSON 的本地写入与读取
with open('1.txt', 'r+', encoding='utf-8') as fp:
# 将x写入到本地文件中
x = [1, 'simple', 'list']
json.dump(x, fp)
# 将文件中的json数据读取到b中
b = json.load(fp)
print(b)
生成器表达式:(使用括号的是生成器表达式,中括号的是列表推导式)
# 下面函数相当于上面生成器的 reverse 函数,但是更加简洁,相对不灵活一些。
# 但是相对于列表推导式更加节省内存,因为他并不是一次性生成所有内容。
reverse = (s for s in 'global')
for s in reverse:
print(s)
虚拟环境:
# 创建 python3 -m venv tutorial-env
# 在windows上运行 tutorial-env\Scripts\activate.bat
# 在unix上运行 source tutorial-env/bin/activate
Python 语法糖记录
if else 简写:
a = 1
b = 2
# 这里通过列表的索引方式写出一个简洁版的if else
# 当后面表达式为真,True》代表的就是
# 1:取到的就是b的值# 当后面表达式为假,False》代表的是0,取到的就是a的值
# 相当于直接用0和1进行索引,来达到if else的简写
# 复杂写法:b if a>b else a
# 但下面这种写法不好理解。装逼之用c = [a, b][a > b]
海象运算符:海象运算符本质上就是一个“赋值表达式”,一边完成赋值工作,一边返回刚刚赋值完的变量。
# 因为上面的解释,所以下面的比较是#
# 先将string的长度给n,然后用n和5进行比较
# 因为海象运算符的优先级比 比较运算符低,所以需要加上括号
# 否则n的值将变为比较的bool值
if (n := len('string')) > 5:
print(n)
# 循环读取文件并且复制,然后判断是否为空
while (block := f.read(256)) != '':
process(block)
迭代解包:前面和后面获取一个值,中间对象获取任意长度的值
(a, *rest, b) = range(6)# (0, 1, 2, 3, 4, 5)
yield from:
# [1, 2, 3, 4]# 正常的yield语法使用需要使用循环,yield出去每一个元素的。
for a in x:
yield a
# 新语法可以用 yield from,
# 去除循环体,语法更加简洁。
# [1, 2, 3, 4]
# yield from x
3.9 字典更新
# 3.9语法
x = {"key1": "value1 from x", "key2": "value2 from x"}
y = {"key2": "value2 from y", "key3": "value3 from y"}
# 以x为准,将y的数据覆盖到x上。
print(x | y) # {'key1': 'value1 from x', 'key2': 'value2 from y', 'key3': 'value3 from y'}
# 也可以通过解包语句,以后面那个为准。
print({**x,**y})
#{'key1': 'value1 from x', 'key2': 'value2 from y', 'key3': 'value3 from y'}
3.10 语法:
# 3.10语法
with (
open('1.txt') as fp1,
open('2.txt') as fp2,
open('3.txt') as fp3,
):
# 多条上下文括号连写
单例类:
当一个类型你希望全局只有一个实例的时候,可以使用单例类
- 通过 hasattr 判断类是否有指定属性,这里使用的是 _instance 属性
- 如果没有这个属性,说明类没有被实例化,通过 object 进行实例化(所有类默认继承自 object)
- 定义 _instance 属性,然后返回,下次判断不成立,直接返回属性
- 在异步操作的时候可能会出错,这里加个锁,虽然会影响效率,但保证了数据的安全
import threading
class Singleton:
_Lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if not hasattr(Singleton, '_instance'):
with Singleton._Lock:
Singleton._instance = object.__new__(cls)
return Singleton._instance