Python进阶

68 阅读9分钟

修改 闭包 内使用的外部函数变量 使用nonlocal关键字来完成。

列表、字典等常见对象的线程数据安全,得益于GIL全局解释锁

def foo():
    yield "11"
    yield "22"
    yield "33"

def func():
    yield "A"
    yield "B"
    yield "C"
    # yield foo() #会打印一个生成器对象
    yield from foo() #会打印“11” “22” “33” 然后接着打印“D” “E”
    yield "D"
    yield "E"

for item in func():
    print(item)
from abc import ABC, abstractmethod
class MyAbstractClass(ABC):
    @abstractmethod
    def my_abstract_method(self):
        print('抽象类')

class MyClass(MyAbstractClass):
    def my_abstract_method(self):
        print('自定义类')

obj = MyClass()
obj.my_abstract_method()
class Sample:
    def __getattr__(self, item):
        """
        如果item被访问且不存在,那么这个方法将被调用
        """
        print('hello attr')

    def __setattr__(self, key, value):
        """
        赋值方法
        """
        print('hello attr')
        self.__dict__[key] = value

    def __delattr__(self, item):
        """
        如果要删除item的话,用这个方法
        """
        print('del attr')

s = Sample()
s.name = 'easy' #直接调用__setattr__
from numba import jit
@jit
def f(x, y):
    return x + y

这段代码的计算 在被调用是第一次执行,numba将在调用期间推断参数类型,然后基于这个信息生成 优化后的代码。 numba也能够基于 输入的类型 编译生成 特定的代码。上面f函数传入整数和浮点数 作为参数 将会生成不同的代码。

class MyClass:
    """
    __slots__是一个特殊的类属性,用于限制实例能添加的属性;
    使用slots可以提高对象的内存效率,因为它会为 对象的属性 创建一个 固定集合,
    而【不是】使用 默认的字典 来存储属性;

    节省内存的同时,也防止添加新的属性;
    使用了__slots__之后,python对象不能动态的添加任意属性;
    只能使用__slots__中定义的属性;

    继承方面:子类的__slots__会覆盖父类的__slots__。如果涉及到多个父类的__slots__;
            则子类的__slots__应该是所有父类的__slots__的并集。

    内置类型:不能定义__slots__;

    元组:__slots__可以是一个字符串或字符串列表,如果定义为单个字符串,它将作为单个槽的名字
    """
    __slots__ = ['name', 'age']  # 定义允许的属性

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

浅copy和深deepcopy:针对可变类型来说,即List数组默认可变

对list进行浅copy,新对象new会开辟第一层内存空间用来存储old对象的第一层数据,new和old对象共享第二层数据内存空间。此时new和old的第一层数据是相互独立的,修改互不影响,而第二层数据一方修改,另一方读取的数据即是被修改过后的新数据。

对list进行深deepcopy,会开辟所有层的内存空间存储old对象的所有层数据。

不可变类型进行深copy,如果子对象没有可变类型,则不会进行拷贝,而只是copy了这个对象的引用。而只要包含有可变类型,则就会开辟新空间copy,是对这整个不可变类型进行包括自身和所有自层的新内存空间的存储。

s = [i%2 for i in range(10)] #数组

t = (i%2 for i in range(10))
"""t是一个生成器对象
<generator object <genexpr> at 0x100839be0>
"""
def num():
    """
    这种现象的原因是因为 lambda 函数中的 i 是一个自由变量,
    它在闭包中捕获了循环结束时的值; 即捕获的是【最后一次】迭代的值
    """
    return [lambda x:i*x for i in range(4)]

result = [m(2) for m in num()]
# print(result)
#[6, 6, 6, 6]

模块中的__all__:如果一个模块文件中有all变量,当使用from xxx import * 导入时,只能导入这个列表中的元素。

img = cv2.imread("morningsun.png")
#图像平移
# 高,宽 
rows, clos = img.shape[:2]  #numpy的操作
M = np.float32([ [1,0,100], [0,1,50] ])
# 对图像进行 仿射变换,
# 第三个参数形式(宽,高)这是因为内部是cv的操作
dst = cv2.warpAffine(img, M, (clos, rows)) 
cv2.imshow("Translated Image",dst)

yield关键字:

· 代码执行到yield会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置 继续往下执行。

· 生成器如果把数据生成完成,再次获取生成器中的下一个数据会抛出一个StopIteration异常,表示停止迭代异常。

· while循环内部 没有处理 异常操作,需要手动添加处理异常操作。

· for循环内部自动处理了 停止迭代异常,

def extend_list(val, data=[]):
    print("参数val:",val,"data内存地址:",id(data))
    data.append(val)
    return data

list1 = extend_list(10)
# 参数val: 10 data内存地址: 4446028416
list2 = extend_list(123,[])
# 参数val: 123 data内存地址: 4446011136
list3 = extend_list("abc")
# 参数val: abc data内存地址: 4446028416

print(list1,"\n",list2,"\n",list3)
# [10, 'abc'] 
# [123] 
# [10, 'abc']

Numpy:

Numpy底层使用c语言编写,内部解除了GIL全局解释器锁,其对数组的操作速度 不受Python解释器的限制,所以效率远高于纯Python代码。

Numpy提供了一个N维数组类型ndarray,它描述了相同类型的items集合。

生成numpy对象:np.array()。

ndarray的优势:

内存块风格:

· 原生list 是 分离式存储,存储元素类型是任意的,只能通过 寻址方式 寻找下一个元素

· ndarray:一体式存储,存储元素类型必须是相同。存储数据和数据地址都是连续的,这使得批量操作数组元素时numpy速度更快

· ndarray支持并行化元算(向量化运算),numpy内置了并行运算功能,当系统有多个核心时,会自动做并行计算。

· 底层是用C语言写的,效果高,释放了GIL。

Numpy数组矢量计算-广播机制:

广播会在 缺失和 或长度为1的维度上进行。

广播机制 需要 扩展维度小 的数组,使得它与维度最大的数组的shape值相同,以便运算。

"""正态分布"""
np.random.randn()
# 三个参数分别是: 均值:0; 方差:1; 生成随机数的数量:100
np.random.normal(0,1,100)

"""均匀分布"""
np.random.rand()
np.random.uniform(0,1,100)
np.random.randint(0,10,size=(3,5))
import numpy as np
x = np.random.randint(0,8,(6,))
# y = x.reshape(2, -1) #产生新的ndarray
# x.resize(2,3) # 修改的是原来的ndarray
x = y.T #行列互换
# numpy操作顺序:(行,列)即(高,宽)也即(y,x)
rows, clos = img.shape[:2]
# cv操作:(列,行)即(宽,高),也即(x,y)
cv2.resize(img, (2*clos, 2*rows), interpolation=cv2.INTER_CUBIC)
# 判断是否存在 缺失值
ishasnull = np.all(pd.notnull(df))
#如果存在
df.replace(to_replace="", value=np.nan)
#如果缺失值,且是np.nan的形式
# 使用统计学指标值(mean、median等)对缺失值进行填充
df['column_name'].fillna(df['column_name'].mean(), inplace=True)
mutex = threading.Lock()
# 上锁
mutex.acquire()

#coding here

#释放锁
mutex.release()
mmap是一种虚拟内存映射文件的方法,
即将一个文件或者其它对象映射到进程的地址空间,
实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一映射关系。
它省掉了内核态和用户态页copy这个动作(两态间copy),
直接将用户态的虚拟地址与内核态空间进行映射,进程直接读取内核空间,
速度提高了,内存占用也少了。

常规文件操作为了提高读写效率和保护磁盘,使用了【页缓存机制】。
这样造成读文件时需要先将【文件页从【磁盘拷贝到页缓存】中,
由于【页缓存处在内核空间】,不能被用户进程直接寻址,
所以还需要将页缓存中数据页【再次拷贝】到 【内存对应的用户空间】中。
这样,通过了【两次数据拷贝】过程,才能完成进程对文件内容的获取任务。
写操作也是一样,待写入的buffer在内核空间不能直接访问,
必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),
也是需要两次数据拷贝

使用mmap操作文件中,创建【新的虚拟内存区域】和【建立文件磁盘地址和虚拟内存区域映射】这两步,
没有任何文件拷贝操作;

总而言之,
⚠️常规文件操作需要从【磁盘】到【页缓存】再到【用户主存】的⚠️【两次数据拷贝】。
而⚠️mmap操控文件,⚠️【只需要】从【磁盘到用户主存】的【一次数据拷贝】过程。
说白了,mmap的关键点是实现了⚠️【用户空间和内核空间的数据直接交互】
而省去了空间不同数据不通的繁琐过程
""
objecttypePython中的两个源对象,它们互相依赖对方来定义
type为对象的顶点,所有对象都创建自typeobject为类继承的顶点,所有类都继承自object。

元类、装饰器、类装饰器都可以归为元编程。
"""
"""
isinstance: 会考虑继承关系,
type(x) : 仅考虑当前类,不会考虑继承关系
"""

class A(object):
    pass
class B(A):
    pass
a = A()
b = B()

print(isinstance(a, A))  # True
print(isinstance(b, A)) # True b是A子类的实例对象
print(type(a) == A) #True
print(type(b) == A) #False
str = "hello world"
print(str.title()) #Hello World
import threading:导入 Python 中的 threading 模块,用于实现多线程编程。

t = threading.Thread(target=task):创建一个名为 t 的线程对象,指定线程的目标函数为 task。这意味着线程 t 将执行 task 函数中定义的任务。

t.start():启动线程 t,线程开始执行 task 函数中的任务。线程的执行是并发的,不会阻塞主线程的执行。

t.join():主线程调用 t.join() 方法,这会使主线程等待线程 t 执行完毕后再继续执行后续代码。相当于同步-阻塞当前线程。
#猴子补丁
class A(object):
    def speak(self):
        print("hello")
        return "hello"

    def __init__(self, array):
        self._list = array

"""
创建一个class类的本质:
class = type(classname,  superclasses, attributeddict)
A = type(A,object,{_list:[]}

type(...) 是 type 的__call__运算符重载,它会进一步调用:
    type.__new__(typeclass, classname, superclasses, attribueddict)
    type.__init__(class, classname, superclasses, attributeddict)
"""

mycls = type('A', (object,), {"_list":[1]})
a = mycls()

print("type类创建:",A([1]), A)
print("type类创建:",a, mycls)
#打印结果
type类创建: <__main__.A object at 0x124517c20> <class '__main__.A'>
type类创建: <__main__.A object at 0x124516a80> <class '__main__.A'>
def speak_patch(self):
    print("猴子补丁:world")
    return 'world'

def length(obj):
    print("猴子补丁:length")
    return len(obj._list)

a = A([])
a.speak() # hello
A.speak = speak_patch
a.speak() # 猴子补丁:world

A.__len__ = length
a = A([1,2,3])
print(len(a)) #本质是调用a.__len__
# 猴子补丁:length
# 3