<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Python | Hugo Academic Blog Theme</title><link>https://blog.xuzhaoyang.fun/category/python/</link><atom:link href="https://blog.xuzhaoyang.fun/category/python/index.xml" rel="self" type="application/rss+xml"/><description>Python</description><generator>Wowchemy (https://wowchemy.com)</generator><language>en-us</language><lastBuildDate>Tue, 12 Mar 2019 19:41:40 +0000</lastBuildDate><image><url>https://blog.xuzhaoyang.fun/media/icon_hua2ec155b4296a9c9791d015323e16eb5_11927_512x512_fill_lanczos_center_3.png</url><title>Python</title><link>https://blog.xuzhaoyang.fun/category/python/</link></image><item><title>Python 之 迭代器与生成器</title><link>https://blog.xuzhaoyang.fun/post/python-%E4%B9%8B-%E8%BF%AD%E4%BB%A3%E5%99%A8%E4%B8%8E%E7%94%9F%E6%88%90%E5%99%A8/</link><pubDate>Tue, 12 Mar 2019 19:41:40 +0000</pubDate><guid>https://blog.xuzhaoyang.fun/post/python-%E4%B9%8B-%E8%BF%AD%E4%BB%A3%E5%99%A8%E4%B8%8E%E7%94%9F%E6%88%90%E5%99%A8/</guid><description>&lt;p>Python中的迭代器和生成器是对于初学者来说很容易混淆，更说不出两者的区别。
当然这也是面试中的常考内容，我今天将要详细介绍下它们。&lt;/p>
&lt;h3 id="迭代器">迭代器&lt;/h3>
&lt;p>Python中的对象之所以能迭代是因为对象定义(或继承)了**__iter__&lt;strong>或&lt;/strong>__getitem__**。&lt;/p>
&lt;!-- more -->
&lt;p>在需要迭代对象的时候，解释器会首先调用iter函数，iter的执行过程为：&lt;/p>
&lt;ul>
&lt;li>查找对象是否存在**__iter__&lt;strong>方法, 如果存在则调用，如果&lt;/strong>__iter__**返回的值是迭代器对象（Iterator），则直接返回，反之则报错,例如：&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>TypeError: iter() returned non-iterator of type &amp;lsquo;int&amp;rsquo;&lt;/p>
&lt;/blockquote>
&lt;ul>
&lt;li>如果没有实现**__iter__&lt;strong>方法,则会查看是否实现了&lt;/strong>__getitem__**方法，如果实现，则会生成一个迭代器,并返回。该迭代器在迭代时会尝试通过索引（从0开始）获取元素，直到抛出IndexError。&lt;/li>
&lt;li>如果两个方法都没有实现，则会抛出异常（其中&lt;strong>Iter&lt;/strong>为对象所属的类）:&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>TypeError: &amp;lsquo;Iter&amp;rsquo; object is not iterable&lt;/p>
&lt;/blockquote>
&lt;p>由此可以看出：&lt;strong>迭代器是从可迭代对象中获取的。&lt;/strong>
我们需要知道，**可迭代和迭代器是不一样的。**这可根据官方源码看出：&lt;/p>
&lt;pre>&lt;code class="language-python">def _check_methods(C, *methods):
mro = C.__mro__
for method in methods:
for B in mro:
if method in B.__dict__:
if B.__dict__[method] is None:
return NotImplemented
break
else:
return NotImplemented
return True
class Iterable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __iter__(self):
while False:
yield None
@classmethod
def __subclasshook__(cls, C):
if cls is Iterable:
return _check_methods(C, &amp;quot;__iter__&amp;quot;)
return NotImplemented
class Iterator(Iterable):
__slots__ = ()
@abstractmethod
def __next__(self):
'Return the next item from the iterator. When exhausted, raise StopIteration'
raise StopIteration
def __iter__(self):
return self
@classmethod
def __subclasshook__(cls, C):
if cls is Iterator:
return _check_methods(C, '__iter__', '__next__')
return NotImplemented
&lt;/code>&lt;/pre>
&lt;p>可以看出&lt;strong>Iterator&lt;/strong>需要实现**__iter__&lt;strong>和&lt;/strong>__next__&lt;strong>，而&lt;/strong>Iterable&lt;strong>只需要实现&lt;/strong>__iter__**就行。&lt;/p>
&lt;p>内置类型（str，dict，list，tuple，set）的实例对象只是&lt;strong>Iterable&lt;/strong>，却不算是&lt;strong>Iterator&lt;/strong>。&lt;/p>
&lt;p>例如：&lt;/p>
&lt;pre>&lt;code class="language-python">from collections.abc import Iterable,Iterator
print(isinstance(&amp;quot;xzyl&amp;quot;, Iterable)) # True
print(isinstance(&amp;quot;xzyl&amp;quot;, Iterator)) # False
class It1:
def __iter__(self):
pass
class It2:
def __iter__(self):
pass
def __next__(self):
pass
print(isinstance(It1(), Iterable)) # True
print(isinstance(It2(), Iterable)) # True
print(isinstance(It1(), Iterator)) # False
print(isinstance(It2(), Iterator)) # True
&lt;/code>&lt;/pre>
&lt;p>从上面的信息来看，判断对象是否可迭代的最准确的方法是通过调用&lt;strong>iter&lt;/strong>方法然后捕获&lt;strong>TypeError&lt;/strong>了，因为实现序列协议**__getitem__&lt;strong>的对象也是可迭代的。并且定义了&lt;/strong>__iter__**方法也不一定就表示该对象可迭代了，例如返回一个整数或字符串等。&lt;/p>
&lt;p>虽然可迭代对象可以定义为迭代器，但是我们不推荐这样做，后面会分享设计模式中的迭代器模式，来说明好的设计为何不应该这样做。&lt;/p>
&lt;h3 id="生成器">生成器&lt;/h3>
&lt;p>在了解什么是生成器之前，我们先了解下生成器函数：&lt;/p>
&lt;pre>&lt;code class="language-python">def counter():
for i in range(1,11):
yield i
print(counter) # &amp;lt;function __main__.counter()&amp;gt;
print(counter()) # &amp;lt;generator object counter at 0x000001B6BB7D2660&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>正如上面看到的，函数体中有yield关键字的函数即为生成器函数。生成器函数自身跟普通函数没有区别，但是在调用时会返回一个生成器对象，是生成器对象的生产工厂。&lt;/p>
&lt;p>生成器对象同样是迭代器对象。&lt;/p>
&lt;pre>&lt;code class="language-python">print(isinstance(counter, Iterator)) # True
&lt;/code>&lt;/pre>
&lt;p>生成器同时拥有延迟计算的特点：&lt;/p>
&lt;pre>&lt;code class="language-python">def counter():
for i in range(1,3):
print(i)
yield i
c = counter()
print(next()) # 1
print(next()) # 2
print(next()) # raise StopIteration
&lt;/code>&lt;/pre>
&lt;p>由于生成器延迟计算的特点，可以在读取大数据时节省很大内存。&lt;/p>
&lt;h3 id="区别">区别&lt;/h3>
&lt;p>个人总结两者的区别有：&lt;/p>
&lt;ul>
&lt;li>两者的生成方式不同&lt;/li>
&lt;/ul>
&lt;p>迭代器对象是实现了迭代器协议(&lt;strong>__iter__&lt;strong>和&lt;/strong>__next__&lt;/strong>)的对象，而生成器对象则是通过调用生成器函数(含&lt;strong>yield&lt;/strong>关键字)或者生成器推导式生成&lt;/p>
&lt;ul>
&lt;li>生成器一定是迭代器，但迭代器不一定是生成器（生成器是迭代器的子类）&lt;/li>
&lt;/ul>
&lt;p>生成器同样实现了迭代器协议&lt;/p>
&lt;ul>
&lt;li>两者的数据源不一样&lt;/li>
&lt;/ul>
&lt;p>迭代器需要确定的原始数据，而生成器可以不需要数据（如果你想，你可以一直在函数中yield值）。&lt;/p>
&lt;ul>
&lt;li>生成器有send方法，而迭代器不一定有&lt;/li>
&lt;/ul>
&lt;p>总体来说在以遍历为目的的时候，两者并无太大区别，但是生成器写法更加简洁。&lt;/p>
&lt;p>生成器更加重要的作用是在协程部分，以后将会分享。&lt;/p></description></item><item><title>Python中的延迟绑定</title><link>https://blog.xuzhaoyang.fun/post/python%E4%B8%AD%E7%9A%84%E5%BB%B6%E8%BF%9F%E7%BB%91%E5%AE%9A/</link><pubDate>Tue, 05 Mar 2019 11:30:16 +0000</pubDate><guid>https://blog.xuzhaoyang.fun/post/python%E4%B8%AD%E7%9A%84%E5%BB%B6%E8%BF%9F%E7%BB%91%E5%AE%9A/</guid><description>&lt;p>延迟绑定出现在闭包问题中。下面我们看一个闭包的例子：&lt;/p>
&lt;pre>&lt;code class="language-python">def gen_mul(n):
def mul(x):
return n*x
return mul
double = gen_mul(2)
doubled_value = double(6) # 12
&lt;/code>&lt;/pre>
&lt;p>可以看出满足闭包的几点：&lt;/p>
&lt;ul>
&lt;li>有内部函数&lt;/li>
&lt;li>内部函数引用了外部函数中的&lt;a href="#%e6%b3%a8">自由变量&lt;/a>&lt;/li>
&lt;li>内部函数被返回&lt;/li>
&lt;/ul>
&lt;p>闭包的优点：&lt;/p>
&lt;ul>
&lt;li>可以避免使用全局变量&lt;/li>
&lt;li>可以持久化变量，达到静态变量的作用&lt;/li>
&lt;/ul>
&lt;p>闭包的缺点：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>可能会消耗大量的内存&lt;/p>
&lt;/li>
&lt;li>
&lt;p>可能会导致内存泄漏&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>当然缺点可以通过人为避免。&lt;/p>
&lt;p>现在我们来看看另一个会引出延迟绑定的例子：&lt;/p>
&lt;pre>&lt;code class="language-python">def multipliers():
return [lambda x : i * x for i in range(4)]
print([m(2) for m in multipliers()]) # [6,6,6,6]
&lt;/code>&lt;/pre>
&lt;p>上边的例子会输出**[6,6,6,6]&lt;strong>,而不是我们预期的&lt;/strong>[0,2,4,6]**。&lt;/p>
&lt;p>这就是延迟绑定导致的结果。具体过程我们可以来分析下：
执行第三行时，会先执行&lt;strong>multipliers&lt;/strong>函数，然后执行函数中的列表解析式。在每一次迭代的时候都会生成一个匿名函数(这里只是定义)作为元素。然后回到第三行，遍历返回的列表中的匿名函数，传入参数2并执行。此时函数类似于这样：&lt;/p>
&lt;pre>&lt;code class="language-python">def noname(x):
return i * x
&lt;/code>&lt;/pre>
&lt;p>我们知道Python查找变量的作用域链的顺序依次为**&lt;a href="#%e6%b3%a8">LEGB&lt;/a>**：&lt;/p>
&lt;blockquote>
&lt;p>局部变量(L)-&amp;gt;外部函数中的局部变量(E)-&amp;gt;全局变量(G)-&amp;gt;内置变量(B)&lt;/p>
&lt;/blockquote>
&lt;p>非常重要的一点我们需要知道：&lt;strong>Python的作用域在编译时就已经形成了，而不是在运行时，函数的作用域与其被调用的位置无关。&lt;/strong>&lt;/p>
&lt;p>那么在本例中,上面的noname函数体中的i从何而来呢？当然首先会到multipliers函数的局部变量中去寻找。此时i的值已经为3，所以出现这种让人&amp;quot;费解&amp;quot;的现象。&lt;/p>
&lt;p>那么现在我们既然已经知道了原因，那么要怎样解决呢？&lt;/p>
&lt;p>我们可以将迭代的i值直接注入到匿名函数的函数体中，这里给出两种方法：&lt;/p>
&lt;ol>
&lt;li>通过为参数设置默认值，这是因为在编译时就会计算确定默认值：&lt;/li>
&lt;/ol>
&lt;pre>&lt;code class="language-python">def multipliers_ch1():
return [lambda m,x=i : m * x for i in range(4)]
&lt;/code>&lt;/pre>
&lt;ol start="2">
&lt;li>通过内置函数partial：&lt;/li>
&lt;/ol>
&lt;pre>&lt;code class="language-python">from functools import partial
def multipliers_ch2():
return [partial(lambda m,x : m * x,i) for i in range(4)]
&lt;/code>&lt;/pre>
&lt;ol start="3">
&lt;li>利用生成器的延迟计算：&lt;/li>
&lt;/ol>
&lt;pre>&lt;code class="language-python">def multipliers_ch3():
for m in range(4):
yield lambda x: m * x
&lt;/code>&lt;/pre>
&lt;p>partial及生成器的内容会在以后分享。&lt;/p>
&lt;p>运行结果&lt;/p>
&lt;pre>&lt;code class="language-python">print([m(2) for m in multipliers_ch1()]) # [0,2,4,6]
print([m(2) for m in multipliers_ch2()]) # [0,2,4,6]
print([m(2) for m in multipliers_ch3()]) # [0,2,4,6]
&lt;/code>&lt;/pre>
&lt;h4 id="注">注:&lt;/h4>
&lt;p>&lt;strong>自由变量&lt;/strong>：指未在本地作用域中绑定的变量，我们可通过访问函数的**__code__**属性进行查看：&lt;/p>
&lt;blockquote>
&lt;p>fun.__code__.co_freevars&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>LEGB&lt;/strong>: 可看&lt;a href="https://stackoverflow.com/a/292502/4447404" target="_blank" rel="noopener">该部分&lt;/a>解释&lt;/p></description></item></channel></rss>