python yield,yield from,深浅拷贝

(一)yield和yield from

转自:理解yield   yield from

(1)yield

1、通常的for…in…循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。它可以是mylist = [1, 2, 3],也可以是mylist = [x*x for x in range(3)]。 它的缺陷是所有数据都在内存中,如果有海量数据的话将会非常耗内存。

2、对比可迭代对象,迭代器其实就只是多了一个函数:__next__(),可以不再使用for循环来间断获取元素值,而可以直接使用next()方法来实现,可通过iter(a),将可迭代对象a转换为一个迭代器。

3、生成器是可以迭代的,但只可以读取它一次。因为用的时候才生成。比如 mygenerator = (x*x for x in range(3)),注意这里用到了(),它就不是数组,而上面的例子是[]。可迭代对象和迭代器,是将所有的值都生成存放在内存中,而生成器则是需要元素才临时生成,节省时间,节省空间。

4、带有 yield 的函数不再是一个普通函数,而是一个生成器generator,可用于迭代。yield 是一个类似 return 的关键字,迭代一次遇到yield时就返回yield后面的值。并在这里阻塞,等待下一次的调用。从而实现节省内存,实现异步编程。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码开始执行。即yield就是 return 返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后开始。

5、生成器并不是一次生成所有元素,而是一次一次的执行返回,激活生成器执行的方法有:使用next(),或者使用generator.send(None)。next()等同于send(None),for循环就用到了next()

6、带有yield的函数不仅仅只用于for循环中,而且可用于某个函数的参数,只要这个函数的参数允许迭代参数。比如array.extend函数,它的原型是array.extend(iterable)。

7、send(msg)与next()的区别在于send可以传递参数给yield表达式,这时传递的参数会作为yield表达式的值,而yield的参数是返回给调用者的值。换句话说,就是send可以强行修改上一个yield表达式值。比如函数中有一个yield赋值,a = yield 5,第一次迭代到这里会返回5,a还没有赋值。第二次迭代时,使用.send(10),那么,就是强行修改yield 5表达式的值为10,本来是5的,那么a=10。

8、send(msg)与next()都有返回值,它们的返回值是当前迭代遇到yield时,yield后面表达式的值,其实就是当前迭代中yield后面的参数。

9、第一次调用时必须先next()或send(None),否则会报错,send后之所以为None是因为这时候没有上一个yield。可以认为,next()等同于send(None),for循环就用到了next()。

(2)yield from

多线程与协程:使用多线程实现并发时,多线程的运行需要频繁的加锁解锁,切换线程,这极大地降低了并发性能;协程是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序。协程和线程,有相似点,多个协程之间和线程一样,只会交叉串行执行;也有不同点,线程之间要频繁进行切换,加锁解锁,复杂度高且效率低。协程通过使用 yield 暂停生成器,可以将程序的执行流程交给其他的子程序,从而实现不同子程序的之间的交替执行,即程序从yield处暂停,然后可以返回去做别的事。

yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器

yield from后面加上可迭代对象,可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰(这种情况下使用yield需要两个循环,而yield from只需要一个循环)。

当 yield from 后面加上一个生成器后,就实现了生成的嵌套。实现生成器的嵌套,并不是一定必须要使用yield from,但使用yield from可以避免自己处理各种料想不到的异常。生成器嵌套的几个概念:

1、调用方:调用委托生成器的客户端(调用方)代码

2、委托生成器:包含yield from表达式的生成器函数

3、子生成器:yield from后面加的生成器函数

这几个概念的示例如下:

# 子生成器
def average_gen():
    total = 0
    count = 0
    average = 0
    while True:
        new_num = yield average
        count += 1
        total += new_num
        average = total/count

# 委托生成器
def proxy_gen():
    while True:
        yield from average_gen()

# 调用方
def main():
    calc_average = proxy_gen()
    next(calc_average)            # 预激下生成器
    print(calc_average.send(10))  # 打印:10.0
    print(calc_average.send(20))  # 打印:15.0
    print(calc_average.send(30))  # 打印:20.0

if __name__ == ‘__main__‘:
    main()

委托生成器的作用是:在调用方与子生成器之间建立一个双向通道即调用方可以通过send()直接发送消息给子生成器,而子生成器yield的值,也是直接返回给调用方。委托生成器只起一个桥梁作用,它建立的是一个双向通道,它并不会对子生成器yield回来的内容做拦截。如下:

# 子生成器
def average_gen():
    total = 0
    count = 0
    average = 0
    while True:
        new_num = yield average
        if new_num is None:
            break
        count += 1
        total += new_num
        average = total/count

    # 每一次return,都意味着当前协程结束。
    return total,count,average

# 委托生成器
def proxy_gen():
    while True:
        # 只有子生成器要结束(return)了,yield from左边的变量才会被赋值,后面的代码才会执行。
        total, count, average = yield from average_gen()
        print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))

# 调用方
def main():
    calc_average = proxy_gen()
    next(calc_average)            # 预激协程
    print(calc_average.send(10))  # 打印:10.0
    print(calc_average.send(20))  # 打印:15.0
    print(calc_average.send(30))  # 打印:20.0
    calc_average.send(None)      # 结束协程
    # 如果此处再调用calc_average.send(10),由于上一协程已经结束,将重开一协程

if __name__ == ‘__main__‘:
    main()

程序输出:
10.0
15.0
20.0
计算完毕!!
总共传入 3 个数值, 总和:60,平均数:20.0

yield from的好处是其做了很多全面的异常处理,使得调用端可以直接使用而不用自己实现多种异常的处理。

(二)深浅拷贝

转自 Python拷贝    深浅拷贝

(1)=赋值:数据完全共享(=赋值是在内存中指向同一个对象,如果是可变(mutable)类型,比如列表,修改其中一个,另一个必定改变

如果是不可变类型(immutable),比如字符串,修改了其中一个,另一个并不会变

l2 = l1 ,l1 完全赋值给l2 ,l2的内存地址与l1 相同,即内存完全指向

l1 = [1, 2, 3, [‘aa‘, ‘bb‘]]
l2 = l1
l2[0]=‘aaa‘
l2[3][0]=‘bbb‘
print(l1)  #[‘aaa‘, 2, 3, [‘bbb‘, ‘bb‘]]
print(id(l1)==id(l2))  #True

(2)浅拷贝:数据半共享(复制其数据独立内存存放,但是只拷贝成功第一层)没有拷贝子对象,所以原始数据改变,子对象会改变

l1 = [1,2,3,[11,22,33]]
l2 = l1.copy()
print(l2) #[1,2,3,[11,22,33]]
l2[3][2]=‘aaa‘
print(l1) #[1, 2, 3, [11, 22, ‘aaa‘]]
print(l2) #[1, 2, 3, [11, 22, ‘aaa‘]]
l1[0]= 0
print(l1) #[0, 2, 3, [11, 22, ‘aaa‘]]
print(l2) #[1, 2, 3, [11, 22, ‘aaa‘]]
print(id(l1)==id(l2)) #Flase

l2浅拷贝了l1 ,之后l2把其列表中的列表的元素给修改,从结果看出,l1也被修改了。但是仅仅修改l1列表中的第一层元素,却并没有影响l2。

比较一下l2与l1的内存地址:False,说明,l2在内存中已经独立出一部分复制了l1的数据,但是只是浅拷贝,第二层的数据并没有拷贝成功,而是指向了l1中的第二层数据的内存地址,所以共享内存‘相当于‘’等号赋值’‘,所以就会有l2中第二层数据发生变化,l1中第二层数据也发生变化

l2拷贝l1的时候只拷贝了他的第一层,也就是在其他内存中重新创建了l1的第一层数据,但是l2无法拷贝l1的第二层数据,也就是列表中的列表,所以他就只能指向l1中的第二层数据

由此,当修改l1中第二层数据的时候,浅拷贝l1的l2中的第二层数据也随之发生改变

(3)深拷贝:数据完全不共享(复制其数据完完全全放独立的一个内存,完全拷贝,数据不共享)

深拷贝就是完完全全复制了一份,且数据不会互相影响,因为内存不共享。

深拷贝的方法有:导入模块

import copy
l1 = [1, 2, 3, [11, 22, 33]]
# l2 = copy.copy(l1)  浅拷贝
l2 = copy.deepcopy(l1)
print(l1,‘>>>‘,l2)
l2[3][0] = 1111
print(l1,">>>",l2)

深拷贝就是数据完完全全独立拷贝出来一份,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变

 

原文地址:https://www.cnblogs.com/ccxikka/p/10658200.html

时间: 2024-11-05 22:47:25

python yield,yield from,深浅拷贝的相关文章

python变量存储和深浅拷贝

python的变量及其存储 在高级语言中,变量是对内存及其地址的抽象.对于python而言,python的一切变量都是对象,变量的存储,采用了引用语义的方式,存储的只是一个变量的值所在的内存地址,而不是这个变量的值本身. 引用语义:在python中,变量保存的是对象(值)的引用,我们称为引用语义.采用这种方式,变量所需的存储空间大小一致,因为变量只是保存了一个引用.也被称为对象语义和指针语义. 值语义:有些语言采用的不是这种方式,它们把变量的值直接保存在变量的存储区里,这种方式被我们称为值语义,

python基础三(深浅拷贝)

1.赋值操作 1 list_1 = [1,2,3,['barry','Jerry']] 2 list_2 = list_1 3 list_1[0] = 111 4 print(list_1) # [111, 2, 3, ['barry', 'Jerry']] 5 print(list_2) # [111, 2, 3, ['barry', 'Jerry']] 对于赋值运算操作.list[1]与list[2]指向的是同一个内存地址.所以完全一样 2.浅拷贝 1 # -------------浅拷贝-

Python中列表的深浅拷贝

copy_lst = [ ('py对象三要素',), ('== 比较运算符',), ('is 身份运算符',), ('小数据池',), ('列表的浅拷贝',), ('列表的深拷贝',), ] py对象三要素 id type value == 比较运算符 通过value进行判断 >>>a = 257 >>>b = 257 >>> a == b True is 身份运算符 通过id进行判断 >>>a = 257 >>>b

Python列表操作与深浅拷贝(7)——列表深浅拷贝、删除、反转、排序

列表复制 浅拷贝:简单类型元素全复制,引用类型元素只复制引用 L1 = [3,2,1,[4,5,6],8,'abc'] L1 [3, 2, 1, [4, 5, 6], 8, 'abc'] L2 = L1.copy() L2 [3, 2, 1, [4, 5, 6], 8, 'abc'] L1[3][1] = 10 #修改L1中的元素L2也随之变化 L1 [3, 2, 1, [4, 10, 6], 8, 'abc'] L2 [3, 2, 1, [4, 10, 6], 8, 'abc'] 深拷贝:co

Python 深浅拷贝 (Shallow copy and Deep copy in Python)

前言 昨天刷公众号看到一篇描述py优雅语法的文章,心痒之下到家就开始尝试,学习了for else statement,yield和py版三目写法.在列表切片这部分中,对作者的列表拷贝写法,有些不太理解. # 拷贝 copy_items = items[::] 或者 items[:] 尝试 首先开一个python,随便建一个列表l=[1,2,3]将其进行两种方法的拷贝: 我的写法 c=l 作者的写法 d=l[:] 分别打印了c和d,并没有什么差别,仔细斟酌了一下作者的用意,觉得应该有一些深层次的考

python学习笔记4:基础(集合,collection系列,深浅拷贝)

转载至:http://www.cnblogs.com/liu-yao/p/5146505.html 一.集合 1.集合(set): 把不同的元素组成一起形成集合,是python基本的数据类型.集合元素(set elements):组成集合的成员 python的set和其他语言类似, 是一个无序不重复元素集, 基本功能包括关系测试和消除重复元素. 集合对象还支持union(联合), intersection(交), difference(差)和sysmmetric difference(对称差集)

Python深浅拷贝

深浅拷贝 深浅拷贝分为两部分,一部分是数字和字符串另一部分是列表.元组.字典等其他数据类型. 数字和字符串 对于数字和字符串而言,赋值.浅拷贝和深拷贝无意义,因为他们的值永远都会指向同一个内存地址. # 导入copy模块>>> import copy# 定义一个变量var1>>> var1 = 123# 输出var1的内存地址>>> id(var1)1347747440>>> var2 = var1# var2的内存地址和var1相同

Python 从零学起(纯基础) 笔记 之 深浅拷贝

深浅拷贝 1. import  copy#浅拷贝copy.copy()#深拷贝copy.deepcopy()#赋值 = 2.   对于数字和字符串而言,赋值.浅拷贝和深拷贝无意义,因为其永远指向同一个内存地址. 对于 字典.元组.列表 而言,进行赋值.浅拷贝和深拷贝时,其内存地址的变化是不同的. 浅拷贝,在内存中只额外创建第一层数据. 深拷贝,在内存中将所有的数据重新创建一份(排除最后一层,即:Python内部对字符串和数字的优化)   1 import copy 2 n1 = {"k1&quo

3.python基础补充(集合,collection系列,深浅拷贝)

一.集合 1.集合(set): 把不同的元素组成一起形成集合,是python基本的数据类型.集合元素(set elements):组成集合的成员 python的set和其他语言类似, 是一个无序不重复元素集, 基本功能包括关系测试和消除重复元素. 集合对象还支持union(联合), intersection(交), difference(差)和sysmmetric difference(对称差集)等数学运算. sets 支持 x in set, len(set),和 for x in set.作