现代程设复习

Python基础

关键字

python中有些关键词是不可以使用的,比如else,elif等,这些关键词可以通过下列方式查询

import keyword
print(keyword.kwlist)

None和其他任何数据类型比较永远会返回False

nonlocal可以在嵌套函数的时候使用,表示外层变量。

代码格式

多语句

可以使用 ; 在同一行显示多条语句

可以使用\来实现多行语句,在[],{},()中的多行语句不需要使用\

print(
    "dafaggakdh"
    "adkshafui"
    "asduihfaui"
)
str="dafsasg\
adagfiusa\
sadgf"

lis=[
    1,3,4,5,
    6,7,8,
    132,3
]

标识符与变量

所有标识符不能以数字开头

单下划线开头表示不能直接访问的类属性

双下划线开头代表类的私有成员

以双下划线开头和结尾表示python里的特殊方法专用标识

del 语句删除变量对象的引用,除非再次赋值

v=100
del v
try:
    print(v)
except NameError:
    print("value v is not defined")
v=200
print(v)

获取变量id id(var)

数据类型划分

在Python中,根据数据变化前后,指向的内存地址是否一致,可将数据分为:可变数据类型与不可变数据类型。

-不可变数据:number,string,tuple

-可变数据:list、dict、set

**类型判断函数:**isinstance(a,int)

def test(x,y):
    print(id(x))
    x.append(2)
    print(id(x))
    print(id(y))
    y=4
    print(id(y))
    return x,y
a=[1,2,3,4,5]
print(id(a))
b=1
print(id(b))
a,b=test(a,b)
print(id(a))
print(id(b))

1569677052992

1569664493872

1569677052992
1569677052992
1569664493872
1569664493968
1569677052992
1569664493968

在引用可变数据时,引用了地址,直接对地址指向的值进行改变,因此可变数据在函数中改变以后id不变,类似于C的指针调用。

在引用不可变数据时,传参传入了一个指向不可变数据地址的内存空间,由于不可变,当参数改变以后,会新开辟一个内存空间存放参数,指向该内存空间,而不改变原有实参内的数据。

两个的区别就是,一个直接改变内存空间内的数据;一个指向另一个内存空间//或者说前者在备份的空间里存放的是地址,后者在备份空间里存放的是具体值,前者通过地址访问内存修改数据,后者直接在空间里修改数据,前者地址不变,后者数值改变。

内存分配:对于简单的对象,会采取重用对象内存的方法来提高内存利用效率

r使反斜杠不发生转义

print(r"\n")

数据类型

列表逆序操作

a[-1::-1]

对字典进行排序

cut_fre
items = list(cut_fre.items()) # 由于字典类型没有顺序,将其转化为有顺序的列表类型
items.sort(key=lambda x: x[1], reverse=True) # 按照每个列表元素的第二个数,即value进行排序,且为降序

对列表删除

a->list
a.clear()==del a[:]

合并列表

list1=[i for i in range(10)]
list2=[i for i in range(10,100)]
import heapq
d=list(heapq.merge(list1,list2))         heapq.merge(list1,list2)是一个生成器
m=[]

for n in d:#按序插入
    heapq.heapify(m,n)
    
heapq.heaqify(d)

组合多个字典使其在逻辑上表现为一个整体,键查找时返回第一次出现的目标键,对该整体修改会影响原始的字典你。(创建一个单一的可更新的视图)

d1={'path':'/c/d/e','cmd':'clear','pwd':'$'}
d2={'root':'/','cmd':'cls','pwd':'#','prompt':'True'}
d=collections.ChainMap(d1,d2)
print(d['path'])
print(d['cmd'])
print(d['root'])
d['cmd']='update'
print(d1['cmd'])
print(d2['cmd'])

对列表进行计数统计,可自动更新计数

req=collections.Counter(['a', 'b', 'c', 'a', 'a', 'b'])
freq.update('abca')
for l in 'abc':
	print("{}:{}".format(l,freq[l]))

对字典进行默认值处理

def default_value():
   return -1
dc = collections.defaultdict(default_value, zjc='38')#默认值为-1,后面为赋值,需要用可调用对象来进行赋值,可以是None或者是一个函数
print(dc['zjc'])
print(dc['lsy'])
dc['lsy']+=1
print(dc['lsy'])

控制流

函数

不定长参数:*args会以tuple的形式插入参数,存放所有未命名的参数变量,**加入参数以字典形式导入

关键字参数

默认参数

[python函数默认参数为可变对象的理解 - 卡路西 - 博客园 (cnblogs.com)](https://www.cnblogs.com/gsx-600r/p/9883114.html#:~:text=python函数默认参数为可变对象的理解,1.代码在执行的过程中,遇到函数定义,初始化函数生成存储函数名,默认参数初识值,函数地址的函数对象。 2.代码执行不在初始化函数,而是直接执行函数体。)

函数在第一次调用的时候被初始化,第二次,第三次调用,如果没有对默认参数重新赋值,仍指向原存储空间,记住函数里的可变参数就是一个指向存储地址的指针。为了避免多次调用的参数变化,应尽量避免使用可变对象。

默认值只被赋值一次,这使得当默认值是可变对象时会有所不同,比如列表、字典或大多数类的实例,也即默认值在后续调用中会累积

当定义一个有默认值参数的函数时,有默认值的参数必须位于所有没默认值参数的后面

即: 将 L 默认为 None, 当该参数未传时, 将 L 设置为空列表, 在Python启动时 L 会设置为 None, 而当我们调用而不传参数 L 时, 在函数 f 内部重新给 L 赋值, 此时 L 为函数 f 内的一个私有变量, 其不会影响到参数 L 本身,

def append_ele(a,l=None):#此时对于l来说没有任何类型,在函数内初始化即可

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

匿名函数

不再使用def语句标准定义

函数名=lambda 参数1,参数2,…,参数n:expression

函数调用同一般函数

闭包✨✨✨

在一个外函数中定义了一个内函数,内函数里运用了外函数的变量,并且外函数的返回值是对内函数的引用

声明nonlocal和global我觉得原理是差不多的,声明这个是为了防止在修改数值时,产生歧义

闭包变量是一种累计变量,除非对于外层函数重新做了初始化

def outer(x):
   def inner(y):
      nonlocal x
      x+=y
      return x
   return inner

f1=outer(10)
print(f1(1))  11
print(f1(2))  13
print(f1(3))   16

print(outer(10)(1))  11
print(outer(10)(2))   12

模块

一个文件能以.py结尾,包含了对象的定义,模块能定义函数,类和变量,模块里也可能包含可执行的代码

模块的名字可以由全局变量 __ name __得到

模块在解释器中只导入一次

dir()函数返回当前模块定义的命名

用来保存不同模块

目录中需要有__ init __.py文件,一般空白即可,可以执行包的初始化代码,只有有这个文件python才将目录当做内容包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zrhrhsws-1678455789906)(C:\Users\kerrla\AppData\Roaming\Typora\typora-user-images\image-20221129172317348.png)]

引用A.B表示引用了名为A包的名为B的子模块

命名空间

内置名称,全局名称,局部名称

查找顺序:局部的命名空间–>全局命名空间——>内置命名空间

命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束

python中只有模块,类,函数才会引用新的作用域,其他的代码块不会引入新的作用域

输入输出

format函数

#位置
print("a1={} a2={} a3={}".format('first','second','third'))
print("a4={1} a5={0} a6={2}".format('first','second','third'))
#关键字参数
print("your name is {name}, age is {age}".format(age=87,name='jack'))
#对象属性
class p:
    def __init__(self):
        self.name='jack';self.age=87

print("your name is {p.name}, age is {p.age}".format(p=p()))

dt={'k1':1,"k2":2,'k3':3}
print('k1:{0[k1]};k2:{0[k2]};k3:{0[k3]}'.format(dt))
print('k1:{k1};k2:{k2};k3:{k3}'.format(**dt))

#格式化输出
#对齐
print('{0:10}==>{1:10d}'.format(name,number))  表示宽,类型
#浮点小数输出
print('常量PI的近似值为{0:.3f}'.format(math.pi))  表示保留几位小数

Python format 格式化函数 | 菜鸟教程 (runoob.com)

格式化输出

b
d
o
x
!s  转化为字符串
!a  转化为ascii
!r  转化为repr

序列化与反序列化

import pickle
with open(path,"wb") as f:
	pickle.dump(object,f)
ob=pickle.load(f)
#以上是转换文件的方式
#以下是转为字符串的方式
import pickle
list=[1,2,3,4,5]
l=pickle.dumps(list)
print(l)
ll=pickle.loads(l)
print(ll)

json

os模块✨✨✨

#路径构建
os.path.join(tuple)
#路径规范
os.path.normpath(str)
#文件信息
os.path.getszie(path)  获取文件大小
os.path.getatime(path) #文件最近访问
os,path.getmtime(path) #属性修改时间
os.path.getctime(path) #内容修改时间
time.ctime()#规定一种打印格式
#文件检测
a=os.path.dirname(__file__)#获取文件路径
os.chdir(a)#将该目录设置为工作目录
os.path.exists()
os.path.isfile()#判断是否为文件
os.path.isdir()#判断是否为目录

通过对os.walk(path)打印可以得出,打印出了文件夹中所有的目录,文件

按层读取

以下面的例子为例,root表示根目录,dir表示文件夹名,names表示根目录root下的文件

按层读取,然后进入子文件夹,返回新一轮的root,dir,names

#访问一个文件夹下的所有文件,并保存在一个列表内
import os
lis=[]
path="D:\经管大三\现代程序设计\week4"
if not os.path.exists(path):
	print("could not find")
for root, dirs, names in os.walk(path):
	for filename in names:
		lis.append(os.path.join(root, filename))
print(list(os.walk(path)))
print("done")

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vZJPUj27-1678455789907)(C:\Users\kerrla\AppData\Roaming\Typora\typora-user-images\image-20221129212537881.png)]

返回当前文件路径下的文件,且根据指定格式匹配,但是不能访问文件的子文件夹内的文件

def list_f(path='.',ext='*.py'):
	p=pathlib.Path(path)
	for f in p.iterdir():
		print(f)
	for f in p.glob(ext):
		print(f)

面向对象

类变量是类的属性而不是对象的属性,感觉类似于函数,第一次调用的时候,类初始化一次,之后都不在初始化,也就是类内变量产生一个映射,如果对值进行修改则改变类变量的字典映射,其他所有的实例化对象都使用同一个类变量。

之前提到,类的命名空间在声明的时候就创建了。也就是说,对一个类,只会执行一次初始化,而对象每创建一次,就要初始化一次。

(28条消息) python中的类变量_feng98ren的博客-CSDN博客_python 类变量

可以看到,Bar中的y被初始化了一次,而Foo中的y在每次生成新的对象时都要被初始化一次。

class A:
	name="twx"
	def __init__(self):
		print(A.name)

a=A()
A.name="mm"
b=A()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UzKzUSd8-1678455789908)(C:\Users\kerrla\AppData\Roaming\Typora\typora-user-images\image-20221221150429426.png)]

说明实例共用类属性,且类只初始化一次,改变类值,即映射改变

自定义类

类的方法

-对于类对象来说是函数

-对于实例对象来说是方法

class T:

​ def f(self):

​ pass

type(T.f)-function

t=T()

type(t.f)-method

构造方法

__new__() __new__() 是一种负责创建类实例的静态方法,它无需使用 staticmethod 装饰器修饰,且该方法会优先 __init__() 初始化方法被调用。
__init__() 初始化方法,在类实例化中会自动调用
__dict__()可以查看各类属性和方法
_属性名,表示私有属性,不能通过实例直接访问和修改
__属性名,对外会修改属性名称
__方法也可以私有,该方法只能在类内调用

设计模式原则

  1. 开闭原则:对扩展开放,对修改关闭(我认为这就是继承的意义)
  2. 里氏代换原则:任何基类可以出现的地方,派生类一定可以出现
  3. 依赖倒转原则:针对接口编程,以来抽象而不依赖具体
  4. 接口隔离原则
  5. 最小知道原则:实体间应尽量少地与其他实体发生作用
  6. 合成复用原则:尽量使用合成/聚合的方式,而不是使用继承

单例模式✨✨✨

全局只有一个实例

python魔法方法,详解__new__()和__init__() - 知乎 (zhihu.com)

class Singleton:
	_instance=None
	def __init__(self, name, volume):
		self.name=name
		self.volume=volume

	def __new__(cls,name,volume):
		if not Singleton._instance:#创建一个实例
		#if not hasattr(Singleton,'_instance'):
			Singleton._instance=object.__new__(cls)#第一次生成一个实例需要继承父类的new方法
			Singleton.__init__(Singleton._instance,name,volume)
		return Singleton._instance#返回实例,始终是对同一个实例进行初始化

slist=[Singleton('z',i) for i in range(10)]
for s in slist:
	print(hex(id(s)),end='\t')
	print(f"{s.name}\t{s.volume}")#所以答应出的volume都是9

继承

继承类可以使用模块指定调用某些基类

派生类(继承类):如果在类中找不到请求调用的属性会搜索基类;如果基类由别的类派生而来,则会递归式搜索。有例子,就是先画图,然后索引,从左向右。如果在子类中没找到对应方法,用前述方法进行操作。且调用的方式是一层层调用,如A\B继承object,则A\B遍历完,再object,即A\B\object

class.__ mro __可以得到类的继承顺序。既然是递归,那么运行方法应该是堆栈的形式,也就是说object运行完后,运行B。或者调用class.mro()

继承方法

多继承需要慎用

1.将多继承的父类分别继承

		People.__init__(self,name,height,age)
		Speaker.__init__(self,topic)

2.super().__ init __()

class People:
    def __init__(self,name):
        self.name = name
    def say(self):
        print("我是人,名字为:",self.name)
class Animal:
    def __init__(self,food):
        self.food = food
        
    def display(self):
        print("我是动物,我吃",self.food)
class Person(People, Animal):
    #自定义构造方法
    def __init__(self,name,food):
        #调用 People 类的构造方法
        super().__init__(name)
        #super(Person,self).__init__(name) #执行效果和上一行相同
        #People.__init__(self,name)#使用未绑定方法调用 People 类构造方法
        #调用其它父类的构造方法,需手动给 self 传值
        Animal.__init__(self,food)    
per = Person("zhangsan","熟食")
per.say()
per.display()

继承的检查

isinstance(obj,ont) 用于检查实例类型,当obj是int或者是从int继承的类型时返回True

issubclass() 用于检查类继承

__ new __() 是一种负责创建类实例的静态方法

python中cls代表的是类的本身,相对应的self则是类的一个实例对象

通过观察print输出结果,不难看出self是经过实例化并分配了内存,cls即为类本身。

也就是说每一次调用,先做__ new __ ()函数,且对于类的修改是静态保存的, __ new __()开辟了一个内存空间。

如果__new__方法返回None,则__init__方法不会被执行,并且返回值只能调用父类中的__new__方法,而不能调用毫无关系的类的__new__方法

class demoClass:
    instances_created = 0
    def __new__(cls,*args,**kwargs):
        print("__new__():",cls,args,kwargs)
        #我们在覆写 __new__() 的实现之前,都会先使用 super 调用父类的 new 方法。
        instance = super().__new__(cls)#返回一个实例
        instance.number = cls.instances_created
        cls.instances_created += 1
        return instance
    def __init__(self,attribute):
        print("__init__():",self,attribute)
        self.attribute = attribute
test1 = demoClass("abc")
test2 = demoClass("xyz")
print(test1.number,test1.instances_created)
print(test2.number,test2.instances_created)
image-20221130205344986
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print('self:', self)

    # 定义一个build方法,返回一个person实例对象,这个方法等价于Person()。
    @classmethod
    def build(cls):
        # cls()等于Person()
        p = cls("Tom", 18)
        print('cls:', cls)
        return p


if __name__ == '__main__':
    person = Person.build()
    print(person, person.name, person.age)

类的专有方法

[(18条消息) Python 运算符重载中__add__(self,other)_开始King的博客-CSDN博客](https://blog.csdn.net/m0_52118763/article/details/115604614#:~:text=python中的def add (self%2C other)%3A就是对加法(%2B)重载,如下代码 def add(self%2C other)%3A,%23重载%2B号 r%3DVecter() r.x%3Dself.x%2Bother.x r.y%3Dself.y%2Bother.y r.z%3Dself.z%2Bother.z return r 1)

D:\经管大三\现代程序设计\week6\week6\point.py

__new__() 是一种负责创建类实例的静态方法,它无需使用 staticmethod 装饰器修饰,且该方法会优先 __init__() 初始化方法被调用。
__ add __(self,other)给类提供增加属性的方法
__del__析构函数,释放对象时使用
__repr__ 返回对象的字符串表达式,用于返回属性
__setitem__ 按照索引赋值
__getitem__ 按照索引获取值
__len__ 获得长度
__call__ 实例对象可调用,可调用对象,可以有参数
__call__()。该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。也就是说使得实例可以像调用普通函数一样调动。
__add__:加运算
__sub__:减运算
__mul__:乘运算
__truediv__:整数除运算
__floordiv__:浮点除运算
__mod__:求余算法
__pow__:乘方
__str__:提供一个不正式的字符串表示,使得对象可用print表示    
__or__:运算符|
__bool__:布尔测试
__lt__:<
#以下是迭代的算法    
__iter__:
__next__:
    

派生类的实例化

-搜索对应的类属性,必要时沿基类链逐级搜索 递归搜索

-派生类可能会覆盖其基类的方法(多态的一种体现)

工厂模式✨✨✨

(19条消息) 工厂模式的作用,为什么要用工厂模式?_ 蜂蜜柚子茶的博客-CSDN博客_工厂模式的作用

解耦:把对象的创建和使用分开,两个类,一个负责创建,一个负责使用

防止多个参数传入类的重复操作复杂

实例化构建过程可能十分复杂,需要封装

工厂管理创建逻辑而不是使用者

即一堆创建的产品(类),工厂类去生产这些类。使用者只需要通过工厂类去调用即可

告诉工厂需求,返回一个实例对象

'''
除下实现方式外,还对以对工厂进一步进行抽象,得到抽象工厂,使得进一步解耦,
不再通过参数来得到要生产的类的对象。
'''
class Fruit:
	pass

class Apple(Fruit):
	pass
	def pie(self):
		print("making apple pie")

class Orange(Fruit):
	pass

	def juice(self):
		print("making orange juice")

class FruitFactory:

	def generate_fruit(self,type):
		if type=='a':
			return Apple() #可以根据apple的定义进行初始化
		elif type=='o':
			return Orange()#可以根据orange的定义进行初始化
		else:
			return None

ff=FruitFactory()
apple=ff.generate_fruit('a')
orange=ff.generate_fruit('o')
apple.pie()
orange.juice()

#“抽象”工厂的简要实现

class Factory:

	def generate(self):
		pass

class AppleFactory(Factory):

	def generate(self):
		return Apple()#

class OrangeFactory(Factory):
	
	def generate(self):
		return Orange()

异常处理✨✨✨

try:

​ block

except 异常:

​ block

–except(RuntimeError,TypeError,NameError) 里面可以插入多种报错信息

自定义异常:
首先需要继承类Exception或者继承类即Exception的子类,如ValueError等

raise手动抛出一个异常

except 异常 as e: 表示获得一个异常指令,同时将该异常实例化,便于之后的信息读取

(19条消息) python中异常处理 try except finally else 执行顺序详解_拓宽视野的博客-CSDN博客

class Error(Exception):
	def __init__(self,message):
		self.message = message
		print("raise Error")

a=input("number?")
try:
	for i in range(len(a)):
		if a[i]<'0' or a[i]>'9':
			raise Error(f"{a} is not a number")#生成一个报错实例
	print(a)
except Error as e:#可以是类也可以是实例
	print(e.message)

如果在except中单独加入一个raise,那么会打印这个错误信息,然后重新抛出具体的异常信息,使得可以trace错误信息

import sys
try:
	a=int(input("请输入一个数字"))
except OSError as e:
	print("OS error:{0}".format(e))
except BaseException:
	print("unexpected error:",sys.exc_info()[0])
	raise

在没有抛出异常之后可以执行其他的一些代码else

定义清理行为

finally实现

  • 无论try子句有无异常,finally子句都会运行,即使try return
  • 定义了无论在任何情况都会执行的清理行为

因此由于finally的功能,可以用来清除,资源释放工作,finally之后的部分不打印

finally 子句中应只做打印错误信息或者关闭资源等操作

避免在finally语句块中再次抛出异常

finally中最好不要返回值,否则对于一些错误信息,不会报错

(19条消息) python中的finally用法_gyniu的博客-CSDN博客_python finally

预定义清理行为

在类定义中的标准清理行为

  • __ enter __()方法:进入时调用,注意要有返回值
  • __ exit __():离开时调用
class DBError(Exception):
	
	def __init__(self,message):
		self.message=message

class DBhelper():
	
	def __init__(self,server_address,server_port,user,pwd):
		self._server_address=server_address
		self._server_port=server_port
		self._user=user
		self._pwd=pwd
		self._conn=None
	
	def get_server_address(self):
		return self._server_address

	def get_server_port(self):
		return self._server_port

	def __enter__(self):
		print("in __enter__: will try to connect the db server")
		return self

	def connect(self):
		try:
			print("in connect: connecting to the server")
			self._conn='success'#simulate the connection
			raise DBError('connect refused by the server')
		except DBError as dbe:
			print("in except: dbe.message")

	def close(self):
		print('in close: the connection is closed')
		#self._conn.close()
		pass

	def __exit__(self,type,value,trace):
		print("in __exit__: will close all the resource occupied")
		self.close()

with  DBhelper('127.0.0.1',3306,'zjc','123') as db:
	db.connect()
	print("connecting")#说明是在程序结束时,退出的时候执行的指令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nOjqv6bR-1678455789909)(C:\Users\kerrla\AppData\Roaming\Typora\typora-user-images\image-20221201144501684.png)]

with … as: 方法 预定义清理行为

断言

下面两个方法等价

if not 1==2:
        raise AssertionError("1 等于 2")

assert 1==2,"1 等于 2"

代理模式✨✨✨

代理模式中,我们作为访问者就相当于编程语言中调用的实体,目标主机就相当于被调用的实体,它们二者直接不产生直接联系,而是通过中间的代理服务器(代理实体)来实现二者的间接联系。提供一个接口(中介商),去对对象进行访问

代理模式能够应用的场景:

  1. 访问某个对象之前执行一个或者多个重要的额外操作
  2. 访问敏感信息或关键功能前需要具备足够的权限
  3. 将计算成本较高的对象的创建过程延迟到用户首次真正使用时才进行
    • 惰性求值

常见类型:

-远程代理:如网络服务器的对象在本地的代理者

-虚拟代理:用于惰性求值,将一个大计算量对象的创建延迟到真正需要的时候进行

-保护/防护代理:控制对敏感对象的访问

-智能代理:在对象被访问时执行额外的动作,如技术或线程安全检查

工厂模式(创建对象)vs 代理模式(访问对象)

这两个的区别还是很大的,Spring的两大核心 IOC 和 AOP就是这两个的典型代表,IOC 是工厂模式AOP是代理模式。
工厂模式意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。主要解决:主要解决接口选择的问题。工厂模式实际是对OOP多态的实现,核心会有一个抽象父类, 多个实现子类, 一个工厂类。其中工厂类可以根据需求生产不同实现的子类。返回实例对象
代理模式意图:为其他对象提供一种代理以控制对这个对象的访问。主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。代理模式核心原理是定义一个代理类, 对于外部通过代理类来访问被代理类。通过代理类我们可以对被代理进行功能加强,监控等。返回操作结果

作者:zane zhou
链接:https://www.zhihu.com/question/394793405/answer/1222633850
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

【进阶Python】第八讲:代理模式 - 知乎 (zhihu.com)

对象、报错类用于控制,代理接口用于访问操作,创建函数去访问。如登录操作,数据库数据修改等操作

装饰器✨✨✨

函数式编程
  • list(map(f,[x1,x2,x3,x4]))=[f(x1),f(x2),f(x3),f(x4)]
  • reduce(f,[x1,x2,x3,x4])=f(f(f(x1,x2),x3),x4) ) 或者有另一种用法 迭代方法
  • list(filter(f,[x1,x2,x3,x4]))=[x for x in [x1,x2,x3,x4] if f(x)]
  • 偏函数固定某参数来返回新函数

装饰器

property & wraps

-使实例方法的使用如同实例属性

-@property 读取属性

-@方法名.setter 修改属性

-@方法名.deleter 删除属性

在不修改原始代码的前提下增强或扩展既有功能。在核心功能的基础上增加额外的功能,如

授权、日志。

采用functools.wrapper专家们和设计器调用函数,在不改变函数名称的前提下,扩展函数的功能

@wraps复制了函数名称、注释文档、参数列表等,使得能够在装饰器里访问在装饰之前的函数属性

-在实现装饰器时应在wrapper函数前加入@wraps 避免因函数内部功能逻辑对函数属性的依赖而导致功能错误

def A(text):
    def dec(func):#输入的函数
        @wraps(func)
        def wrapper(*args,**kwargs):#表示被装饰的函数,函数参数
			print(text)
            return func(*args,**kwargs)
        return wrapper
    return dec

@A(text)#func==A(text)(func)
def func():
    pass
func()
from functools import wraps
from functools import reduce
from functools import partial
import time


def compose(x,y,f):
	return f(x)+f(y)

f=abs#直接调用函数而不是调用函数结果
print(compose(1,-2,f))

def f(x,y):
	return x*10+y

print(reduce(f,[4,3,2,1],2))# 迭代器函数 返回结果为24321

def cton(s):
	digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
	return digits[s]

print(reduce(f,map(cton,'1234')))#函数投影映射map

def odd(d):
	if d%2==0:
		return False
	else:
		return True

print(list(filter(odd,list(range(10)))))
print([x for x in range(10) if odd(x)])


def count():
    fs = []
    for i in range(1, 4):
        def f():
            return i
        fs.append(f)
    return fs
f1, f2, f3 = count()
print(f1())
print(f2())
print(f3())

def count():
    def f(j):
        def g():
            return j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
    return fs
f1,f2,f3=count()
print(f1())
print(f2())
print(f3())



def log(func):
	@wraps(func)
	def wrapper(*args,**kwargs):
		print("call "+func.__name__)#extra function
		return func(*args,**kwargs)
	return wrapper

@log
def now():
	print(time.strftime('%Y-%m-%d',time.localtime(time.time())))

now()
print(now.__name__)
#now=log(now), now() == log(now)()


######################################
def log(text):
	def decorator(func):
		@wraps(func)
		def wrapper(*args,**kwargs):
			print(text+' '+'call '+func.__name__)
			return func(*args,**kwargs)
		return wrapper
	return decorator

@log('日志:')
def now():
	print(time.strftime('%Y-%m-%d',time.localtime(time.time())))
print(now.__name__)
now() #now = log('日志:')(now),now() == log('日志')(now)()
############################################################

def growth(step=1,limit=200):
	g=0
	while(True):
		if g+step>limit:
			break
		g+=step
	return g

print(growth())
print(growth(step=3))

growth3=partial(growth,step=3)#functools.partial
print(growth3())
print(growth3(limit=300))

class Log:
	def __init__(self,logfile='out.log'):
		self.logfile=logfile
	def __call__(self,func):
		@wraps(func)
		def wrapper(*args,**kwargs):
			info="INFO: "+func.__name__+" was called"
			with open(self.logfile,'a') as file:
				file.write(info+'\n')
			return func(*args,**kwargs)#传入函数,返回函数对象
		return wrapper

@Log('test.log')
def myfunc():
	pass
myfunc()

class Student:
	
	def __init__(self,id):
		self._id=id

	@property
	def id(self):
		return self._id

	@id.setter
	def id(self,id_value):
		if(id_value<0):
			pass
		else:
			self._id=id_value

	@id.deleter
	def id(self):
		del self._id

s=Student(12)
print(s.id)
s.id=18
print(s.id)

class Employee:
	def __init__(self,name):
		self._name=name

	def _get_name(self):
		return self._name

	def _set_name(self,name):
		self._name=name

	def _del_name(self,name):
		del self._name
	
	Name=property(_get_name,_set_name,_del_name,'Name')

employee=Employee('lsy')
employee.Name='zhaojichang'
print(employee.Name)


class Finder:
	def __init__(self,path):
		self._path=path

	def get_path(self):
		return self._path

	@staticmethod
	def list_files(dir):
		print("{} contains files as listed:".format(dir))
		pass

	@classmethod
	def generate_finder(cls,root):
		print("class info: {}".format(cls))
		return cls(root)

print(type(Finder.list_files))
print(type(Finder.get_path))
print(type(Finder.generate_finder))

finder_1=Finder('info')
print(type(finder_1.list_files))
print(type(finder_1.get_path))
print(type(finder_1.generate_finder))

Finder.list_files('test')
finder_2=Finder.generate_finder('test')
print(finder_2.get_path())
finder_2.list_files('finder_2')


functools

  • lru_cache

    使函数具备最小缓存机制;调用相同的参数时会从缓存中直接调取出结果而不再经过函数运算

    @functools.lru_cache
    def func():
    
    func().cache_clear() 用来清除缓存
    func().cache_info() 用来打印缓存
    

装饰器顺序

装饰顺序:就近原则,从下往上装饰

调用顺序:就远原则,从上往下调用

类方法与静态方法

@classmethod:classmethod 修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。调用的时候直接使用 类.func(*arg,**argv)

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class A(object):
    bar = 1
    def func1(self):  
        print ('foo') 
    @classmethod
    def func2(cls):
        print ('func2')
        print (cls.bar)
        cls().func1()   # 调用 foo 方法
 
A.func2()               # 不需要实例化

[python3 @classmethod 和 @staticmethod 的区别 - 编程猎人 (programminghunter.com)](https://www.programminghunter.com/article/8736632046/#:~:text=python-staticmethod和classmethod classmethod 和 staticmethod 非常相似,但在用法上依然有一些明显的区别。,classmethod 必须有一个指向 类对象 的引用作为第一个参数,而 staticmethod 可以没有任何参数。)

@staticmethod:与普通函数类似

class A(object):
    def foo(self, x):
        print("executing foo(%s, %s)" % (self, x))

    @classmethod
    def class_foo(cls, x):
        print("executing class_foo(%s, %s)" % (cls, x))

    @staticmethod
    def static_foo(x):#无需传入一个实例对象self,是一个静态方法
        print("executing static_foo(%s)" % x)

A.static_foo(1)
A.class_foo(1)
装饰器类
class Log:
	def __init__(self,logfile='out.log'):
		self.logfile=logfile
	def __call__(self,func):#使得实例对象变得像函数一样可以被使用
		@wraps(func)
		def wrapper(*args,**kwargs):
			info="INFO: "+func.__name__+" was called"
			with open(self.logfile,'a') as file:
				file.write(info+'\n')
			return func(*args,**kwargs)#传入函数,返回函数对象
		return wrapper

@Log('test.log')
def myfunc():
	pass
myfunc()

pysnooper 能够打印出运行过程中各变量的值的变化

代码内存占用分析:

使用memory_profiler 里的 profile

from memory_profiler import profile
@profile
def my_func():
    a = [1] * (10 ** 6)
    b = [2] * (2 * 10 ** 7)
    del b
    return a
my_func()

通过命令行实现

  1. import time

    @profile

    import time
    @profile
    def test_time():
    	for i in range(100):
    		a=[1]*(10**4)
    		b=[2]*(10**4)
    		pass
    test_time()
    

    命令行调用 kernprof -lv path.py

  2. from line_profiler import LineProfiler

    from line_profiler import LineProfiler
    import random
    
    def do_other_stuff(numbers):
        s = sum(numbers)
    
    def do_stuff(numbers):
        do_other_stuff(numbers)
        l = [numbers[i]/43 for i in range(len(numbers))]
        m = ['hello'+str(numbers[i]) for i in range(len(numbers))]
    
    numbers = [random.randint(1,100) for i in range(1000)]
    lp = LineProfiler()#产生一个装饰器实例
    lp.add_function(do_other_stuff)   # add additional function to profile,把需要测量的函数加入
    lp_wrapper = lp(do_stuff)
    lp_wrapper(numbers)
    lp.print_stats()
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4fIj2cEo-1678455789910)(C:\Users\kerrla\AppData\Roaming\Typora\typora-user-images\image-20221201210204630.png)]

(19条消息) Python代码优化工具——line_profile_被Python玩的Kenny的博客-CSDN博客_line profile

观察者模式✨✨✨

self指的是实例,cls指的是类

对于观察者模式我们需要:被观察者(用来存储所有注册的观察者,并向所有观察者发送新消息),具体观察者(ConcreteObserver 实现这些接口,并与主题创建联系,主要通过类内初始化实现),观察者的接口(Obserber,里面定义了观察者获得通知的方法)

python设计模式之观察者模式 - sfencs - 博客园 (cnblogs.com)

对于简单的模型,也可以不用观察者的接口

一般被观察者具有的功能:将观察者加入列表,pop出列表,监听(条件)、通知(对于列表中的观察者进行遍历,对遍历的实例对象进行update操作–update操作在观察者类内有定义)

观察者的功能比较简单,主要是接受通知的update函数

如果要删除观察者实例可以使用remove函数,也可以使用del、pop函数

nums = [40, 36, 89, 2, 36, 100, 7]
#第一次删除36
nums.remove(36)

nums = [40, 36, 89, 2, 36, 100, 7]
nums.pop(3)

lang = ["Python", "C++", "Java", "PHP", "Ruby", "MATLAB"]
#使用正数索引
del lang[2]

生成器与迭代器✨✨✨

迭代:判断迭代 instance([1,2,3],iterable)

创建步骤:

迭代器

可以被next()函数调用并不断返回下一个值的对象称为迭代器

  • 迭代器可以记住遍历位置
  • 迭代器只能往前不能后退

生成器都是Iterator,但list、dict、str虽然是Iterable,却不是迭代器

把list、dict、str等变成Iterator可以使用iter()函数

Iterator对象表示一个数据流,被next不断调用返回,知道没有数据抛出时,抛出StopIteration错误

创建迭代器✨✨✨

  1. iter()函数

  2. 生成一个迭代器类,需要使用__ iter __ ()与 __ next __()方法

    前者返回一个特殊的迭代器对象,实现 __ next __()方法

    后者返回下一个元素,并通过StopIteration异常标识迭代完成

    class It:
        def __init__(self,start,end,step=1):
            self.start = start
            self.step = step
            self.end = end
    
        def __iter__(self):#生成一个迭代器的函数,返回self实例
            return self
    
        def __next__(self):#生成下一个元素的函数,注意需要通过raise StopIteration作为迭代器结束标志,但是这个异常不会打印
            x=self.start
            self.start+=self.step
            if x>self.end:
                raise StopIteration("finish")
            return x
    
    it=It(1,10,1)
    for item in it:#采用for语句调用或者采用next函数
        print(item)
    

    range(start,end,step)

    a=[1,2,3,4,5,6]
    for x in reversed(a):
    	pass
    	print(x)
    
    for x in reversed(range(1,7,2)):
    	pass
    	print(x)
    
    class Countdown:
    	def __init__(self,start):
    		self.start=start
    		self._n=self.start
    		
    
    	def __iter__(self):
    		
    		return self
    
    	def __next__(self):
    		if self._n>0:
    			x=self._n
    			self._n-=1
    			return x
    		else:
    			raise StopIteration('Countdown is over')
    
    	def __reversed__(self):
    		self._n=1
    		while self._n<=self.start:
    			yield self._n
    			self._n+=1
    
    cd=Countdown(10)
    
    for c in cd:
    	print(c)
    
    for c in reversed(cd):
    	print(c)
    

迭代器的相关工具

import itertools
import operator

l1=list(range(1,6))
l2=[True,False,True,False,True]
#compress(it,selector_it) 并行处理两个可迭代对象,如果selector_it中的元素为真,则返回it中对应位置的元素
for i in itertools.compress(l1,l2):
	pass
	#print(i)

def predictate(k):
	if k%2==0:
		return True
	else:
		return False

#takewhile(predicate,it) 不断使用当前元素作为参数调用函数并测试返回结果,如果返回值为真,则生成当前元素,循环继续;否则立即中断当前循环    
for i in itertools.takewhile(predictate,l1):
	pass
	#print(i)

#dropwhile(predicate,it) 处理it,跳过predicate计算结果为True后,不再进行下一步检查,输出‘剩下’的元素
for i in itertools.dropwhile(predictate,l1):
	pass
	#print(i)
#islice(it,stop) 作用类似于[:stop]
for i in itertools.islice(l1,2):#2的索引位置取不到
	pass
	#print(i)

#accumulate(it)  累计求和,注意是迭代器累计求和    
for i in itertools.accumulate(l1):
	pass
	#print(i)
    
#starmap(func,it) 把it中各个元素传给func,类似于map(func,*element)
l2=['a','b','c','d']
for s in itertools.starmap(operator.mul,enumerate(l2,1)):
	pass
	#print(s)

#chain(it1,..,itN) 返回一个生成器,依次产生it1,...里的元素    
for i in itertools.chain(l1,l2):
	pass
	#print(i)
    
#enumerate(iteration, start) 默认start为0,且标签在迭代结果的前面
#chain.from_iterable(it)  it是一个有可迭代对象组成的可迭代对象,将之拆分并依次返回
for i in itertools.chain.from_iterable(enumerate(l2,1)):
	pass
	#print(i)

#product(it1,it2,...,itN) 从输入的各个可迭代对象中获取元素,合并成有N个元素组成的元组,连接方式采用笛卡尔积    
l3=['e','f','g']
for i in itertools.product(l1,l2,l3):
	pass
	#print(i)
#zip(iterable1,...) 对应元素组合,元素个数与最短的列表一致  zip(*z)
for e in zip(l1,l2,l3):
	pass
	#print(e)

#zip(l1,l2)返回一个可迭代对象,print(*z)将结果打印完了,再次打印print(*z)无返回值,打印空行    
z=zip(l1,l2)
#print(*z)
#print(*z)

#拆分成两个迭代器结果打印
for i in zip(*z):
	pass
	#print(i)

#zip_longest(it1,it2,...,fillvalue='fill') 元素个数与最长的列表一致,连接以后自动填充    
for e in itertools.zip_longest(l1,l2,l3,fillvalue='0'):
	pass
	#print(e)
#combinations(it,out_len) 把it中out_len个元素的组合以元组的形式输出,不包含同一元素的组合,从iter中挑选数据
l4=[1,1,2,3]
for i in itertools.combinations(l4,3):
	pass
	#print(i)
#包含同一元素的组合 combinations_with_replacement(it,out_len)    
for i in itertools.combinations_with_replacement(l4,2):
	pass
	#print(i)
#for n in itertools.count(start=0,step=0.1):#不断产生数字,可以是浮点
	#pass
	#print(n)
#for s in itertools.cycle(l2):#按顺序重复输出it中的各个元素,it循环
#	pass
#	print(s)
for c in itertools.permutations(l4):#permutations(it,out_len=None) 生成长度为out_len的it元素的所有排列组合
	pass
	#print(c)
for n in itertools.repeat('a',5):#重复不断生成times个指定元素
	pass
	#print(n)
    
#groupby(it,key=None)  返回groupb(key,group),其中key是分组标准,group是生成器,用于产生分组中的元素,需对输入的可迭代对象使用分组标准进行排序,佛则输出会混乱
animals = ['duck','eagle','rat','giraffe','bear','bat','dolphin','shark','lion']
animals.sort(key=len,reverse=True)
for length,group in itertools.groupby(animals,len):#reversed(animals)
	pass
	print(length,':',list(group))
#tee(it,n=2)产生n个迭代器,每个迭代器都和输入的可迭代对象it一致  --copy
#g1,g2,g3=itertools.tee(l2,3)
#print(list(g1))
#print(list(g2))
#print(list(g3))

生成器: 直接生成列表可能收到内存大小的限制,其实就是每调用一次,进行一次创建。

  1. 通过列表推导式创建生成器

    列表:L=[x*x for x in range(10)]

    生成器:G=(x*x for x in range(10))

    用next,或者 for 遍历

  2. 通过定义函数构建生成器

​ 函数中定义包含yield关键字

​ 返回值需要捕获StopIteration异常

​ 普通函数调用直接返回结果,generator函数的调用实际返回一个generator对象。✨

def fib(max):
   n,a,b=0,0,1
   while(n<max):
      yield b
      a,b=b,a+b
      n+=1
   return 'done'

print(fib(6))#返回一个生成器
#print(len(fib(6)))

for f in fib(6):
   print(f)

f=fib(6)
while True:
   try:
      print(next(f))
   except StopIteration as si:
      print(si.value)
      break
from collections import Iterable

def flatten(items,ingore_types=(str,bytes)):
   for x in items:
      if isinstance(x,Iterable) and not isinstance(x,ingore_types):
         yield from flatten(x,ingore_types)#这一步需要等待调用完全,才能执行下一步
      else:
         yield x

items=[1,2,[3,4,5],[6,7],[[[8]]],9,10]
finput=list(flatten(items))
print(finput)
#for x in flatten(items):
#  print(x)

Python迭代器和生成器详解 - 知乎 (zhihu.com)

抽象类✨✨✨

type()函数:

​ 1、返回对象类型

​ 2、使用type()创建类,type(类名称, 继承的父类集合(tuple), 属性(数据或方 法)字典)

def say_hello(self):
	print(f"hi,I am {self.name}")

def __init__(self,name):
	self.name=name

People=type('Peo',(object,),dict(say_hello=say_hello,__init__=__init__))#返回一个类
p=People('zjc')
p.say_hello()
print(People.__name__)

元类:??

metaclass 控制类的创建行为

  • 先定义metaclass,然后创建类,最后创建实例
  • 类可以看成metaclass创建的“实例”

元类的定义

  • metaclass的类名总是以Metaclass结尾
  • metaclass是类的模板,所以必须从type类型派生

元类的使用

  • 通过关键字参数metaclass来指定元类创建类

元类用于动态定义,类扩展

def add(self,value):
	self.append(value)

class ListMetaClass(type):
	def __new__(cls,name,bases,attrs):
		#cls: metaclass name
		#name: 要构建的类的名称
		print(f'__new__:cls={cls}, name={name}, jicheng={bases}')#前者是元类的名称,后者是创建类的名称
		attrs['add']=add#将add方法加入到类中
		return type.__new__(cls,name,bases,attrs)

class Mylist(list,metaclass=ListMetaClass):#继承list类,并在list类中加入了一个方法add,用元类来表示,元类可以看做给类增加了方法,产生新的类
	pass

l=Mylist()
l.add(1)
l.add(2)
print(l)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mig1kNt0-1678455789910)(C:\Users\kerrla\AppData\Roaming\Typora\typora-user-images\image-20221206202446942.png)]

def __ new __ (cls,name,bases,attrs):#继承type类,type类方法创建类,通过打印发现,cls表示类本身,name表示创建的类名,bases表示继承的类(如上文的list),attrs表示调用的方法,在字典中。

type. __ new __ (cls,name,bases,attrs)等价于type(name,bases,attrs) 返回一个实例,由init接收构造

通常来说,新式类开始实例化时,new()方法会返回cls(cls指代当前类)的实例,然后该类的__init__()方法作为构造方法会接收这个实例(即self)作为自己的第一个参数,然后依次传入__new__()方法中接收的位置参数和命名参数。

抽象类:

abstract class 特殊的类,只能被继承,不能实例化;从不同类抽取相同的属性和行为

抽象类的实现:借助abc模块实现

子类必须实现抽象方法,语法上要求必须覆盖

抽象类是对各种类的总结,提取相同特点

Python 抽象类 - 知乎 (zhihu.com)

import abc

class Fruit(abc.ABC):
	@abc.abstractmethod##定义抽象方法,无需实现功能
	def harvest(self):
		pass

	@abc.abstractmethod
	def grow(self):
		pass

class Apple(Fruit):#必须实现抽象方法
	
	def harvest(self):
		print("大丰收")

	def grow(self):
		print("种苹果树")

	def juice(self):
		print("做苹果汁")

class Watermelon(Fruit):
	def harvest(self):
		super().harvest()#继承父类的该方法,抽象类方法可以做某些实现,而接口不做任何实现
		print("从地里摘")

	def grow(self):
		print("用种子种")

@Fruit.register#注册抽象类
class Orange:
	def harvest(self):
		print("从树上摘")
	
	def grow(self):
		print("种橘子树")

#f=Fruit()
a=Apple()
a.grow()
w=Watermelon()
w.harvest()#打印两句话

o=Orange()
o.grow()

print(isinstance(o,Fruit))
print(issubclass(Orange,Fruit))#True
print([sc.__name__ for sc in Fruit.__subclasses__()]) #no orange


注册抽象类和继承抽象类的区别在于,Fruit. __ subclasses __ ()不包括注册子类

**虚拟子类:**对于用户自定义的类型

接口

可以约定任何方法都只是一种规范,具体的功能需要子类实现

与抽象类的区别:

  • 抽象类中可以对一些抽象方法做出基础实现
  • 接口中所有方法只是规范,不做任何实现

使用原则:

  • 继承类应该尽量避免你多继承
  • 继承(实现)接口鼓励多继承

对于接口方法可以不全部实现,即不用抽象方法去定义类内方法

鸭子类型:一个对象有效的语义,不是由继承自特定的类或实现特定的接口看IE偶的美好的,而是由“当前方法和属性的集合决定的”

from collections.abc import Iterator
#该类虽然没有继承迭代器类,但是由于方法和属性与迭代器相同,其实例被认为是迭代器,类被认为是迭代器的子类
class IterOneDuck:

	def __init__(self,start=0):
		self._start=start

	def __iter__(self):
		return self

	def __next__(self):
		tmp=self._start
		self._start+=1
		return tmp


iduck=IterOneDuck()


'''for duck in iduck:
	print(duck)
	if duck>10:break'''

print(10 in iduck)
print(issubclass(IterOneDuck,Iterator))
print(isinstance(iduck,Iterator))

泛函数

-函数对不同的参数类型会提供不同的处理方式

-通过装饰器来实现

-类似于重载机制 重载,从简单说,就是函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者重载方法。

泛型,即“参数化类型”,

1 singledispatch : 标记处理函数传值类型

2 register(类型): 为传值判断类型后输出结果

3 后续使用无需写函数名, 只要有register(类型装饰器)即可调用

4 定义需要判断的类型int str tuple dict list set 根据自己需求

from functools import singledispatch
class Student:
	pass
@singledispatch#标记为泛函数(我们需要一次性定义这个方法名的泛型使用,而不希望重复调用)
def log(obj):
	print(obj)

@log.register(str)#函数对str类型的处理
def _(text):
	print(text.title())

@log.register(int)
def _(num):
	ns=[]
	while num:
		ns.append(num%10)
		num//=10
	for n in ns:
		print(n,end='')
	print()

@log.register(dict)
def _(d):
	print("{} kvs load...".format(len(d)))

@log.register(tuple)
@log.register(list)
def _(l):
	print('不会打印列表或者元组...')

@log.register(Student)
def _(s):
	print("额,学生类也打不了。。。")

log('china')
log(1234)
log([1,2,3,4])
log({'a':1,'b':2,'c':3})
log((1,))
log(Student())

适配器模式✨✨✨

将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配造成的兼容性问题。

需要目标类(Target)、适配器类(Adapter)、适配者类(Adaptee)

适用于无现成代码;利用既有组件可能成本更低;版本升级与兼容

适配器类一般由抽象类或者接口充当,也就是说一些方法可能不兼容,采用适配器转化成兼容的方式

import abc
class Computer:
	def __init__(self,name):
		self._name=name

	@property
	def name(self):
		return self._name

	@name.setter
	def name(self,value):
		self._name=value

	def __str__(self):
		return 'the {} computer'.format(self._name)

	def execute(self):
		print('execute a computer program')

class Human:
	def __init__(self,name):
		self._name=name
	def __str__(self):
		return '{} is an human'.format(self._name)
	def speak(self):
		print('hello word from human')

class Synthesizer:
	def __init__(self,name):
		self._name=name
	def __str__(self):
		return 'the {} synthesizer'.format(self._name)
	def play(self):
		print('play a synthesizer')

class Target(metaclass=abc.ABCMeta):
	@abc.abstractmethod
	def execute(self):
		pass

class HumanAdapter(Target):
	def __init__(self,human):
		self.human=human

	def execute(self):
		self.human.speak()

class SynthesizerAdapter(Target):
	def __init__(self,syn):
		self.syn=syn
	def execute(self):
		self.syn.play()

#另一种实现策略
class Adapter:
	def __init__(self,adp_obj,adp_methods):
		self.obj=adp_obj
		self.__dict__.update(adp_methods)

	def __str__(self):
		return str(self.obj)

def main():

	objects=[Computer('mac')]
	syn=Synthesizer('yamaha')
	h=Human('zjc')

	objects.append(HumanAdapter(h))
	objects.append(SynthesizerAdapter(syn))
	for obj in objects:
		obj.execute()


	objects2=[Computer('mac')]
	objects2.append(Adapter(syn,dict(execute=syn.play)))#重要
	objects2.append(Adapter(h,dict(execute=h.speak)))
	for obj in objects2:
		obj.execute()

if __name__=='__main__':main()

两种实现方法:

  1. 写一个抽象类抽象方法,然后通过重写,将不同的函数名调用放在一个规范的函数内
  2. 写一个转化器,传入一个实例,一个待规范的方法,更新该类的函数方法,self. __ dict __ .update(adp_methods)传入的是一个属性或者方法字典,可以通过该方法更新,即将创建一个规范方法与旧版本方法的对应

多进程与多线程

看笔记,注意画出各个进程、线程之间的关系

多进程

简单多进程实现

from multiprocessing import Process

p=Process(target=fun1,args=(A,))
p.start()#开始一个进程
p.join()#阻塞主进程,直到所有的子进程完成,主进程才继续

#使用多进程的方法,需要使用
if __name__ == '__main__':

p.is_alive() --如果p仍然运行,返回True

p.daemon 守护进程

将p设为守护进程,经过测试发现,守护进程打印了第一行字符串,然后由于主进程先结束了,于是子进程自动停止。如果不设置守护进程,默认守护进程为False,此时如果子进程没完成,会等子进程完成,程序结束。join只是控制顺序

from multiprocessing import Process
import time
import random
def task(name):
    print('%s is piaoing' %name)
    time.sleep(3)
    print('%s is piao end' %name)
if __name__ == '__main__':
    p=Process(target=task,args=('egon',))
    p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
    p.start()
    time.sleep(2)
    print('主') #只要终端打印出这一行内容,那么守护进程p也就跟着结束掉了

进程类:将任务写进进程类中,这样就不需要调用Process来生成,直接生成实例即可 。当然进程类需要继承Process类

from multiprocessing import Process
import time
class Task(Process):
    def __init__(self,name):
        super().__init__()
        self._name=name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self,value):
        self._name = value

    @name.deleter
    def name(self):
        del self._name

    def __pp(self):
        print("进入")

    def run(self):
        print("Running")
        time.sleep(10)
        self.__pp()
        self.name="test"
        print(self._name)

if __name__ == "__main__":
    t=Task("zjc")
    t.start()
    t.join()
    print('ending')

p.name 进程的名称

p.pid 进程的pid

互斥锁

from multiprocessing import Lock

lock=Lock()
lock.acquire()
lock.release()

信号量

from multiprocessing import Process, Semaphore
semaphore=Seaphore(5)#全局变量,在任务中加入
def ktv(i, sem):
    sem.acquire()
    print('%s 走进ktv' %i)
    time.sleep(random.randint(1, 5))
    print('%s 走出ktv' %i)
    sem.release()

Python 线程信号量 semaphore - Python零基础入门教程 - 简书 (jianshu.com)

获取一个信号量,说明启动一个进程,信号量减1,直到结束释放,信号量加1。注意生成进程的时候还没启动,启动需要start()

mps.py

事件✨

首先我们解析一下关于event的函数

event.is_set():返回event的状态值;

event.wait():如果 event.is_set()==False将阻塞进程,直到event状态值改变,进程继续;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False
from multiprocessing import Process
from multiprocessing import Event
import time
import random

def car(event,name):
	while True:
		if event.is_set():
			time.sleep(random.random())
			print("car {} passes...".format(name))
			break
		else:
			print("car {} waits...".format(name))
			event.wait()#阻塞直至事件状态发生变化

def light(event):
	while True:
		if event.is_set():
			event.clear()
			print("红灯")
			time.sleep(random.random())
		else:
			event.set()
			print("绿灯")
			time.sleep(random.random())

if __name__=='__main__':
	event=Event()
	event.clear()
	l=Process(target=light,args=(event,))
	l.daemon=True#设为守护进程,车辆通行完即结束进程
	l.start()
	cars=[]
	for i in range(10):
		c=Process(target=car,args=(event,'c_'+str(i+1)))
		cars.append(c)
	for c in cars:
		c.start()
	for c in cars:
		c.join()
	print("all cars passed...")

条件

进程间通信

1.管道✨✨ pipe([duplex]),跨机的管道通信使用的是socket

可以看到和tcp传递消息很相似,而管道和tcp都是留用socket套接字来传递消息,区别在于一个是网络套接字,一个是本地文件套接字。

一般用于两个而进程间的通信,单一可以用于多个进程,管道就像是一个队列。

管道的本质:在内存中开辟一个新的空间,对多个进程可见,在通信形式上形成一种约束。进程结束后,管道在内存中开辟的空间就会释放。

在进程间创建一条管道,并返回元组

(conn1,conn2),其中conn1,conn2表示管道两端的连接对象

-conn1:读,接收消息

-conn2:写,发送消息

必须在产生Process对象之前产生管道

duplex

  • 默认全双工,如果将duplex=False,对于一个进程只能通过conn1收,conn2发

1.向管道发送数据使用send函数,从管道接收数据使用recv()函数

2.recv()函数为阻塞函数,当管道中数据为空的时候会阻塞

3.一次recv()只能接收一次send()的内容

4.send可以发送的数据类型比较多样,字符串,数字,列表等等

函数(任务)内部关闭通道并不能有效进行,需要在主进程内关闭,且管道需要作为参数传入

conn1.recv() 接收对象,如果连接的另一端全部关闭,那么recv方法会抛出EOFError;conn2.send(object) 发送对象 object时域序列化相容的任意对象;conn1.close()关闭连接

2.队列✨✨

8.python多进程之队列 - 简书 (jianshu.com)

无非就是生产者-消费者模型,生产者将资源放在队列中,然后消费者从队列中获取资源

队列,当队列为空时,get方法会阻塞,直到队列中有可用方法,因此想要退出必须设置特殊指令;put方法,如果队列已满,此方法将阻塞至有空间可用为止–timeout可以解决等待时间过长问题。或者将消费者设置为守护进程也行。也就是说,我们并不需要担心队列空报错。因此此时队列就像一个通讯管道,实时监听着(堵塞)。

那么如何设置这个信号呢?在队列的最后插入None q.put(None) 注意不是字符串,然后让q.get()去判断队列是否为空,即 q.get() is None

from multiprocessing import Process, Queue

q=Queue(maxsize)

q.put()

q.get()

q.empty()

q.full()

q.qsize()

判断队列是否为空的条件为 q.get() is None

os.getpid() 返回当前进程的id

3.JoinableQueue() 允许通知数据成功处理

JoinableQueue除了与Queue相同的方法之外,还具有2个特有的方法

q.task_done() 消费者使用此方法发出信号,标识q.get()返回的数据已经被处理

q.join() 生产者任务中用此方法进行阻塞,直到队列中所有的项目均被处理 ------一旦队列中的元素没有被进程消耗光,就会阻塞主进程

内存共享:通过multiprocessing导入函数Value、Array作为共享的内存对象

​ 通过manager实现内存共享,此时不需要加锁来保护资源,通过manager代理其他进程对象进行操作,返回的管理器支持类型支持list、dict等

image-20221207193005499

进程池

-进程开启过多导致效率下降(同步、切换成本)

-应固定工作进程的数目

from multiprocessing import Process #进程
from multiprocessing import Pool #进程池
from multiprocessing import current_process #当前进程
  1. Pool()

    一部分主要的函数

    p=Pool()#cpu determines
    ms=range(1,20)
    results=[]
        
    for m in ms:
    	res=p.apply_async(job,args=(m,),callback=add_result)#回调函数callback,属于异步调用,当进程池中任意进程完成任务后会立即通知主进程,主进程会调用另一个函数去处理该结果,其参数为返回结果。主要方法,res保存回调函数返回值,不会马上处理完    
       
    #p.apply()是同步调用,立即返回结果 3.x后弃用
        result.append(res)
    for res in results:
        print(res.get())#等待回调函数的处理结果
    p.close()#关闭进程池
    p.join() #等待所有工作进程退出后调用,只能在close后面使用   
    
  2. ProcessPoolExecutor()

    import concurrent.futures
    

    使用submit或者map函数,将结果返回到n_future,未来调用结果

    submit返回结果的调用结果的方式为res.result()

    map调用结果的方式为返回一个结果迭代器,无需使用result()调用值

    
    def main_submit():
        results=[]
        with concurrent.futures.ProcessPoolExecutor() as executor:
            for number in PRIMES:
                n_future=executor.submit(is_prime,number)
                #print(n_future.result())#block
                results.append(n_future)
                #这里要注意,如果马上在主进程里获取结果,即n_future.result(),即主进程会阻塞,无法并行
                #因此建议先将n_future搁进list,等启动所有进程后再获取结果。
            for number, res in zip(PRIMES,results):
                print("Result: %d is prime: %s" % (number,res.result()))
                
    def main_map():
        with concurrent.futures.ProcessPoolExecutor() as executor:
            for number, prime_or_not in zip(PRIMES, executor.map(is_prime, PRIMES)):
                print('%d is prime: %s' % (number, prime_or_not))            
    
多线程

多进程主要用于计算密集型,多线程主要用于IO密集型(访问,读写等);多进程可以利用多核,多线程开销小,但是无法利用多核。

多线程编程

  • threading 模块在使用层面类似于multiprocessing

    threading.currentThread() 返回当前的线程实例,如果需要返回名称,则+ .name

    threading.enumerate() 返回所有正在运行的线程list

    threading.activeCount() 返回正在运行的线程数量

创建多线程

from threading import Thread,currentThread
import time
  • 通过target参数

    t=Thread(target=func,args=(a,))
    t.start()
    t.join()
    
  • 通过继承Thread类

    class Task(Thread):
        def __init__(self,name):
            super().__init__()
            self._name=name
        ##run函数覆写 start()运行该任务,这是一个主任务,是一个线程的发起点。
        def run(self):
            time.sleep(2)
            print('%s print name: %s' % (currentThread().name,self._name))
            
    t=Task('thread_task_class')#继承thread类方法
    t.start()
    t.join()        
    
  • 守护线程

    t.setDaemon(True)#设置了一个守护线程,应在start()前面
    
  • 线程池

    python线程池 ThreadPoolExecutor 的用法及实战 - 简书 (jianshu.com)

    #主要结构
    import concurrent.futures
    def download_many(count):
    	with concurrent.futures.ThreadPoolExecutor(max_workers=count) as executor:
    		todomap=[]
    		for x in range(100):
    			future=executor.submit(task,x)
    			todomap.append(future)
    		dointer=concurrent.futures.as_completed(todomap)
    		dointer=tqdm.tqdm(dointer,total=len(todomap))
    		for future in dointer:
    			res=future.result()
    

    as_completed()生成一个生成器(yield),当任务没有完成或没有返回值时,会一直阻塞主线程,直到任务完成,先返回的值在前面,不按任务顺序。

    map:批量映射,按顺序返回运行结果

  • 队列

    q=queue.Queue()
    q.task_done()
    q.join()
    
    from threading import Thread,currentThread
    import queue
    import random
    import time
    
    def worker():
        while True:
            item = q.get()
            if item is None:
                break
            time.sleep(random.randint(1,2))
            print("%s get %s" %(currentThread().name,item))
            q.task_done()
    
    num_worker_threads=10
    
    q = queue.Queue()
    threads = []
    for i in range(num_worker_threads):
        t = Thread(target=worker)
        threads.append(t)
        t.start()
        
    #因为在主线程中,只有所有元素都被put以后,才能进行下一步join
    for item in "abcdefghijklmnopqrstuvwxyz0123456789":
        q.put(item)
    
    # block until all tasks are done
    q.join()
    
    # stop workers
    for i in range(num_worker_threads):
        q.put(None)
    for t in threads:
        t.join()
    
  • lock=Threading.lock()
    
  • 信号量

    semaphore=threading.Semaphore(n)#在任务中使用
    def foo():
        semaphore.acquire()    #计数器获得锁
        time.sleep(2)   #程序休眠2秒
        print("当前时间:",time.ctime()) # 打印当前系统时间
        semaphore.release()    #计数器释放锁
    
  • 事件

    event=threading.Event()
    event.is_set()
    event.is_set():返回event的状态值;
    event.wait():如果 event.is_set()==False将阻塞,直到event状态值改变,继续;
    event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    event.clear():恢复event的状态值为False
  • 条件

    当小伙伴a在往火锅里面添加鱼丸,这个就是生产者行为;另外一个小伙伴b在吃掉鱼丸就是消费者行为。当火锅里面鱼丸达到一定数量加满后b才能吃,这就是一种条件判断了。

    就是一个线程获取条件,当满足某一条件的时候,通知其他线程

    wait()表示挂起,当收到通知时,挂起结束,继续运行

    notifyAll()通知所有线程

    下面例子,模仿的是加鱼丸,吃鱼丸的过程,循环过程

    # coding=utf-8
    import threading
    import time
    
    con = threading.Condition()
    
    num = 0
    
    # 生产者
    class Producer(threading.Thread):
    
        def __init__(self):
            threading.Thread.__init__(self)
    
        def run(self):
            # 锁定线程
            global num
            con.acquire()
            while True:
                print "开始添加!!!"
                num += 1
                print "火锅里面鱼丸个数:%s" % str(num)
                time.sleep(1)
                if num >= 5:
                    print "火锅里面里面鱼丸数量已经到达5个,无法添加了!"
                    # 唤醒等待的线程
                    con.notify()  # 唤醒小伙伴开吃啦
                    # 等待通知
                    con.wait()
            # 释放锁
            con.release()
    
    # 消费者
    class Consumers(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
    
        def run(self):
            con.acquire()
            global num
            while True:
                print "开始吃啦!!!"
                num -= 1
                print "火锅里面剩余鱼丸数量:%s" %str(num)
                time.sleep(2)
                if num <= 0:
                    print "锅底没货了,赶紧加鱼丸吧!"
                    con.notify()  # 唤醒其它线程
                    # 等待通知
                    con.wait()
            con.release()
    
    p = Producer()
    c = Consumers()
    p.start()
    c.start()
    
    

    D:\经管大三\现代程序设计\week12\week12\week12\mtc.py

    python笔记11-多线程之Condition(条件变量) - 上海-悠悠 - 博客园 (cnblogs.com)

  • 定时器

    threading.Timer(interval, function, args=[], kwargs={})#interval秒后生成该线程
    

网络编程

TCP socket编程

1024以下是常用端口,一般自定义的端口在1024以上

服务端使用的端口号再49152以上,是短暂端口

(23条消息) Python之TCP网络编程(Socket)_咕咕@的博客-CSDN博客_python网络编程

#分为服务端,客户端,注意端口号的格式是数字int
#当需要多个客户端时,使用多线程,每个线程管理一个客户端的通讯
#服务端
from socket import *
sever=socket(AF_INET,SOCK_STREAM)#生成一个socket用于描述ip和端口
sever.bind(ip,post)#将socket服务器的ip和端口绑定,此时这个ip和端口不能被其他socket绑定
server.setsocket(SOL_SOCKET,SO_REUSERADDR,1)#设置socket参数
sever.listen(n)#设置最大监听数,只要不关闭,就会阻塞主进程,常开
while True:
    clent_socket,address=sever.accept()#address是由ip,post构成的,指的是客户端的地址,client_socket是构建的服务器与客户端的一对一连接
     recv_data=client_socket.recv(1024)#利用专门负责客户端通信的socket进行,此时负责监听的socket套接字可以关闭
    client_socket.send("hello")
    client_socket.close()
sever.close()#如果需要的话

sever.accept()本身可以监听,收到一个连接请求即返回conn,adrr没问题,但是需要注意的是开辟一个线程去处理对应连接。注意是否会阻塞一个循环监听过程。如老师的例子中 tcpsever.py 之所以不能实现多个客户端连接,是因为处理单个客户端的函数不为一个线程,而在循环内,导致阻塞。

以下是对代码tcpsever.py修改后的结果,当然如何关闭服务器是需要小心考虑的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sYtTsWtD-1678455789912)(C:\Users\kerrla\AppData\Roaming\Typora\typora-user-images\image-20221208144844109.png)]

客户端
from socket import *
client=socket(AF_INET,SOCK_STREAM)
client.connect(ip,port)#输入的是服务器的ip和端口号,请求连接,一旦请求连接成功,服务端会给该客户端随机分配一个端口号
#发送数据,str表示的是发送的文本信息
client.send(str.endcode("utf-8"))
#接收信息,表示接收端一次最多接收1024个字节的信息,MTU为1500字节
recv_data=client.recv(1024)
#对数据进行解码
print("recv:", recv_data.decode("utf8"))
#结束连接
client.close()

可以写成sever类,client类,进行封装调用

注意传输使用的是二进制,因此接收的数据流需要data.decode(),发送的需要data.encode()

UDP socket编程

UDP的优势:

  • 应用层能更好地控制要发送的数据和发送时间
  • 无需连接
  • 无需任何准备即可进行数据传输
  • 不引入建立连接的时延
  • 无需连接状态
  • 无需维护一些状态参数
  • 分组头部开销小
    • TCP头至少占20个字节,UDP头占8个字节

TCP是建立可靠连接, 并且通信双方都可以以流的形式发送数据。 相对TCP, UDP则是面向无连接的协议。使用UDP协议时, 不需要建立连接, 只需要知道对方的IP地址和端口号, 就可以直接发数据包。 但是, 能不能到达就不知道了。

从socket格式就可以看出

TCP:socket(AF_INET,SOCK_STREAM)–stream 流 ,由于TCP的一对一连接,可靠性连接的流传输

UDP:socket(AF_INET,SOCK_DGRAM)–dgram 数据报,由于不是一对一连接,数据报方式发送

服务器


#server

import socket

#创建Socket时, SOCK_DGRAM 指定了这个Socket的类型是UDP。

server = socket.socket(type=socket.SOCK_DGRAM)

server.bind(('192.168.1.165',7890))

#不需要调用 listen() 方法, 而是直接接收来自任何客户端的数据,只需要在数据包后跟上ip与端口

print('服务端已开启7890端口,正在等待被连接...')

#recvfrom() 方法返回数据和客户端的地址与端口, 这样, 服务器收到数据后,

#直接调用 sendto() 就可以把数据用UDP发给客户端

data,address = server.recvfrom(1024)

print("client>>",data.decode('utf-8'))

print("客户端连接的socket地址:",  address)

server.sendto(b'drink more water!',address)

server.close()

客户端

import socket

#创建Socket时, SOCK_DGRAM 指定了这个Socket的类型是UDP。

client = socket.socket(type=socket.SOCK_DGRAM)

send_data  =b'hello sheenstar'

client.sendto(send_data,('192.168.1.165',7890))

re_Data,address = client.recvfrom(1024)

print('server>>',re_Data.decode('utf-8'))

client.close()

recvfrom()返回数据,元组(ip,port)

tcp------socket.recv(); socket.send()

udp-----socket.recvfrom(); socket.sendto()

udp用于传输视频比较好,对于可靠性没有这么高的要求。

其他网络编程

SMTP

SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。

【Python】SMTP发送邮件 - 知乎 (zhihu.com)

POP3

POP3是一种用于接收电子邮件的协议, 我使用的python3.7版本自带pop3操作库, 库的名字为poplib.

(23条消息) python使用POP3获取邮件信息_Hi_BelingBeling的博客-CSDN博客_python pop3用法得到原文

IMAP

互联网邮件访问协议

异步IO

python的异步IO就是由协程实现的,同步IO的硬盘读写操作会阻塞用户端的线程(CPU运行)

(23条消息) I/O操作为什么不需要cpu_hllyzms的博客-CSDN博客_io操作为什么不占用cpu

IO由硬盘进行读写,占用内存部分极其小,当IO操作完成以后,通知CPU,CPU继续接下来的操作。这也是协程为什么可以提高效率的原因,如果采用同步操作,则必须等硬盘IO操作(阻塞线程)结束,才能继续运行。

我们在学习的过程中,主要采取事件列表模型。

  • 主线程不断重复“读取请求-处理请求”过程
  • 进行IO操作时,相关代码只发出IO请求,不等待IO结果,直接结束本轮事件处理,进入下一轮事件处理
  • 当IO操作完成后,将收到IO完成消息,在处理该消息是时在获取IO操作结果
  • 在发出IO请求到收到IO完成消息期间,主线程并不阻塞,而是再循环中继续处理其他消息
  • 对于大多数IO密集型的应用程序,使用异步IO将大大提升系统的多任务处理能力

协程✨✨✨

协程又叫微线程,Coroutine。

在一个线程中会有很多函数,一般将这些函数称为子程序,而别的子程序执行过程中可以中断去执行别的子程序,别的也可中断继续执行之前的子程序。

表象上类似多线程,协程本质上只有一个线程在运行。

高并发+高扩展性+低成本

yield关键字实现协程

next不会返回yield表达式的结果

def gtest():
	print("let's begin")
	while True:
		url=yield 1
		print(url)

g=gtest()
# print(next(g))
# print(next(g))
# print(next(g))

g.send(None)#启动到yield行,第一次返回yield,此时print yield的返回值,而不是返回x的值
for i in range(10):
	g.send(i)
#send先返回yield,再传值给yield表达式
1 0 1 2 ...
def producer(conn):#conn为传入
    with open("D:\经管大三\现代程序设计\week14\music.csv","r",encoding="utf-8") as f:
        print("开始筛选")
        conn.send(None)
        reader = csv.reader(f)#相当于生成一个迭代,每次读是一个IO操作
        lis=next(reader)#先把第一行的名称部分删除,注意每行记录之间有空行
        while True:#我们希望先读取一个,然后进行解析判断,跳过读取部分
            try:
                lis=next(reader)#一个读的IO操作
                lis=next(reader)#真正读取了一个数据行,当读完最后一个空行,再执行时,引发报错
                if int(lis[4])>100000:#播放量大于100000,则将id传给consumer
                    conn.send(int(lis[1]))#将值传给迭代器运行
            except StopIteration:
                print("ending")
                conn.send("nothing")

#写是一个IO操作
def consumer():
    with open("./筛选的url.txt","a") as f:
        while True:
            print(f'开始写入')
            id=yield 1
            if isinstance(id,str):#说明已经筛选完了
                break
            url='https://music.163.com/' + str(id)
            f.write(url+'\n')


def main_1():
    conn=consumer()
    producer(conn)
gevent

(19条消息) Python 的 Gevent — 高性能的 Python 并发框架_擒贼先擒王的博客-CSDN博客_gevent

1 gevent 无法捕获普通的耗时操作, 那么遇见耗时操作只会傻傻等待,不会去切换另外的协程 去执行,那样我们开多协程就没意义了;
2 但是我们需要切换另外的协程去执行,同时我们又不想改变原来的代码;
3 那么我们就需要以打补丁的形式去处理这样的问题,采用的就是猴子补丁。

使用monkey.patch_all()猴子补丁如果遇见耗时操作会自动进行协程的切换。

猴子补丁用于将普通耗时操作改为异步IO形式

import gevent

jobs=[gevent.spawn(foo),gevent.spawn(bar),gevent.spawn(func),gevent.spawn(fund)]
gevent.joinall(jobs)
for job in jobs:
	print(job.value) #能够保证返回的顺序

运用协程池

import gevent
from gevent import monkey, pool
#patch十分必要,否则无法体现协程的优势(io是阻塞的)
monkey.patch_all()
#十分神奇,但一定要注意,先import gevent,完了提前进行monkey.patch_all(),之后再引入其他模块

from bs4 import BeautifulSoup
import requests
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

jobs = []

p = pool.Pool(10)#控制数目,开启协程池


for url in urls:
    jobs.append(p.spawn(get_links, url))#如果指定协程的话,使用的是gevent.spawn表示一个协程任务,但是这里采用协程池就不需要自己分配
gevent.joinall(jobs)
for url, links in zip(urls,[job.value for job in jobs]):
	print("%s\t%s" % (url,links))
asyncio✨

Python中协程异步IO(asyncio)详解 - 知乎 (zhihu.com)

对协程的理解,loop相当于一个事件队列,当任务遇到IO操作时,将任务跳过,进行下一个任务,(asyncio.sleep()模拟了一个IO操作),当这个IO结束时,恢复该协程进行操作

async def 用来定义异步函数,await 表示当前协程任务等待睡眠时间,允许其他任务运行。然后获得一个事件循环,主线程调用asyncio.get_event_loop()时会创建事件循环,你需要把异步的任务丢给这个循环的run_until_complete()方法,事件循环会安排协同程序的执行。

await 等待子生成器的完成,返回结果。在任务之前加入await表示将控制权交给主事件循环,在IO操作结束后,恢复线程,所以说和yield from是类似的。而此时主事件去执行别的任务。挂起,执行别的协程。当task1遇到IO,挂起,主事件循环进行下一个task2,遇到IO挂起,等待IO完成,所以看上去task1和task2是一起开始计时的

async 声明一个协程任务

loop.create_task() 创建一个task实例

import asyncio
import time

async def say_after(delay, what):#声明异步操作
    await asyncio.sleep(delay)
    print(what)

async def main_1():
    print(f"started at {time.strftime('%X')}")

    await say_after(2, 'hello')#未把协程创建成一个任务,需要创建成任务才可以使用,否则调度协程无效,这种写法就是同步的
    await say_after(1, 'world')

    print(f"finished at {time.strftime('%X')}")

asyncio.run(main_1())

async def main_2():
    task1 = asyncio.create_task(
        say_after(2, 'hello'))

    task2 = asyncio.create_task(
        say_after(1, 'world'))

    print(f"started at {time.strftime('%X')}")

    await task1
    await task2

    print(f"finished at {time.strftime('%X')}")

asyncio.run(main_2())

*run_until_complete()*:阻塞调用,直到协程运行结束才返回。参数是future,传入协程对象时内部会自动变为future

可通过调用 *task.result()* 方法来获取协程的返回值,但是只有运行完毕后才能获取,若没有运行完毕,result()方法不会阻塞去等待结果,而是抛出 asyncio.InvalidStateError 错误

通过asyncio.wait()可以控制多任务

asyncio.wait()是一个协程,不会阻塞,立即返回,返回的是协程对象。传入的参数是future或协程构成的**可迭代对象。最后将返回值传给run_until_complete()**加入事件循环

需要返回值时,需要loop.create_task()创建你一个任务实例,然后放入wait中

tasks = [loop.create_task(coroutine_example('Zarten_' + str(i))) for i in range(3)]
#创建协程任务的队列,然后运行完以后会返回值到原有的位置
task.add_done_callback(print_status)#对每一个返回结果做回调操作
import threading
import asyncio

@asyncio.coroutine #标记为coroutine类型,过时的写法,仅示例
def foo(i):
    print('foo {} before yield from {}'.format(i,threading.currentThread()))
    yield from asyncio.sleep(100)
    print('foo {} after yield from {}'.format(i,threading.currentThread()))

loop = asyncio.get_event_loop()
tasks = [foo(i) for i in range(10000)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

关键在于这个

loop = asyncio.get_event_loop()
urls=['https://www.163.com','https://www.qq.com','https://www.jd.com']
tasks=[loop.create_task(fetch_page(url)) for url in urls]
loop.run_until_complete(asyncio.wait(tasks))

(19条消息) python并发运行协程 asyncio.gather 和 asyncio.wait_林胖胖哒的博客-CSDN博客

aiohttp

aiofile

Python aiofiles尝鲜 - 掘金 (juejin.cn)

该操作就是利用异步操作加快文件读写,看看咋用就行

无非就是在 with open, def , 前加async,在读写操作前面加await。然后采用异步方式运行主函数

import aiofiles
import asyncio
import sys
import os
import random

async def nio_read(path):#有可能出现文件编码的错误,
    async with aiofiles.open(path, mode='r') as f:
        print(f'try to read {path}...')
        await asyncio.sleep(random.randint(1,10))
        contents = await f.read()
        print(f'finish the read of {path}')
        return path, len(contents)

#通过loop无法运行nio_read,认为其没有await,但通过gather可以。
#注意观察gather的顺序
async def main():
    tasks=[]
    root=sys.argv[1]
    extention=sys.argv[2]
    d=os.walk(root)
    for _dir, _, files in d:
        for file in files:
            if file.find(extention)>0:
                tasks.append(nio_read(os.path.join(_dir,file)))
    results=await asyncio.gather(*tasks)#该指令用于并发运行任务,传入任务
    for f,l in results:
        print(f'{f}: {l}')

asyncio.run(main())

对于要返回结果的任务,需要create

task.result()为返回结果

或者采用回调的方式,直接将结果传入函数

最后loop.close()

数据库连接

第三方数据库

关系数据库

psycopg

import psycopg
#创建数据库连接(会话)
with psycopg.connect("dbname=zhaojichang user=a password=s") as conn:
#创建游标并通过游标执行sql语句
with conn.cursor() as cur:
#执行sql语句
cur.execute("")
#通过会话完成事务的提交或回滚
conn.commit()
conn.rollback()
#关闭数据库会话
cur.close()
conn.close()
#通过try来确保连接被关闭
conn=pyscopg.connect(DSN)
try:
    with conn:
        with conn.cursor() as curs:
            curs.execute(SQL1)
	with conn:
        with conn.cursor() as curs:
			curs.execute(SQL2)
finally:
    conn.close()        
#利用with语句进行连接和游标的管理
with psycopg.connect(DSN) as conn:
    with conn.cursor() as curs:
		...
#注意退出with上下文时关闭连接
with psycopg.conect(autocommit=True) as conn:#自动提交
    cur=conn.cursor()
    with conn.transaction():
		cur.execute("")
        cur.execute("")
        

psyconpg

#给sql语句传参
cur.execute("""
insert into table(an_int,a_date,a_string)
values(%s %s %s);
""",
(10,datetime.date(2020,11,18),"o'reilly")         
)

cur.execute("""
insert into table(an_int,a_date,a_string)
values(%(int)s %(date)s %(str)s);
""",
{'int':10,'str':"o'reilly",'date':datetime.date(2020,11,18)     
})

cur.execute("insert into foo values(%s)",("bar",))
cur.execute("insert into numbers values('%s')",(10,))

import psycopg.sql
cur.execute(SQL("insert into {} values(%s)").format(Identifier('numbers')),(10,))

#结果的获取
cursor实例本身可迭代
cur.execute("select * from test;")
for record in cur:
    print(record)
fetechone()-返回tuple
fetchmany([size])-返回tuplelist,如果误数据即返回[]
fetchall()-返回tuplelist

#以字典形式返回执行结果
with conn.cursor(row_factory=dict_row) as dict_cur:
	dict_cur.execute('select * from test;')
    res=dict_cur.fetchone()
    print(res['id'])
    print(res['data'])
    
#以类实例的形式返回结果
from dataclasses import dataclass
@dataclass
class Test:
	id:int
    num:int
    data:str
    
with psycopg.connect("dbname=zhaojichang") as conn:
	with conn.cursor(row_factory=class_row(Test))  as class_cur:
		class_cur.execute('select * from test')
        test=class_cur.fetchone()
        print(test.id)
        print(test.num)
        print(test.data)
        
#批量操作
import psycopg
import random
with psycopg.connect("dbname=zhaojichang") as conn:
	with conn.cursor() as cursor:
		with cursor.copy("COPY test (num, data) FROM STDIN") as copy:
			records=[(random.randint(0,100000),random.randint(0,100000)) for i in range(100000)]
			print(len(records))
			for r in records:
				copy.write_row(r)
print("finished...")

#异步IO
import asyncio
import psycopg

async def fetchmany(dsn,volume=1000):
    async with await psycopg.AsyncConnection.connect(dsn) as aconn:
        print(f"connection established for {volume}")
        async with aconn.cursor() as acur:
            await acur.execute("SELECT * FROM test")
            res=await acur.fetchmany(size=volume)
            return volume, res

dsn = 'dbname=zhaojichang'
loop = asyncio.get_event_loop()
tasks=[loop.create_task(fetchmany(dsn,volume=v)) for v in range(10,30)]
loop.run_until_complete(asyncio.wait(tasks))
for t in tasks:
    v,res=t.result()
    print(f'{v}:{len(res)}')

非关系数据库

pymongo

import pymongo
#创建连接
myclient = pymongo.MongoClient("mongodb://localhost:27017/")
dblist = myclient.list_database_names()
for db in dblist:
	print(db)
#指定数据库    
mydb = myclient["student"]
mycol = mydb["comments"]
myclient.close()
#指定集合
collection=db['test-collection']

ORM(补充)

with conn:
with conn.cursor() as curs:
curs.execute(SQL1)
with conn:
with conn.cursor() as curs:
curs.execute(SQL2)
finally:
conn.close()


```py
#利用with语句进行连接和游标的管理
with psycopg.connect(DSN) as conn:
    with conn.cursor() as curs:
		...
#注意退出with上下文时关闭连接
with psycopg.conect(autocommit=True) as conn:#自动提交
    cur=conn.cursor()
    with conn.transaction():
		cur.execute("")
        cur.execute("")
        

psyconpg

#给sql语句传参
cur.execute("""
insert into table(an_int,a_date,a_string)
values(%s %s %s);
""",
(10,datetime.date(2020,11,18),"o'reilly")         
)

cur.execute("""
insert into table(an_int,a_date,a_string)
values(%(int)s %(date)s %(str)s);
""",
{'int':10,'str':"o'reilly",'date':datetime.date(2020,11,18)     
})

cur.execute("insert into foo values(%s)",("bar",))
cur.execute("insert into numbers values('%s')",(10,))

import psycopg.sql
cur.execute(SQL("insert into {} values(%s)").format(Identifier('numbers')),(10,))

#结果的获取
cursor实例本身可迭代
cur.execute("select * from test;")
for record in cur:
    print(record)
fetechone()-返回tuple
fetchmany([size])-返回tuplelist,如果误数据即返回[]
fetchall()-返回tuplelist

#以字典形式返回执行结果
with conn.cursor(row_factory=dict_row) as dict_cur:
	dict_cur.execute('select * from test;')
    res=dict_cur.fetchone()
    print(res['id'])
    print(res['data'])
    
#以类实例的形式返回结果
from dataclasses import dataclass
@dataclass
class Test:
	id:int
    num:int
    data:str
    
with psycopg.connect("dbname=zhaojichang") as conn:
	with conn.cursor(row_factory=class_row(Test))  as class_cur:
		class_cur.execute('select * from test')
        test=class_cur.fetchone()
        print(test.id)
        print(test.num)
        print(test.data)
        
#批量操作
import psycopg
import random
with psycopg.connect("dbname=zhaojichang") as conn:
	with conn.cursor() as cursor:
		with cursor.copy("COPY test (num, data) FROM STDIN") as copy:
			records=[(random.randint(0,100000),random.randint(0,100000)) for i in range(100000)]
			print(len(records))
			for r in records:
				copy.write_row(r)
print("finished...")

#异步IO
import asyncio
import psycopg

async def fetchmany(dsn,volume=1000):
    async with await psycopg.AsyncConnection.connect(dsn) as aconn:
        print(f"connection established for {volume}")
        async with aconn.cursor() as acur:
            await acur.execute("SELECT * FROM test")
            res=await acur.fetchmany(size=volume)
            return volume, res

dsn = 'dbname=zhaojichang'
loop = asyncio.get_event_loop()
tasks=[loop.create_task(fetchmany(dsn,volume=v)) for v in range(10,30)]
loop.run_until_complete(asyncio.wait(tasks))
for t in tasks:
    v,res=t.result()
    print(f'{v}:{len(res)}')

非关系数据库

pymongo

import pymongo
#创建连接
myclient = pymongo.MongoClient("mongodb://localhost:27017/")
dblist = myclient.list_database_names()
for db in dblist:
	print(db)
#指定数据库    
mydb = myclient["student"]
mycol = mydb["comments"]
myclient.close()
#指定集合
collection=db['test-collection']

ORM(补充)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值