有关python知识点学习

  1. *args 和 **kwargs
  2. 装饰器和闭包
  3. 垃圾回收机制
  4. 赋值、深拷贝、浅拷贝

*args和**kwargs

*和**在python中主要有三方面的用途

  • 对可迭代对象进行拆分
  • 可变变量的赋值
  • 函数的可选参数标志

对可迭代对象进行拆分

典型的可迭代对象包括元组(tuple)、列表(list)、集合(set)、字典(dict)等等

print("a", "b", "c")
print(*("a", "b", "c"))    #元组
print(*["a", "b", "c"])    #列表
print(*{"a", "b", "c"})    #集合
print(*{"a": 1, "b": 2, "c": 3})    #注意:对字典拆解时只拆解key
print(*"abc")

可变变量的赋值

对于一个可迭代对象列表,如果想要将其中的元素赋值给变量。写法如下:

a, *b, c = {1, 2, 3, 4, 5, 6}

 函数的可选参数标志

如果是单星号*标记就是可选的位置参数(positional arguments), 如果是双星号**标记就是可选的关键词参数(keyword arguments), 如:

def func(a, *args, **kwargs):
    print(a)
    print(args)
    print(kwargs)

func(1, 2, 3, c = 4, d = 3)

kwargs.get()方法和kwargs.pop()方法:

kwargs.get("denominator", 1)会从kwargs这个字典中读取"denominator"这个key对应的值,如果没有这个key,就会返回1。这样写可以避免不断地写if else语句。

kwargs.pop()与kwargs.get()不同的地方在于,kwargs.pop()读取某个key对应的值后,kwargs这个字典中的这个键值对就会被删除。
参考:【Python】函数中的*args和**kwargs是什么?

arg是argument的简称, *args用来发送一个非键值对的可变数量的参数列表给一个函数。倘若确定传入一个参数,可以将参数单独写出来。但是不确定需要传入多少个参数,可以使用*args占位代替,即使传入非常多的参数,也可以逐一显示。

**kwargs允许将不定长度的键值对,作为参数传递给函数。如果想要在一个函数里面处理带名字的参数,需要使用**kwargs。

在一个函数中,普通的参数与*args、**kwargs在使用时的顺序。如果想要在函数中同时使用所有的三种参数,顺序如下:(必选参数、默认参数、*args, **kwargs)

func(normal_args, *args, **kwargs)

Python的装饰器与闭包

闭包是Python装饰器的基础。

要理解闭包,需要先了解Python中的变量作用域规则

变量作用域规则

  • 函数能访问全局变量
  • 在一个嵌套函数中,内层函数能够访问在外层函数中定义的局部变量

闭包

嵌套函数就是闭包。闭包是指延伸了作用域的函数,在其中能够访问未在函数定义体中定义的非全局变量。未在函数定义体中定义的非全局变量一般都是在嵌套函数中出现的。

当Python函数中的全局变量或者自由变量是不可变对象(数字、字符串、元组等)时,是只能读取,无法更新的。

装饰器

一、什么是装饰器?

装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。装饰器的使用符合面向对象编程的开放封闭原则。

开放封闭原则主要体现在两个方面:

  • 对扩展开放,意味着有新的需求或者变化时,可以对现有代码进行扩展,以适应新的情况。
  • 对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类进行任何修改

二、为什么要用装饰器?

python里万物皆是对象,万物皆可传参。函数也可以作为函数的参数进行传递

装饰器是可调用对象,

def func1():
    print("我是子函数1")

def func2(name):
    print("子函数2开始")
    name()
    print("子函数2结束")

if __name__ == "__main__":
    func = func1
    func()
    print("-" * 20)
    func2(func)

对于两个函数的执行时间分别是多少,需要分别对两个函数进行改写。

import time

def func1():
    print("我是子函数1")
    time.sleep(2)

def count_time(func):
    def wrapper():
        t1 = time.time()
        func()
        print("执行时间为:" , time.time() - t1)

    return wrapper

if __name__ == "__main__":

    func1 = count_time(func1)
    func1() # 执行func1就相当于执行wrapper()

这里的count_time是一个装饰器,装饰器函数里面定义一个wrapper函数, 把func这个函数当作参数传入,函数实现的功能是把func包裹起来,并且返回wrapper函数。wrapper函数体就是要实现装饰器的内容。 

三、装饰器的语法糖@

使用装饰器的是,默认转入的参数就是被装饰的函数。

import time

def count_time(func):
    def wrapper():
        t1 = time.time()
        func()
        print("执行时间为:", time.time() - t1)

    return wrapper

@count_time
def func1():
    print("我是子函数1")
    time.sleep(2)

if __name__ == "__main__":
    func1() #用语法糖之后,就可以直接调用该函数

四、带参数的装饰器

装饰器函数也是函数,函数可以进行参数传递,写一个带参数的装饰器

import time

def count_time_args(msg = None):
    def count_time(func):
        def wrapper(*args, **kwargs):
            t1 = time.time()
            func(*args, **kwargs)
            print("{}执行时间为:".format(msg), time.time() - t1)

        return wrapper

    return count_time

@count_time_args(msg = "jc1")
def fun_one():
    time.sleep(1)

@count_time_args(msg = "jc2")
def fun_two():
    time.sleep(1)

@count_time_args(msg = "jc3")
def fun_three():
    time.sleep(1)
    

if __name__ == "__main__":
    fun_one()
    fun_two()
    fun_three()

五、类装饰器

在python中,可以用类来实现装饰器的功能,称之为类装饰器。类装饰器的实现调用类里面的__call__函数。类装饰器的写法比装饰器函数的写法更为简单。

当将类作为一个装饰器,工作流程:

  • 通过__init__()方法初始化类
  • 通过__call__()方法调用真正的装饰方法
import time

class Decorator:
    def __init__(self, func):
        self.func = func
        print("执行类的__init__方法")

    def __call__(self, *args, **kwargs):
        print("进入__call__函数")
        t1 = time.time()
        self.func(*args, **kwargs)
        print("执行时间为:", time.time() - t1)

@Decorator
def jc():
    print("我是jc函数")
    time.sleep(2)

def jc2():
    print("我是jc2函数")
    time.sleep(1)

@Decorator
def func(name):
    print("进入func函数")
    name()
    print("func函数结束")


if __name__ == "__main__":
    jc()
    print("-" * 20)
    func(jc2)

一个函数可以被多个装饰器进行修饰,在装饰器修饰的函数,在执行的时候先执行原函数的功能,然后再由里向外依次执行装饰器的内容。

重点【Python】一文弄懂python装饰器(附源码例子)

参考:

Python装饰器与闭包

python 闭包和装饰器详解

写一个装饰器计算函数的运行时间代码

参考:

装饰器实现计算函数的运行时间

python-装饰器实现函数的运行时间 - 知乎 (zhihu.com)

import time

def time_it(func):
    def inner():
        start = time.time()
        func()
        end = time.time()
        print(end - start)
    return inner

@time_it
def func():
    time.sleep(2)
    print("func has sleeping 2 s")

if __name__ == '__main__':
    func()

Python垃圾回收(GC)

python垃圾回收机制(GC)

  • 引用计数(主要)【缺点:维护性高、循环引用不能解决】
  • 标记-清除
  • 分代回收【垃圾回收 = 垃圾检测 + 释放】

python中sorted函数对字典按key排序和按value排序

python的sorted函数对字典按key排序和按value排序

sorted函数, sorted(iterable, key, reverse), sorted一共有iterable, key, reverse三个参数。

赋值、浅拷贝、深拷贝

Python中的赋值(复制)、浅拷贝与深拷贝 - 知乎 (zhihu.com)

  • 变量:是一个系统表的元素,拥有指向对象的连接空间
  • 对象:被分配的一块内存,存储其所代表的值
  • 引用:是自动形成的从变量到对象的指针
  • 类型:属于对象,而非变量
  • 不可变对象:一旦创建就不可修改的对象,包括字符串、元组、数值类型
  • 可变对象:可以修改的对象,包括列表、字典、集合

(不可变对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。)

(可变对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。)

赋值:只是复制了新对象的引用,不会开辟新的内存空间

并不会产生一个独立的对象单独存在,只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。

浅拷贝: 切片操作,工厂函数,copy模块中的copy函数。

如: lst = [1,2,[3,4]]

切片操作:lst1 = lst[:] 或者 lst1 = [each for each in lst]

工厂函数:lst1 = list(lst)

copy函数:lst1 = copy.copy(lst)

浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已,在lst中有一个嵌套的list[3,4],如果我们修改了它,情况就不一样了。

浅复制要分两种情况进行讨论:

1)当浅复制的值是不可变对象(字符串、元组、数值类型)时和“赋值”的情况一样,对象的id值(id()函数用于获取对象的内存地址)与浅复制原来的值相同。

2)当浅复制的值是可变对象(列表、字典、集合)时会产生一个“不是那么独立的对象”存在。有两种情况:

第一种情况:复制的对象中无复杂子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。

第二种情况:复制的对象中有复杂子对象(例如列表中的一个子元素是一个列表),如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。 但是改变原来的值中的复杂子对象的值会影响浅复制的值。

深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。

所以改变原有被复制对象不会对已经复制出来的新对象产生影响。

只有一种形式,copy模块中的deepcopy函数

深浅拷贝对比

不可变对象类型,没有被拷贝的说法,即便是用深拷贝,查看id的话也是一样的,如果对其重新赋值,也只是新创建一个对象,替换掉旧的而已。

一句话就是,不可变类型,不管是深拷贝还是浅拷贝,地址值和拷贝后的值都是一样的。

  • 赋值: 值相等,地址相等
  • copy浅拷贝:值相等,地址不相等
  • deepcopy深拷贝:值相等,地址不相等

python集合元素可以是列表吗

python中的不可变类型:Numble(数字),Tuple(元组), String(字符串)

python中的可变类型:List 列表, Dict 字典, Set 集合

集合的元素不可以是列表,只能固定数据类型, 数值、字符串、元组。

Pytorch并行计算

Pytorch多进程并行计算

DistributedDataParallel(DDP):多进程并行计算,支持多节点分布式计算

DataParallel(DP):单进程多线程计算

DDP通常性能也更好,因为它不仅规避了Python多线程的全局解释器锁争用(GIL contention)造成的性能开销,而且还不需要在多GPU训练中频繁复制同步模型、分发输入数据和收集模型输出。

分布式训练:同步训练、异步训练。同步训练利用AllReduce来整合不同worker计算gradient, 异步训练则是基于参数服务器架构。

AllReduce是一类算法,将不同机器中的数据整合之后再将结果分发给各个机器。优化网络带宽的占用或者延迟。AllReduce分为主从式架构、环形架构、全连接架构。

功能上:

  1. DDP的原理是多进程,因此DDP支持多机多卡的分布式计算,而DP是但经常多线程,因此最高只支持单机多卡;
  2. DDP支持模型并行(model parallel),可以把一个模型拆成几个阶段来跑,而DP还不支持。

性能上:

  1. 正是因为DDP基于多进程(通常推荐1个GPU匹配一个工作进程),所以不像DP那样基于单进程多线程的并行性能受到GIL争用开销的阻碍。
  2. 在单机多卡的情况下,DP需要在训练中频繁在多卡之间复制模型以完成同步,需要分发(scatter)输入和收集(gather)输出,而DDP采用的All-Reduce算法采取聚合通信(collective communication)的方式收集梯度,其性能更好。

launch 使用 multiprocessing.spawn 创建 nprocs个新进程, 每个进程都执行worker函数。

初始化进程组 - DDP包装模型 - 准备数据 - 进行训练 - 销毁进程组

进程组后端负责进程组聚合通信 Pytorch 分布式训练GPU使用NCCL, CPU使用Cloo

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值