python进阶
参考
《流畅的python》 https://github.com/fluentpython/example-code-2e
1. 数据类型
1.1 sort()/sorted()
sort()
是对象的方法,inplace
操作
sorted()
是内置函数,需重新赋值,且sorted
可以接受多种类型的数据,但是返回的都是```list``列表
sorted(list,key=)
key的使用
https://blog.csdn.net/qq_40549291/article/details/88683929
reverse
参数表示输出是否为原来的倒序
1.2 具名元祖collections.namedtuple()
定义tuple子类,用于创建只有少数属性但是没有方法的对象
https://blog.csdn.net/june_young_fan/article/details/91359194
https://blog.csdn.net/u012633319/article/details/109322673
https://blog.csdn.net/m0_37586991/article/details/103713691
https://docs.python.org/3.10/library/collections.html#collections.namedtuple
import collection
# collection.namedtuple(typename,filed_name) #typename为描述性声明,filed_names为字段名(属性)
Point=collection.namedtuple('Point',['x','y'])
p=Point(x=1,y=2) # 实例化
City=collection.namedtuple('City','a b c')
City._fileds # ('a','b',''c) 显示属性
obj=('aa','bb','cc')
city_obj=City._make(obj) # ._make() 创建具名元组实例
# ._asdick() 以orderDict的形式返回数据
for name,data in City._asdict().items():
print(f'{name}: {data}')
1.3 序列对象
python内置序列类型都是一维的,只支持单一索引,元组式索引不能使用
1.3.0 序列分类
- 容器序列类型
list
tuple
collection.deque
可存放不同类型的数据,实际上存放的是所包含的任意类型的对象的引用
- 扁平序列类型
str
bytes
bytearray
memoryview
array.array
只能存放一种数据类型,存放的是值而不是引用,即一段连续的内存空间
分类2
- 可变类型
list、bytearray、array.array、collections.deque、memoryview
- 不可变类型
tuple、str、bytes
1.3.1 可变对象创建*n时的错误!!!
嵌套 * 的使用
使用*
创建列表相当于对同一个对象的引用,慎用!!!
# version1
a=[['_']*2 for i in range(2)] # [['_','_'],['_','_']]
a[0][0]=100 # [['0','_'],['_','_']]
# version2
a=[['_']*2]*2 # [['_','_'],['_','_']]
a[0][0]=100 # [['0','_'],['0','_']]
# 上述version1 等价于
a=[]
for i in range(2):
a.append(['_']*2)
# 上述version2 等价于
a=[]
row=['_']*2
for i in range(2):
a.append(row) # 对同一个对象引用多次
1.3.2 tuple 创建注意事项
不要把可变对象放在元组中!!!
1.3.3 可变对象与不可变对象进阶
接上述1.3.2节
不可变对象实际上说的是数据结构的物理内容,即保存的引用不可变
t1=(1,2,[1,2])
t2=(3,4,[3,4])
t1==t2
id(t1[-1]) # 4302515784
t1[-1].append(4)
id(t1[-1]) # 4302515784 # !!! 这里没有变化,
可变对象的inplace修改操作并不改变 id()
a=10
b=[2,3]
t=(a,b)
id(b) # 1886905984064
id(t[-1]) # 1886905984064
b.append(4)
id(b) # 1886905984064
id(t[-1]) # 1886905984064
应用的注意事项
默认参数注意事项
避免使用可变对象作为默认参数
class C(object):
def __init__(self,x=[]): # 应该避免这种写法的出现!!!
self.x=x
# 可以改成
class C(object):
def __init__(self,x=None):
if x:
self.x=x
else:
self.x=[]
id
!!!
无论是深拷贝还是浅拷贝,id()前后均不相同(这句话不完全正确),(对可变对象的深浅拷贝id前后均不相同,对不可变对象的浅拷贝id前后相同(也就是对不可变对象的浅拷贝实际上返回的是对该对象的一个引用),深拷贝id前后不同)
!!! 要讲深浅拷贝的id()变化 和 inplace的id()变化分开来看
而且注意inplace操作返回值为None
from copy import copy
a=[1,2]
b=(3,4)
a2=copy(a) # id(a2) != id(a)
b2=copy(b) # id(b2) 等于 id(b)
a.append(3) # append前后,id(a)相同
浅拷贝/深拷贝
个人理解:浅拷贝实际上是不完全拷贝,只拷贝外层容器,且不论是可变对象还是不可变对象,如果元素是不可变对象,对(不可变对象)元素的修改,那么副本不会改变,而对于可变对象元素,对应的副本会进行改变
另外,浅拷贝对外层容器是深拷贝
注意拷贝与直接赋值之间的区别,区别就是引用的不同(内存地址的不同)
from copy import copy,deepcopy
a=[1,2,3]
b=a.copy() # 等价于b=list(a) b=a[:]
c=copy(a)
a.append(10)
a # [1,2,3,10]
b # [1,2,3]
c # [1,2,3]
!!!
无论是深拷贝还是浅拷贝,id()前后均不相同(这句话不完全正确),(对可变对象的深浅拷贝id前后均不相同,对不可变对象的浅拷贝id前后相同,深拷贝id前后不同!!!)
!!! 要讲深浅拷贝的id()变化 和 inplace的id()变化分开来看
而且注意inplace操作返回值为None
from copy import copy
a=[1,2]
b=(3,4)
a2=copy(a) # id(a2) != id(a)
b2=copy(b) # id(b2) 等于 id(b) !!!
a.append(3) # append前后,id(a)相同
1.3.4 自定义序列功能
序列协议:只需要定义
__len__
和__getitem__
方法就行
注意,并不是一定要实现iter方法,从而让其变成可迭代对象,因为序列是序列,可迭代对象是可迭代对象
见python-面向对象变成
1.4 运算方法
1.4.1增量运算+= ; …
+= 对于可变对象来说就是__iadd__(); __imul__()方法,表示就地操作,不改变原对象的内存地址
对于不可变对象的增量运算,只是相当于__add__(); __mul__方法,变量名会被关联到新的对象上
注意,数值类型也算是不可变对象,所以前后的
id()
不同,表明是新的对象
1.4.2 is ==
https://blog.csdn.net/qq_26442553/article/details/82195061 – 还没看
is
比较的是对象,是否是同一个对象(指向的是同一个内存地址)
==
比较的是值
id()
返回的是对象标识(在cpython中是内存地址)的整数表示
is
运算符比==
运算符快
==
等价于a.__eq__(b)
# 不要使用 variable == None
# !!! 要使用 variable is None
1.5 array数组与memoryview内存视图
1.5.1 array数组
list
存放的是浮点数
array.array
存放的是数值的机器码(字节形式),因此对于大规模数据的存储更加高效
array
数组不仅可以用于创建数值,还可以从文件中读取和存储数值,且相比于文本文件的存储与读取(先读取文本再转换为数值类型)更加高效
注意:array
数组不支持.sort()
就地操作
array.array
是可变序列
array
不允许在数组中存放除指定类型之外的数据
from array import array
floats=array('d',(random() for i in range(10**7))) # 'd' 表示类型码,注意这里边是生成表达式,不是列表推导式
# 把数组存入二进制文件中
fp=open('floats.bin','wb')
floats.tofile(fp)
fp.close()
#创建一个双浮点精度空数组
floats2=array('d')
#从二进制文件中读取数组
fp=open('floats.bin','rb')
floats2.fromfile(fp,10**7)
1.5.2 memoryview内存视图 – 没细看
memeoryview
是一个内置类,其能让用户在不复制内容的情况下操作同一个数组的不同切片,即在不复制内容的前提下,在数据结构之间共享内存。其中数据结构可以是任何形式的
应用场景:处理大数据集合时,共享内存,从而节省内存
重点:**共享内存~~(浅拷贝)~~(和拷贝好像还不一样) **
返回给定参数的 内存查看对象
内存查看对象:指 对支持缓冲区协议的数据进行包装,在不需要赋值对象基础上允许Python代码访问,可以理解为对内存地址的直接访问
memoryview
对象相当于nums在内存中的表示形式,但是属于不同的对象
https://blog.csdn.net/Murphy_31/article/details/107100413 – 没看懂
https://blog.csdn.net/sinat_41395907/article/details/105902563 – 没看懂
memoryview.cast
能用不同的方式读写同一块数内存数据,且内容字节不会随意移动,即会把同一块内存中的内容打包成一个全新的memoryview
对象
memoryview.toist()
转换成列表
见《流畅的python》
1.6 字典进阶
1.6.1 setdefault() 查找并插入新值
键存在,返回指定键值对应的value
键不存在,返回设定的默认的value
my_dict.setdefault(key,[]).append(new_value)
# 先创建一个空list,然后再在空list的新值
if key not in my_dict():
my_dict[key]=[]
my_dict[key].append(new_value)
1.6.2 defaultdict/__ missing __查找取值
键不存在,也能返回一个默认的值
# version1
my_dict.get('key',fault_value)
collections.defaultdict
defaultdict
是dict类的一个子类
dict=defaultdict(factory_function)
,接受一个工厂函数作为参数,可以是list
,set
,str
,int
等,即当key
不存在的收,返回工厂函数的默认值,[ ],' ',0,set
实际就是,将工厂函数构造方法作为default_factory
来创建一个defaultdict
,如果查询不到对应的值,则首先利用default_factory
构造方法进行调用,为其创建一个默认对象并返回,然后进行后续操作
注意,如果defaultdict()
没有指定对应的default_factory
,当查询不到的时候,仍然会触发keyerror
注意2,default_factory
只会在__getitem__
里被调用,即在执行d[k]
时创建初始值,其他方法,如d.get(k)
则不会被用来进行初始化对象
# version2
from collections import defaultdict
# 创建defaultdict对象的时候,就需要给其配置一个为找不到的键创建默认值得方法
d={}
d.setdefault(key,[]).append(v)
# 等价于
d=defaultdict(list)
d[key].append(v)
# 还可以用于计数
d=defaultdict(int)
d[k]+=1
__missing__
进一步讲,当找不到键的时候会牵扯到
__missing__
方法,注意,dict基类没有定义__missing__
方法,但是如果一个子类继承自dict基类,且提供了__missing__
方法,那么当__getitem__
(d[k]
)方法找不到键的时候,会自动调用__missing__
方法,另外,如上述提到的,在利用get()
方法或__contain__
方法(k in d
)时,__missing__
方法仍然不起作用,即会抛出keyerror
1.6.3 OrderedDict
在添加键的时候会保持顺序,且在迭代键的时候次序一致
from collections import OrderedDict as od
# 下面三个是等价方法
d=od([('a',1),('b',2)])
d=od(a=1,b=2)
d=od({'a':1,'b':2})
d.keys()
d.values
# 转化为普通dict
dict(d)
1.6.4 不可变映射类型
不希望用户修改键值
types
模块中的MappingProxyType
类,利用这个类返回对象的一个只读映射视图,但是会根据原对象进行动态更新,但是通过只读视图不能对原对象进行修改
from types import MappingProxyType
d={1:A}
d_proxy=MappingProxyType(d)
# d 修改 d_proxy同样变动
# d_proxy变动,直接抛出异常
1.7 字符串/字节进阶
码位???
https://blog.csdn.net/weixin_43465312/article/details/105918985 – 还没看
数值/字符串/字节串(二进制)
字节是单位,一个字节可以表示(包括)8位二进制数或2位十六进制数
进制数才是底层存储的数据,十六进制是二进制数的另一种表达方式
数值与二进制数
本质上是进制转换
https://blog.csdn.net/weixin_43353539/article/details/89444838
https://blog.csdn.net/qq_46119688/article/details/122640639
相关方法:int
,bin
,oct
,hex
字符串与字节串(字节串就是字节序列)
通过编码字符集进行转换
注意:只包含数值的字符串,hex(num)
和'num'.encode('utf8')
是不一样的
相关方法:encode()
,decode()
,bytes()
,bytearray()
实际传输过程中,利用struct集合上述转换进行 编解码(拆包与解包)
见下面的
struct
bytes/bytearray
将字符串对象转换为字节串对象后,
b'xyz' 或 b'\xabc'
bytes
是不可变类型
bytearray
不可变类型
字节串对象的各个元素都是介于0~255之间的整数,因为一个字节是8位(2位十六进制数),而8位二进制数是0-255的区间
0-255的整数,其实是将表达的进制数转换成十进制数的结果
字节串对象方法
https://blog.csdn.net/xietansheng/article/details/115557553
部分支持字符串对象的方法,可以支持字节串对象
创建
- 对字符串进行创建
bytes('string',encoding='utf8')
例如:a=bytes('ca',encoding='utf8') # 99 97
- 利用可迭待对象进行创建
a2=bytes([99,97]) # b'ca'
例如下面的bytearray的创建方法
切片/索引
索引单个元素返回的是该字节对应的0-255的整数
切片(即使是单个字节的切片)返回的也是字节串对象 b’xxx’
'€'.encode('utf8') # b'\xe2\x82\xac' # 说明占3个字节,8位等于一个字节,一节字节等于2位十六进制
'€'.encode('utf8')[0] # 226
len('€'.encode('utf8')) # 3,印证了3个字节的说法
a='hello'.encode() # b'hello'
a[0] # 104,十进制的表达当时
# 切片与索引
b=bytes('ca',encoding='utf8') # b'ca'
b[0] # 99 是整数
b[0:1] # b'c'
可变对象bytearray的修改
由于无论是
bytes
对象还是bytearray
对象,每个元素都是0-255之间的整数(注意,可能多个元素才能组成一个正确的要表达的字符),且由于bytes
是不可变类型,而bytearray
是可变类型,对于可变类型,如其他常用的可变类型一样,都可以进行修改,注意修改的是整数元素
a=bytearray(range(3)) # bytearray(b'\x00\x00\x00')
a.append(65) # bytearray(b'\x00\x00\x00A'),由于65对应的符合ASCII码,所以直接添加进去
b=[x for x in '€'.encode('utf8')] # [226, 130, 172]
for x in b:
a.append(x)
print(a) # bytearray(b'\x00\x00\x00A\xe2\x82\xac')
a.decode('utf8') # '\x00\x00\x00A€'
struct/memoryview
https://docs.python.org/zh-cn/3.10/library/struct.html?highlight=struct#module-struct
struct
用于处理二进制数据
这个好像才是根据数据协议进行传输的形式
其实就是对不同的数据类型对象,根据定义的协议,在字节串(流)与不同类型数据之间进行转换
struct
模块提供了一些函数,把打包的字节序列转换成不同类型字段组成的元素,或执行反向转换,即把元组转换成打包的字节序列
struct
模块能处理bytes
和bytearray
和memoryview
对象
处理二进制数据,如果用struct来处理文件,需要用wb或rb以二进制(字节串)读写文件
处理c语言中的结构体
struct
不能直接对字符串进行转换
pack
转换成字节串,(注意:这和encode不一样,pack有严格的补位要求)
unpack
转换成不同类型的元组
a=bytes('name',encoding='utf8')
b=bytes('李四',encoding='utf8')
c=28
d=False
e=180.
data=[a,b,c,d,e]
# format
mat=f'{len(a)}s{len(b)}si?f'
# 转换成字节流格式
b_data=struct.pack(mat,*data)
# 转换为不同的类型
data_new=struct.unpack(mat,bdata)
数据存储扩展–大小端
数据以字节编址
端:数据在存储器中存放顺序的顺序
大端:高位存在低地址
小端:低位存在低地址
文本/二进制文件处理进阶
处理文本的实践经验:三明治
尽早的把输入(如读取文件时)的字节序列(二进制格式)解码成字符串:
bytes
->str
在业务逻辑层面,只处理字符串对象(不能频繁的进行编码、解码操作):str
<–>str
对输出来说,要今早的把字符串对象编码成字节序列:str
->bytes
本质上,读写操作都是对字节串对象进行解析的,但是当以文本方式进行读写时,python会自动实现字节串到字符串的转换
注意,当以二进制模式进行读写时,操作的对象是字节串对象,而不是字符型进制数(底层原始数据)(字节串对象与进制数在字面表达上的差异)
注意:建议在读写过程中,显示的指定编码方式,防止跨平台的兼容问题出现
注意:不要以二进制模式打开文本文件,只应该以二进制模式打开二进制文件
# 查看文本的编码方式
f=open('a.txt','r')
f.encoding # 文本的编码方式
二进制模式文件处理
读写都是字节串(字节序列)(二进制)数据
正则表达式处理字符串与字节串
注意:某些(正则表达式)模式,对于字节序列只能匹配ASCII字符的字节串形式
os函数中的字符串和字节序列 – 没懂 /《流程的python》4.9
os模块中的所有函数、文件名、路径名参数即能使用字符串也能使用字节序列
unicode字符规范化 – 没细看 / 见《流畅的python》4.6
对于组合字符或者是变音符号,同一字符串(字符序列)的不同构成方式(表达方式)可能被识别成不同的对象,因此需要unicode规范化
from unicodedata import normalize
,以不同的标准进行规范化处理
unicode文本排序 – 没细看 / 见《流畅的Python》4.7
序列会涉及比较运算,对于非ASCII字符序列,可能会出现问题
出现问题的更多的可能是组合字符或变音符号
unicode 数据库 – 没细看 / 见《流程的python》4.8
1.8 字符串进阶
!s / !r format
!s
在格式化之前应用str()
方法转换数据,!r
在格式化之前应用repr
转换数据
补充:!a
调用ascii()
方法转换数据
!r
对于字符串数据,在输出后显示一个单引号,对数值型数据依然不会加单引号,!s
对字符串和数值型数据都不会加单引号
!s
等价于%s
,!r
等价于
print('{!r} {}'.format('1',2)) # '1' 2
'{!r} {}'.format('1',2) # "'1' 2"
print('{!s} {}'.format('1',2)) # 1 2
'{!s} {}'.format('1',2) # 1 2
%
通常% 在
'%'%
方法中使用
在某些特定类型的专属格式化中可以与format
连用,如datetime
1.9 散列对象
支持
hash()
函数,且通过__hash__()
得到的散列值永不改变,且如果a==b
,那么hash(a)==hash(b)
也是True的情况下,如果对象即有__hash__
方法,也有__eq__
方法,那么这样的对象被称为可散列的对象
如果一个对象是可散列的,那么在找个对象的声明周期中,它的散列值是不变的,而且找个对象需要实现__hash__
方法和__eq__
方法,这样才能跟其他键做比较。如果两个可散列的对象是相等的,那么他们的散列值一定是一样的
在内置的类型中,大多数不可变的类型都是可散列的,如数值类型、字符串、元组、frozenset; 另外,对于元组,仅当元组的每一个元素都是可散列的时,元组才是可散列的
为了是自定义的实例对象是可散列的,1. 要么同时实现__hash__、__eq__
方法,要么都不实现,(通常来说,用户定义的类型的对象都是可散列的,且散列值是id()
函数的返回值,所以这些对象在比较的时候都是不相等的(不同对象),(如果一个对象实现了__eq__
方法,并且在方法中用到了这个对象的内部状态的话,那么只有当所有这些内部状态都是不可变的情况下,这个对象才是可散列的))
from array import array
import math
class Vector2d:
typecode='d'
def __init__(self,x,y):
self.x=float(x)
self.y=float(y)
def __iter__(self):
return (i for i in (self.x,self.y))
def __repr__(self):
# print(self,type(self))
class_name=type(self).__name__
return '{}({!r},{!r})'.format(class_name,*self)
def __str__(self):
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)])+
bytes([array(self.typecode,self)]))
def __eq__(self,other):
return tuple(self)==tuple(other)
def __abs__(self):
return math.hypot(self.x,self.y)
def __bool__(self):
return bool(abs(self))
# def __hash__(self):
# return hash(self.x)^hash(self.y)
v=Vecotor(3,4)
print(hash(v)) # error,不可散列对象,因为有了eq,还没实现hash,最重要的是,v.x=7 在类外,可以通过实例对象的属性值对其属性进行修改,这相对变成了可变元素,使得hash(v)值进行了变化,所以是不可散列对象
# 要变成可散列对象,那么就要
# 将属性值变成只读属性
2. 面向对象
2.0 元对象协议
元对象协议:构建核心语言的API
2.1 迭代器/生成器
可迭代对象不等于可索引
可索引 对应于__getitem__
可迭代 对应于__iter__
isinstace(data,Iterable) # T or F
2.1.1 迭代器
可迭代对象不等于迭代器
from collections.abc import Iterable, Iterator
isinstance(obj,Iterable) # 判断对象是否是可迭代
isinstance(obj,Iterator) # 判断对象是否是迭代器
内置list 是可迭代的(可迭代对象),但是不是迭代器
只要实现了__iter__ 就是可迭代对象,但不一定是迭代器
__iter__(self)
__next__(self)
try:
pass
except StopIterable:
pass
# 等于for ... in ...
# for 内自带一个StopIterable 异常处理机制
http://c.biancheng.net/view/5419.html
https://www.zhihu.com/search?q=%E7%94%9F%E6%88%90%E5%99%A8%E5%92%8C%E8%BF%AD%E4%BB%A3%E5%99%A8&utm_content=search_suggestion&type=content
自定义迭代器
__next__(self) # 返回容易的下一个元素
__iter__(self) # 该方法返回一个迭代器
class CLS:
def __init__(self):
pass
def __iter__(self):
return self
def __next__(self):
pass
return xxx
c=CLS()
next(c)
# 利用内置函数生成迭代器
iterlist=iter([1,2,3])
# 使用next方法
next(iterlist)# 或 iterlist.__next__()
# 使用for 方法
for x in iterlist:
pass
还有一种写法
class IterList:
def __init__(self):
self.items=[]
def add(self,item):
self.items.append(item)
def __iter__(self):
iterator_list=IteratorList(self)
return iterator_list
class IteratorList:
def __init__(self,iterList):
self.iterator_list=iterList
self.current=0
def __next__(self):
if self.current<len(self.iterator_list.items):
item=self.iterator_list.items[self.current]
self.current+=1
return item
else:
raise stopIteration
def __iter__(self):
return self
避坑!!!
要想重复调用
self.start_num=-1 # !!!很重要
from collections.abc import Iterable
from collections.abc import Iterator
class A:
def __init__(self,l):
self.items=[x for x in l]
self.start_num=-1
def __iter__(self):
return self
def __next__(self):
self.start_num+=1
try:
return self.items[self.start_num]
except:
self.start_num=-1 # !!!很重要
raise StopIteration # 这里不会报错是因为,这个Error与其他的错误类型内部处理机制不同
return self.start_num
l=[1,2,3]
a=A(l)
for x in a:
print(x)
for x in a:
print(x)
避坑2!!! – 很重要!!!
内置类型使用for x in iter(items)
可以
自定义类型不能使用for x in iter(items)
因为自定义的时候__iter__方法 return的是 self
,要想能够使用,应该return 不是self而是一个iterator
迭代器与for
内置可迭代类型 虽然
dir(built_in)
中只有__iter__
方法没有__next__
方法,但是仍然可以使用for _ in xxx
进行迭代,这是因为内置数据类型中的__iter__
中返回了另一个具有__next__
的对象(迭代器)
结果显示,执行for _ in xxx
时会自动先取出一个iter
例子1
class Itable:
def __init__(self,items):
self.items=items
self.curr=0
def __iter__(self):
return self
def __next__(self):
try:
item=self.items[self.curr]
self.curr+=1
return item
except:
raise StopIteration
it=Itable(list(range(10)))
print(isinstance(it,Iterable)) # True
print(isinstance(it,Iterator)) # 不实现__next__时: False,实现__next__的时:True
# 不实现__next__时: 不可执行
# 实现__next__的时:可执行
for x in it:
print(x)
# 不实现__next__时: 不可执行
# 实现__next__的时:可执行
for x in iter(it):
print(x)
例子2
class Itable:
def __init__(self,items):
self.items=items
self.curr=0
def __iter__(self):
return Itator(self)
class Itator:
def __init__(self,items):
self.items=items
self.curr=0
def __iter__(self):
return self
def __next__(self):
try:
item=self.items.items[self.curr]
self.curr+=1
return item
except:
raise StopIteration
it=Itable(list(range(10)))
print(isinstance(it,Iterable)) # True
print(isinstance(it,Iterator)) # False
# 尽管 Iterator 是False,但是仍然是执行
for x in it:
print(x)
# 尽管 Iterator 是False,但是仍然是执行
for x in iter(it):
print(x)
迭代器与拆包操作
这里针对的是自定义类型,内置类型的拆包操作有python解释器的内部实现策略
要想自定义对象可以实现拆包操作,那么要求 对象 是 可迭代对象 ,也就是 实现了__iter__
方法(只有定义成可迭代对象才能实现拆包操作)
别忘了*
或**
也能实现拆包操作
class A:
def __init__(self,x,y):
self.x=x
self.y=y
a=A(1,2)
m,n=a # error: TypeError: cannot unpack non-iterable A object
# 直接创建一个可迭代对象
class A:
def __init__(self,x,y):
self.x=x
self.y=y
def __iter__(self):
return self
a=A(1,2)
m,n=a # error: TypeError: iter() returned non-iterator of type 'A'
# 正确的做法是
class A:
def __init__(self,x,y):
self.x=x
self.y=y
def __iter__(self):
return (x for x in (self.x,self.y)) # 注意,这是生成器表达式!!!
a=A(1,2)
当同时实现
__iter__
和__next__
时的拆包操作是什么???
迭代器与 __ getitem __/ for in
在
for... in
时,如果实现了__iter__/__next__
方法,则基于iter和next
方法进行调用,如果没有实现实现了__iter__/__next__
方法,但实现了__getitem__
方法,则利用__getitem__
方法循环,且无需实现StopIteration
,如果连__getitem__
方法都没有实现,则抛出异常
迭代器与__ call __()???
2.1.2 迭代器进阶 – 还要深入思考
from array import array
import math
class Vector2d:
typecode='d'
def __init__(self,x,y):
self.x=float(x)
self.y=float(y)
def __iter__(self):
return (i for i in (self.x,self.y)) # 注意:这里是生成器表达式
def __repr__(self):
# print(self,type(self))
class_name=type(self).__name__
return '{}({!r},{!r})'.format(class_name,*self) # 注意这里的*self
def __str__(self):
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)])+
bytes([array(self.typecode,self)]))
def __eq__(self,other):
return tuple(self)==tuple(other)
def __abs__(self):
return math.hypot(self.x,self.y)
def __bool__(self):
return bool(abs(self)) # 先调用__abs__方法
2.1.3 生成器
迭代器:需要将所有数据放到容器中,然后开始迭代
生成器:能够在迭代的同时生成元素,(只记录生成数据的方式)
生成器实现了迭代器协议,并实现了延时操作(生成器是一种特殊的迭代器)
yield一次返回一个值,然后挂起,yield只能遍历完整数据一次
https://www.zhihu.com/search?q=%E7%94%9F%E6%88%90%E5%99%A8%E5%92%8C%E8%BF%AD%E4%BB%A3%E5%99%A8&utm_content=search_suggestion&type=content
(通过next()调用,如果是第一次执行,则从def代码块的开始部分执行,知道遇到yield位置,并且把yield关键字后的数值返回,当做next()的返回值;如果不是第一次执行,则从上一次暂停的位置执行,即从上一次yield关键字的下一个语句开始执行,直到遇到下一次yield)
创建生成器的方法
# version 1 对于列表推导式
a=[x for x in range(10)] # -->
a=(x for x in range(10)) # generator
#for 会自动处理StopIteration
for x in a: # a: generator
print(x)
# version 2 函数方式
def fun():
for i in range(10):
yield i # fun()函数暂停
pass # some other code 直到下一个next执行,或者是下一次for
# 使用生成器时,需要进行函数调用
f=fun() # 返回生成器对象,但 不执行代码
# 使用next方法
f.__next__() # 或next(f) # !!! 这种会抛出StopIteration
# 使用for方法
for i in f:
pass
# 完整的例子
def func():
for i in range(10):
print('generate:',i)
yield i
for x in func():
print('...',x)
生成器与迭代器
# 利用迭代器构建生成器
class PointXY(object):
def __init__(self):
self.x=0.
def __iter__(self):
return self
def __next__(self):
temp_y=2*self.x+1
temp_point_x_y=(self.x,temp_y)
self.x=temp_y
return temp_point_x_y
pointxy=PointXY()
for x in pointxy:
print(x)
生成器的StopIteration异常处理与return
通常情况下 不能得到return 的返回值,返回的是yield后面的的值
return 相当于 raise StopIteration
def G():
pass
yield yield_data
return return_data
try:
next(generator)
except StopIteration as ex:
print(ex.value) # 返回的是 return_data
生成器中的send方法
send 相当于 监听与发送中的发送
def G():
pass
# yield data # 该语句没有返回值,返回的是None
data_from_send = yield data # 当主函数调用到g.send(send_data) 时,挂起,并将send_data 赋值给data_from_send
g=G() # 创建生成器对象
g.send(send_data)
上述通过括号()创建生成器的方式,也叫生成表达式,注意与列表推导式区分
2.2 装饰器
2.2.1 @property 内置装饰器
见https://blog.csdn.net/L_fengzifei/article/details/105178477
2.2.2 自定义装饰器
用于包装其他函数,在其他函数之前或之后进行操作
装饰器通常在一个模块中定义,然后再使用中import导入
# 例子1
# 编码过程中,这是需要自己定义的,用于完善f函数的功能
# 这是一个闭包函数
def wrap(func): # func是函数的引用
def decorate():
# front code
func()
# after code
return decorate
@warp
def f():
print('source code')
# 装饰器调用流程
1. 遇到装饰器@warp 首先将其作为可调用对象,进行调用,即warp(func),且此时的func是f函数的引用,即将f函数当做实参传递给warp函数,即warp(f)
此时就相当于闭包函数
2. 将warp()函数的返回值当做新的f的值,即f=warp(f),在内部机制中,就相当于f=decorate
f此时指向了decorate
3. 在主函数中调用f(),则实际上运行的是warp(f)闭包函数中的decorate()函数
4. 也就是相当于在原始f函数前后装饰了其他功能
注:在定义
# 例子2 -- 带参装饰
def wrap(func): # func是函数的引用
def decorate(x,y): # a,b # 这里必须与原函数形参保持一致
# front code # 这里可以对x,y进行修改,从而传递给func函数!!!
func(x_new,y_new) # 这里是被包装的函数,因此也必须与原函数保持一致
# after code
return decorate
@warp
def f(x,y):
print('source code')
# 例子3 包装多个具有不同参数的函数
def wrap(func): # func是函数的引用
def decorate(*args,**kwargs): # 接受不定长的参数
# front code
func(*args,**kwargs) # 传参时,拆包
# after code
return decorate
@warp
def f1(x,y):
print('source code')
@warp
def f2(x,y,z):
print('source code')
# 例子3 多装饰器包装
def wrap1(func):
def decorate1():
return 'wrap1'+func()
return decorate1
def wrap2(func):
def decorate2():
return 'wrap2'+func()
return decorate2
@wrap1
@wrap2
def f():
return 'f'
# 多装饰器包装,执行顺序
1. 先执行wrap1中的非闭包代码块
2. 执行wrap2中的非闭包代码块
3. 假设没有wrap1,和普通的装饰器代码执行顺序一致 f=wrap2(f)
4. 此时f指向decorate2
5. wrap1包装的就是decorate2,讲此时的f(本质是decorate2)作为wrap1的实参,引用
6. 将@wrap2与其原始装饰的f看作一个整体,然后在用warp1包装,当做普通装饰器函数进行执行
7.此时原始f指向了decorate1
7. 最终执行f(),编程了decorate1+decorate2+f
# 例子4 装饰器带参数
def wrap(param=0):
def wrap1(func):
def decorate(*args):
# front code
func_return=func(*args)
# after code
return func_return
return decorate
return wrap1
@wrap(param)
def f(x,y):
return x+y
# 装饰器带参数,执行顺序
1. 先执行@wrap1,即带参数wrap1
2. 返回wrap1的引用,并用wrap1包装函数f,相当于@wrap1 def包装
# 例子5 将类对象当做装饰器
class Wrap(object):
def __init__(self,func):
self.__func=func
def __call__(self):
return self.__func()
# def __call__(self,*args):
# return self.__func(*args)
@Wrap
def f():
pass
f()
# @wrap
# def f(x,y):
# # pass
# f(x,y)
# 将类对象当做装饰器,执行过程
1. 将f作为类Wrap初始化方法的参数
2. 注意:初始化形参,在实例化后自动释放,所以利用私有对象进行存储
3. 此时self.__func指向了原始函数f,即是对原始函数f的引用
4. 执行@Wrap后,返回的是实例对象,即是f指向了实例对象
5. 如果想要直接调用f函数,则需要实现__call__函数
# 例子6 带参数的类作为装饰器
class Wrap(object):
def __init__(self,num):
self.__num=num
def __call__(self,func):
self.__func=func
return self.call_func
def call_func(self):
self.__func()
def call_func(self,*args):
self.__func(*args)
@Wrap(10)
def f():
pass
f()
# 带参类装饰器,执行过程
1. 实际上是将Wrap(10)初始化后的实例对象包装下面的函数f
2. 要想实现包装,则需要定义call函数,将函数f作为参数进行传递
3. f指向了call函数的返回值self.call_func
4. 调用f(),实际上是self.call_func()调用
执行顺序的补充
装饰器 在 被装饰器的函数定义 之后,立即被执行
注意函数定义,并不是函数调用(但是定义之后会在内存中存在一个地址引用)
在函数定义后,如果打印函数而不是调用函数,默认显示的是函数的内存地址
registry=[]
def register(func):
print('running register(%s)'%func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
@register
def f3():
print('running f3()')
def main():
print('running main()')
print('registry->',registry)
f1()
f2()
f3()
if __name__=='__main__':
main()
# 输出
running register(<function f1 at 0x000001E91E360D30>)
running register(<function f2 at 0x000001E91E360CA0>)
running main()
registry-> [<function f1 at 0x000001E91E360D30>, <function f2 at 0x000001E91E360CA0>]
running f1()
running f2()
running f3()
其他参考 – 可以不看
https://www.runoob.com/w3cnote/python-func-decorators.html
https://www.zhihu.com/search?type=content&q=Python%20%E8%A3%85%E9%A5%B0%E5%99%A8
http://c.biancheng.net/view/2270.html
https://www.cnblogs.com/jfdwd/p/11253925.html
修改其他函数的功能
#1
def decorator(fun):
def wrapper(*args,**kwargs):
# some wrapper op
fun(*args,**kwargs)
return xxx
return wrapper
@decorator #没有括号
def fun(*args):
#some fun op
pass
return xxx
#执行
fun(*args)
# @相当于
# fun=decorator(fun)
# fun()
#1-改进写法
from funtools import warps
def decorator(fun):
@warps(fun)
def wrapper(*args,**kwargs):
# some wrapper op
fun(*args,**kwargs)
return xxx
return wrapper
@decorator #没有括号
def fun(*args):
#some fun op
pass
return xxx
#执行
fun(*args)
#2
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated
@decorator_name
def func():
return("Function is running")
can_run = True
print(func())
# Output: Function is running
can_run = False
print(func())
# Output: Function will not run
# output:
# some wrapper op
# some fun op
2.2.3 functools.wraps()/partical()
functools.wraps(func) # return 一个 functools.partical() 函数
functools.partical()
类似于装饰器函数,本质上市固定某些参数(可以使多个参数),然后通过函数调用传递剩下得到参数
from functools import partical
# 例子1
# 原始装饰器
def func(m):
def innerf(n):
return m*n
return innerf
f=func(3)
f(4) # 12
# 是利用innerf
def func(m,n):
return m*n
f=partical(3)
f(4) # 12
# 例子2
# 与类相关
class C(object):
def __init__(self,*args,**kwargs):
self.__a=args[0]
self.__b=args[1]
def add(self):
return self.__a+self.__b
def show(self):
pass
f=partical(C,3)
f2=partical(C,x=10) # x 传入到kwargs中
f.add(4) # 7
functools.wraps()
# 无wraps
import functools
def my_decorator(func):
def wrapper(*args, **kwargs):
'''decorator'''
print('Calling decorated function...')
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""Docstring"""
print('Called example function')
print(example.__name__, example.__doc__) # wrapper decorator
# wraps
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
'''decorator'''
print('Calling decorated function...')
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""Docstring"""
print('Called example function')
print(example.__name__, example.__doc__) # example docstring
2.2.4 functools.lru_cache() ???
2.2.5 functools.singledpatch 单分派函数
其实就是限定传入参数的数据类型
(类似其他语言的重载函数)
注意: 对于分配的函数,值根据传入的第一个参数的类型进行分派,后面是什么类型并不关心;如果分配的函数类型没有与之匹配的,则执行调用最原始的函数
可以叠放多个装饰器,让一个函数支持不同的类型
可以处理自定义类型
(抽象基类、虚拟子类这里不做特殊说明,见后面的note)
from functools import singledispatch
@singledisparch
def func(*agrs,**kwargs):
print('原始',*agrs)
@func.register(int)
def __func(*agrs):
print('int',*args)
@func.register(dict)
@func.register(tuple)
def __func(*agrs):
print('dict/tuple',*args)
import numbers
from collections import abc
@func.register(numbers.Integral) # 注意抽象基类的使用
f(1,2) # int 1 2
f(1,'a') # int 1 a # a是class<int> 类型
f('a',2) # 原始 a b
2.3 函数也是对象
见 ‘python 函数一节’
3. 控制类型
3.1 闭包函数
见 python-函数一章
4. 自动回收机制
4.1 概念
小整数对象池
python解释器为了能更方便的让程序运行效率更高,所以通常将常用的数据,如1,2,3等,范围是[-5,256](注意在某些系统上linux、MAC、windows范围可能存在不同),在python解释器运行开始的阶段,就已经创建好了,只等待被使用,因此在下面的例子中会存在共同的内存地址
a=1
b=1
id(a)==id(b)
a=1243
b=1243
id(a)!=id(b)
大整数对象池
非小整数池,就是大整数对象池,覆盖范围无限,因此不事先创建
字符串驻留机制/Intern机制
python解释器,默认自动默认对普通字符串开启Intern机制,即共用同一份,但是如果字符串中包含空格等特殊字符,则不会共享内存
a='hello'
b='hello'
id(a) == id(b)
a='hello world' # 有空格
b='hello world'
id(a) != id(b)
4.2 垃圾回收/GC垃圾回收
https://patshaughnessy.net/2013/10/24/visualizing-garbage-collection-in-ruby-and-python – 还没看
python 采用的是引用计数机制为主,分代收集机制为辅的策略
某些情况下,Python解释器对内存清理不完全
python中为了能够知道当前这个对象有多少个变量指向它,因此会在每个对象中有一个小小的空间存放 引用计数
a=xxx() # 此时实例对象中有个空间 存储 1,表示有一个变量值向了这个对象
b=a # 空间 1 --> 2
gc系统的触发机制
- 当gc模块的计数模块达到阈值的时候,自动回收垃圾
- 调用
gc.collect()
手动回收垃圾,配合gc.disable()
手动关闭自动回收机制;import gc
引用计数
实时性:一但没有引用,内存就直接释放了,(不像其他机制等到特定时机再释放);处理回收内存的时间分摊到了平时
缺点:内存资源消耗,额外存储计数空间(速度不一定快)
引用计数能够完成大部分的回收机制,但是对 循环引用 没有解决方案
为解决循环引用出现的问题,使用了分代收集的方法
引用计数+1的情况
- 对象被创建
a=1
- 对象被引用
b=a
- 对象作为参数,传入到一个函数中
func(a)
- 对象作为一个元素,存储在容器中
l=[a,a]
注意:在原生python解释器中 和 在IDE(如pycharm)中,可能在创建对象,然后调用sys.getrefcount(obj)
显示的引用计数可能会不同,因为IDE会对Python解释器进行二次优化
引用计数-1的情况
- 对象的别名被显示销毁
del a
- 对象的别名被赋予新的对象,
a=2
- 一个对象离开它的作用域,
func函数的局部变量
- 对象所在的容器被销毁,或从容器中删除对象
循环引用/内存泄漏
a=value
b=value2
a.xxx=b # b引用计数变成2
b.xxx=a # a引用计数变成2
del a # a引用计数变成1
del b # b引用计数变成1
但是上述引用没有课调用的了,因此产生了内存泄漏的原因
分代收集
触发条件:生成的变量个数为n,已经回收的变量个数为m,当n-m>GC阈值的时候,就会触发分代回收的机制
在分代收集机制中,把对象分为三代。一开始,对象在创建的时候,放在第一代,如果在一次一代的垃圾检查中,该对象存活下来,会被放在第二代,同理,在一次二代的垃圾检查中,该对象存活下来,会被放在第三代
import gc
gc.get_count()
gc.get_threshold()
4.3 del
del
关键字删除的是名称(变量(名)),而不是实际的对象
与__del__
有关,其与上述的垃圾回收机制有关
del
只是删除对象的引用,并不是直接调用__del__
方法,之后当(回收机制中,该对象的引用计数为0的时候,才会自动调用__del__
方法)
可以用sys.getrefcount()
测量引用对象的个数
注意:如果自定义实现__del__
方法,对应的实例则无法被Python的循环引用收集器收集,尽量不要自定义__del__
class C(object):
def __init__(self,name):
self.__name=name
def get(self):
return self.__name
def __del__(self):
print('__del__被调用')
a=[1,2,3]
c=C(a)
b=c
del c # 不调用__del__
print('*'*10)
del b # 调用__del__,引用计数为0,所以
4.4 弱引用
弱引用:如果内存中有对该对象的应用,则使用该引用,如果除了弱引用本身外,没有其他对该对象的引用了,弱引用本身也不再引用该对象
对 对象的弱引用不能保证对象存活,当对象的引用只剩下弱引用的时候,对应的内存被销毁,(但是在实际销毁对象之前,即使没有强引用,弱引用也一直能返回该对象)
有时需要引用对象,而不让对象存在的时间超过所需时间
弱引用不会增加对象的引用数量;弱引用不会妨碍所指对象(被引用对象)被当做垃圾回收
弱引用在缓存应用中很有用
如果对象存在,调用引用可以获得对象,否则返回None,见下面的例子
不是所有对象都可以作为弱引用的目标,list、dict、int、tuple实例都不可以作为弱引用的对象
import weakref
import sys
a_set={1,2}
print(sys.getrefcount(a_set)) # 2 (比实际多1)
b_set=a_set
print(sys.getrefcount(a_set)) # 3
wref=weakref.ref(a_set)
print(wref) # <weakref at 0x000002401B9E2720; to 'set' at 0x000002401B895200>
print(sys.getrefcount(a_set)) # 3,没有增加引用计数
print(wref()) # 弱引用是可调用的,返回的是被引用的对象
# a_set={2,3,4} # 删除a_set变量对集合{1,2}的引用
del a_set
# print(sys.getrefcount(a_set)) # 2
print(sys.getrefcount(b_set)) # 2
# print(a_set)
del b_set
# print(sys.getrefcount(a_set)) # 2 重叠了???
# print(sys.getrefcount(b_set)) # 2 # wrong!,b_set没有定义了
print(wref) # <weakref at 0x000002401B9E2720; dead>
print(wref()) # None
5. python编译
解释型语言/编译型语言
编译型语言:通过编译器将代码编译成机器码
编译型语言:在运行时直接将代码解释成机器码
python比较特殊:先变异成一系列的过度指令(字节码bytecode),再由虚拟机转化为机器码
codeobject
所有Python代码,在运行的时候都被编译成了
codeobject
参考https://www.bilibili.com/video/BV12i4y1C7MH/?spm_id_from=333.788&vd_source=7155082256127a432d5ed516a6423e20
def f():
pass
code=f.__code__
print(f.__code__) # <code object xxxx>
# code.co_code
print(code.co_code) # 保存的是该函数的真正字节码,二进制的字节码(也就是机器码)
print(code.co_name) # 函数名字
print(code.co_filename) # 文件名
# position only params
# keywords only params
print(code.co_argcount) # 变量名的数量,除了*args,**kwargs之外的参数的数量
print(code.co_posonlyargcount)
print(code.co_kwonlyargcount)
print(code.co_nlocals) # 函数的局部变量个数(传入的参数,加上局部变量空间下的变量)
print(code.co_varnames) # 函数的局部变量名
print(code.co_names) # 除了 varnames、cellvars、consts等,剩下的都包含在co_names下
# 下面是辅助完成闭包函数的两个函数
print(code.co_cellvars) # variable变量还会在其他作用空间被用到
print(code.co_freevars) # variable变量表示从其他作用空间来的
print(code.co_consts) # 常用的常量值
虚拟机/字节码
frame 为python解释器提供了一个运行代码的环境
可以理解没一个bytecode都是在一个frame中执行的,每一个frame除了在python解释器启动时创建的frame,都可以当做是一个函数调用
建立frame之后,python解释器之后开始一条一条执行字节码(实际上保存的字节码是二进制的数据,需要dis.dis
方法转换成可读的字节码)
字节码
字节码的运行机制,是以栈为核心
frame
出事python解释器或者每一次调用函数会建立一个frame,然后在找个frame的环境下,会一条一条的bytecode,没一条bytecode会有一个C语言的代码与之对应,对于每个frame,python会维护一个stack栈,然后bytecode与找个栈进行交互操作,也会与codeobjet保存的数据进行交互
dis 模块
官方文档https://docs.python.org/3.10/library/dis.html?highlight=dis#module-dis
import dis
def f(a,b):
return a+b
print(dis.dis(f)))
2 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_ADD
6 RETURN_VALUE
LOAD_FAST # 把一个local变量压到栈中
0,1 是栈的索引???
BINARY_ADD # 从栈顶拿出2个数据相加,然后再压入栈
RETURN_VALUE # 返回栈顶数据
编译与变量空间 – 还要细讨论
原则上,python是解释性语言,不需要编译,在运行时才会被翻译成机器语言,每执行一次都要翻译一次
一个例子 https://mp.weixin.qq.com/s?__biz=MzUyOTk2MTcwNg==&mid=2247484016&idx=1&sn=285da9f937cfe1d8d7e581bee06d3569&scene=21#wechat_redirect
python 虽然是解释型语言,但是也有编译过程
对于定义的函数,编译器先将函数解析成一个抽象语法树,然后扫描树上的名字节点,接着被扫描出来的变量名,都会作为局部作用域的变量名存入栈中。在编译期之后,局部作用域内的变量名已经确定了,只是没有赋值,
6. 元类
通常元类很少使用
目的是实现类的控制
通常来讲,自定义的类,其实就是利用
type
方法进行创建
在 元类 定义的时候,可以自定义,如下面的例子
def upper_attr(class_name,class_parents,class_attr):
"""
class_name 接收C
class_parents 接收object
class_attr 接收property_name=value
"""
new_attr={}
for name,value in class_attr.items():
if not name.startwith('__'):
new_attr[name.upper()]=value
# 本质上还是利用type方法进行创建
return type(calss_name,class_parents,new_attr)
class C(object,metaclass=upper_attr):
property_name=value
进阶
class UpperAttrMetaClass(type): # 注意这里继承的是type
# __new__ 是在__init__ 之前被调用的特殊方法
# __new__ 是用来创建对象并返回的方法,(!!!注意这里的对象 既可以是实例对象,也可以是类对象,下面的例子就是创建一个类对象)
# 而__init__是用传入的参数为对象初始化
# 利用__new__创建一个类对象
def __new__(cls,class_name,class_parents,class_attr):
new_attr={}
for name,value in class_attr.items():
if not name.startwith('__'):
new_attr[name.upper()]=value
# 本质上还是利用type方法进行创建
# return type.__new__(cls,class_name,class_parents,new_attr)
return type(calss_name,class_parents,new_attr)
# 这里metaclass=UpperAttrMetaCLas会调用UpperAttrMetaCLas这个类,而调用的结果是返回一个type定义的类对象,从而指向C类
class C(object,metaclass=UpperAttrMetaCLas):
property_name=value
程序设计模式
设计模式:与编程语言无关,是由经验总结的解决某种问题的常用套路(与规范)
鸭子类型
了解即可:https://www.bilibili.com/video/BV1PQ4y1e7ix?spm_id_from=333.337.search-card.all.click&vd_source=7155082256127a432d5ed516a6423e20
即,忽略对象的真正类型,转而关注对象有没有实现所需的方法、签名和语义
python -m???
正则表达式
见 python进阶-正则表达式 一章
python将脚本打包成可执行文件
pip install pyinstaller
# >>> cmd
# workpath 生成可执行文件时 临时文件存储的位置 ./bulid
# distpath 最终可执行文件所在位置 ./bin
pyinstaller file.exe --workpath outputpath --distpath outputpath
不使用conda 创建虚拟环境
首先要有不属于conda内的python环境
https://www.byhy.net/tut/py/etc/venv/
# >>>cmd
python -m venv env_path
# env_path
# pip.exe
# activate.bat 激活虚拟环境
# deactivate.bat 切换base环境
链接base环境下的第三方库
--system-site-packages
???- 修改pyvenv.cfg文件 将include-system-site-packages设置为True
python调用动态链接库
- python语言的官方解释器CPython是用c语言开发的
- 动态链接库,再不同的操作系统中,文件格式不同
https://www.zhihu.com/question/20484931
补充(简单介绍,不做展开)
https://blog.csdn.net/qq_40250056/article/details/118736066
编译型语言编译过程
预处理 # .i文件
编译器 # .s文件
汇编器 # .o文件
链接器 # 连接库文件和其他目标代码
生成可执行文件
库
可执行代码的二进制文件,可以被操作系统载入内存执行,(提高代码的复用性)
静态库:.lib(win);.a(linux)
动态库:.dll(win);.so(linux)
静态库
- 将包含自定义的变量、函数,在编译(笼统的说)阶段,由编译器与链接器将这些集成到可执行文件中(直接集成到.exe文件中)
- 具体来说,是在链接阶段将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件,即为静态链接方式
- 静态库与.o文件格式相似。静态库可以简单看成一组目标文件.o的集合(将很多目标文件经过压缩打包后形成一个文件)
静态库对函数库的链接是放在编译时期完成的
发布时,只需发布exe文件,因为库已经集成到可执行文件中,运行时不在依赖静态文件,即应用运行时与函数库无关,方便移植
静态库集成到可执行文件中,使得exe文件较大,所有有关的目标文件与涉及的函数库合成一个可执行文件造成浪费空间和资源,后续升级库必须重新编译,需要全量更新
步骤
创建静态库项目
添加.h头文件和.cpp文件
生成.lib/或.a文件
动态库
- 动态库在程序编译(汇编后的链接阶段)不会被链接到目标文件(代码中),而是在程序运行时被载入
- 不同的应用程序调用相同的库,内存中只需要一份共享库的实例
减少空间消耗,方便维护,增量更新
实现进程之间的资源共享(共享库)???
CPython
CPython就是一个 可以执行用户编写的python代码的 另一个由C语言写的程序
https://zhuanlan.zhihu.com/p/358690339
python调用动态链接库-具体没细看
ctypes
是python内建的用于调用动态链接库函数的模块
import ctypes
问题解决
‘pycharm debug error:502’
https://blog.csdn.net/hy761722699/article/details/121627916
https://stackoverflow.com/questions/67934395/debugger-not-functioning-in-pycharm
交互式>>>python报错unicodeDecodeError
https://blog.csdn.net/lairongxuan/article/details/107497260
其他模块
collections 模块
- 排列组合
OrderedDict
见字典进阶
counter
用来给可散列对象计数
计数对象.update()
累加计数
计数对象.most_common(n)
返回计数最大的前n个集合
Userdict
–还没看ChainMap
–还没看
functools 模块
timeit 模块
反复运行语句
from timeit import Timer
pathlib 模块
argparse 模块
pickle 模块
logging 模块
gzip 模块
requests 模块
见"web"专栏
pychart 模块
pymysql 模块
hashlib 模块
tarfile / zipfile 模块
bisect 模块
用于二分查找
struct 模块
数据结构相关模块
队列、堆、栈
collections.deque
queue
multiprocessing
sayncio
heapq