背景介绍在学习Python的初期,对容器,迭代器,生产器等概念一直没有去梳理清楚,所以也就是一直糊里糊涂的用着Python。最近,用的多了,总是需要看一些源码文件,想通过源码文件来了解自己写的代码性能瓶颈会在哪里?此时,发现这些概念,我似乎需要仔细的梳理一下。梳理的过程中,会参阅一些参考文献,毕竟站在前人的肩膀上会看的更远嘛,在文末,会一并附上参看文献。 contrainer 容器出自官方文档的一句定义:Some objects contain references to other objects; these are called containers. 容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in, not in关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特例,并不是所有的元素都放在内存,比如迭代器和生成器对象)那么在Python中,常见的容器有哪些?
以上常用的容器,各自都有自己的数据特点。这里本不应该一一阐述,但多数使用者都应该有所了解。 可修改与否?我们常用的容器,如:列表,字典是可以直接修改的,即从地址上修改存储的内容; 有可以修改,就有不可以修改,不然就没有必要区别分了。不可修改的有元组tuple,类似的不可变数据类型包括整型int、浮点型float、字符串型string。 当然不可变的容器和数据类型在我理解并不是真的不可变,如果你要修改,就是改变指针指向位置,将指针指向新的内容位置,那么原始内容是不变的,随着指针的移动,就便成了废弃的了,被程序清楚。 传址与传址的概念传值是指传入一个参数的值,传址是指传入一个参数的地址,也就是内存的地址(指针)。二者的区别是如果函数里面对传入的参数重新赋值,函数外的全局变量是否相应改变,用传值传入的参数不会改变的,用传址传入就会改变。 Python不允许程序员显性的传值还是传址操作。Python参数传递采用的是“传对象引用”的方式。这种方式也相当于传值和传址的综合形式。当函数收到的是一个可变对象(比如字典或者列表)的引用(指针),就能修改对象的原始值——相当于传址。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象——相当于传值。 所以python的传值和传址是根据传入参数的类型来选择的 传值的参数类型:数字,字符串,元组 传址的参数类型:列表,字典 所以在写程序时候一定要注意这一点,如果不小心就容易出现bug 自定义容器Python 提供了collections容器类,(待续) Iterables 可迭代对象前面说的很多容器其实都是可迭代对象,此外还有更多的对象同样也是可迭代对象,比如处于打开状态的files等等。凡是可以返回一个迭代器的对象都可以称之为可迭代对象,举一个简单的例子:
这里x是一个可迭代对象,可迭代对象和容器一样是一种通俗的叫法,它们具有包含关系,属于概括的概念,并不是指某种具体的数据类型,list是可迭代对象,dict是可迭代对象,set也是可迭代对象。y和z是两个独立的迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。迭代器有一种具体的迭代器类型,比如list_iterator,set_iterator。可迭代对象实现了__iter__方法,该方法返回一个迭代器对象。 当运行以下代码:
背后真实的调用过程如下图所示: Iterators 迭代器迭代的意思就是重复的做一些事情,例如循环的形式。任何具有__next__()方法的对象都是迭代器,对迭代器调用next()方法可以获取下一个值。next()方法不需要任何参数,如果next()方法被调用时,迭代器没有值可以返回,就会引发一个StopIteration的异常。 另外迭代器一般是__iter__() 方法返回。 所以迭代器本质上是一个产生值的工厂,每次向迭代器请求下一个值,迭代器都会进行计算出相应的值并返回。 迭代器的例子很多,例如,所有itertools模块中的函数都会返回一个迭代器,有的还可以产生无穷的序列。
从一个有限序列中生成无限序列:
为了更直观地感受迭代器内部的执行过程,我们定义一个迭代器,如斐波那契数列:
Fib既是一个可迭代对象(包含__iter__方法),又是一个迭代器(因为实现了__next__方法)。实例变量prev和curr用户维护迭代器内部的状态。每次调用next()方法的时候做两件事: 为下一次调用next()方法修改状态,为当前这次调用生成返回结果。迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。 Generators 生成器生成器算得上是Python语言中最吸引人的特性之一,生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。
fib就是一个普通的python函数,它特殊的地方在于函数体中没有return关键字,函数的返回值是一个生成器对象。当执行f=fib()返回的是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。 来剖析代码:首先,fib是一个很普通的函数,但是函数中没有return语句,函数的返回值是一个生成器。 当调用f = fib()时,生成器被实例化并返回,这时并不会执行任何代码,生成器处于空闲状态,注意这里prev, curr = 0, 1并未执行。 然后这个生成器被包含在isslice()中,而这又是一个迭代器,所以还是没有执行上面的代码。 然后这个迭代器又被包含在list()中,它会根据传进来的参数生成一个列表。所以它首先对isslice()对象调用next()方法,isslice()对象又会对实例f调用next()。 然后执行到底11步的时候,isslice()对象就会抛出StopIteration异常,意味着已经到达末尾了。注意生成器不会接收到第11次next()请求,后面会被垃圾回收掉。 生成器在Python中是一个非常强大的编程结构,可以用更少地中间变量写流式代码,此外,相比其它容器对象它更能节省内存和CPU,当然它可以用更少的代码来实现相似的功能。现在就可以动手重构你的代码了,但凡看到类似:
都可以用生成器函数来替换实现,效果会更好:
生成器的类型在Python中生成器有两种类型:生成器函数以及生成器表达式。生成器函数就是包含yield参数的函数。生成器表达式与列表解析式类似。
使用set解析式也可以达到同样的目的:
或者dict解析式:
还可以使用生成器表达式:
注意我们第一次调用next()之后,lazy_squares对象的状态已经发生改变,所以后面后面地调用list()方法只会返回部分元素组成的列表。 总结生成器是Python中一种非常强大的特性,它让我们能够编写更加简洁的代码,同时也更加节省内存,使用CPU也更加高效。 容器,迭代对象,迭代器,生成器具有着一种必然的联系,是一种进步和推广。 容器是一系列元素的集合,str、list、set、dict、file、sockets对象都可以看作是容器,容器都可以被迭代(用在for,while等语句中),因此他们被称为可迭代对象。 可迭代对象实现了__iter__方法,该方法返回一个迭代器对象。 迭代器持有一个内部状态的字段,用于记录下次迭代返回值,它实现了__next__和__iter__方法,迭代器不会一次性把所有元素加载到内存,而是需要的时候才生成返回结果。 生成器是一种特殊的迭代器,它的返回值不是通过return而是用yield。 学习一个东西,梳理好内在联系,深层次理解一个概念很重要。 参考文献:1.https:///posts/iterators-vs-generators/ |
|
来自: imnobody2001 > 《Python》