【www.5929.com】揭秘yield关键字的地上边纱,Generator生成器函数

写在序言

1.迭代

在知晓生成器从前,先明了迭代。

 

www.5929.com 1

时常会看见,python函数中带有yield关键字,那么yield是如何,有如何效果?

1.1 迭代

即使给定一个list或tuple,大家得以由此for循环来遍历那几个list或tuple,那种遍历我们称为迭代(Iteration)

alist = [1, 2, 3, 4, 5]

for i in alist:
    print(i)

1
2
3
4
5

正如将列表中的元素通过for循环,遍历了全部alist列表,那种不另行地方便其里面包车型客车每三个子项的行事就是迭代。

几次三番串小说 — ES6笔记体系

Yield

 

一.2 可迭代对象

能够间接功用于for循环的指标统称为可迭代对象:Iterable,可迭代对象壹般都落到实处了__iter()__办法,可迭代对象通过其内建的方__iter()__归来二个迭代器对象。

a_iterable = [1, 2, 3]

a_iterator = iter(a_iterable)  # 将可迭代对象转化为迭代器

next(a_iterator)

1

next(a_iterator)

2

next(a_iterator)

3

 

基本功概念

答案:能够理解yield是1个生成器;

1.3 迭代器

可以被next()函数调用并连发重临下1个值的靶子称为迭代器:Iterator,迭代器其内完成了__iter__方法和__next__主意,for循环本质是由此调用可迭代对象的__iter__格局,该办法重临3个迭代器对象,再用__next__办法遍历成分

概念二个迭代器:

class MyRange:
    def __init__(self, end):
        self.index = 0
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < self.end:
            val = self.index
            self.index += 1
            return val
        else:
            raise StopIteration()

my_range = MyRange(3)

print([i for i in my_range])

[0, 1, 2]

print([i for i in my_range])

[]

迭代器只可以迭代3遍,每一遍调用调用 next()
方法就会上前一步,无法后退,所以当迭代器迭代到最后时,就不可能另行利用,全数需求将迭代器和可迭代对象分别定义

修改上面的可迭代对象:

class MyRange:
    def __init__(self, end):
        self.end = end

    def __iter__(self):
        return MyIterator(self.end)

class MyIterator:
    def __init__(self, end):
        self.index = 0
        self.end = end

    def __iter__(self):
        return self    

    def __next__(self):
        if self.index < self.end:
            val = self.index
            self.index += 1
            return val
        else:
            raise StopIteration()

my_range = MyRange(3)

print([i for i in my_range])

[0, 1, 2]

print([i for i in my_range])

[0, 1, 2]

接触过Ajax请求的会碰着过异步调用的标题,为了确认保证调用顺序的没错,一般大家会在回调函数中调用,也有用到部分新的化解方案如Promise相关的技能。

在异步编制程序中,还有一种常用的消除方案,它正是Generator生成器函数。顾名思义,它是四个生成器,它也是一个状态机,内部有着值及相关的场合,生成器再次来到3个迭代器Iterator对象,咱们能够透过这些迭代器,手动地遍历相关的值、状态,保障科学的施行各样。

可迭代对象

python中,1般能够被for循环遍历的对象正是可迭代对象。
拥有__iter__()主意的靶子称之为可迭代对象,__【www.5929.com】揭秘yield关键字的地上边纱,Generator生成器函数。iter__()艺术重回二个迭代器。

职能:遭受yield关键字,函数会直接重回yield值,相当于return;不一致的是下次调用的时候会从yield之后的代码开首进行。

【www.5929.com】揭秘yield关键字的地上边纱,Generator生成器函数。2. 生成器

www.5929.com,生成器与可迭代对象、迭代器的涉嫌

www.5929.com 2

图表来源Iterables vs. Iterators vs.
Generators

生成器对象,在历次调用它的next()方法时回来1个值,直到它抛出StopInteration。

生成器是足以迭代的,不过你 只好够读取它2次,因为它并不把装有的值放在内存中,它是实时地变化数据,
能够用生成器表明式创立:

my_generator = (x ** 2 for x in range(3))

my_generator

<generator object <genexpr> at 0x7f975b7a4af0>

for i in my_generator:
    print(i)

0
1
4

yield

能够写一个普通的包蕴yield语句的Python函数,Python会检查测试对yield的运用并将函数标记为三个生成器,当函数执行到yield语句时,像return语句那样再次回到三个值,然则解释器会保存对栈的引用,它会被用来在下2次调用next时回涨函数。

def my_generator():
    yield 1
    yield 2
    yield 'a'
    yield 'generator'

g = my_generator()

g

<generator object my_generator at 0x7f975b7a4d58>

next(g)

1

next(g)

2

next(g)

'a'

next(g)

'generator'

next(g)

---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-12-5f315c5de15b> in <module>()
----> 1 next(g)


StopIteration: 

地点的事例中,每一次调用next()开首实时地转变数据,并赶回,因而生成器只可读取叁次,上次执行读取的值在下次施行中就不或然读取。当全部生成器的值都被读取后,在调用机会晤世StopIteration的一无所能。

def my_gen():
    for i in range(5):
        yield i ** 3

my_gen()

<generator object my_gen at 0x7f975ae15a40>

mygen = my_gen()

for i in mygen:
    print(i)

0
1
8
27
64

每回执行到yield语句,则赶回二个值,再实践的时候从上次停下来的地方开头实施。yield语句保存了上次实施后的图景,下次执行不是从头初始,而是从上次的图景开头。

当调用my_gen()那些函数的时候,函数内部的代码不会立马执行,而是重返贰个生成器对象,当使用for循环举办遍历的时候,函数内部的代码初始举办,执行到yield表达式再次回到3个值,记录当前场所并终止,下叁回的拜会时再从那些景况早先执行。

举四个不太合适的例子,普通的函数正是从未存档的玩耍,只要游戏开首,就玩到结尾,下二遍再玩照旧从头开头,而生成器正是加了存档,下次玩从上次存档的地点开头

 

迭代器

迭代器是访问集合内成分的1种方法。迭代器对象从集合的首先个因素初步走访,直到全数的要素都被访问2次后完工。
能够行使工厂函数iter()再次回到八个迭代器。

>>> iter([1,2,3])
<listiterator object at 0x1100b6a50>

 

有关生成器的思辨

(瞎掰的。。。。)生成器到底起到哪边呢功用吗,即便生成二个生成器对象,而生成器对象自然是3个迭代器,所以能够如此说,生成器再次来到了贰个能够用for循环遍历所以子项,能够用next()方法访问下一个子项,能够在造访时动态的扭转数据而节省里部存款和储蓄器的靶子。

一、不难利用

for循环遍历可迭代对象进度

  1. Python将对首要字in后的对象调用iter函数获取迭代器
  2. 调用迭代器的next方法赢得成分,直到抛出StopIteration极度。
  3. 对迭代器调用iter函数时将回到迭代器自己,所以迭代器也得以用于for语句中,不供给独特处理。
    代码如下

it=iter(lst)
try:
      while True:
          val=it.next()
          print val
except
      StopIteration:
          pass

生成器是怎么着?

阅读

统统了然 Python
迭代对象、迭代器、生成器
对 Python
迭代的深远钻研
Python迭代器和生成器
3.
(译)Python关键字yield的解释(stackoverflow)
Python之列表生成式、生成器、可迭代对象与迭代器

1. 声明

Generator的宣示形式接近1般的函数评释,只是多了个*号,并且1般能够在函数内看看yield关键字

function* showWords() {
    yield 'one';
    yield 'two';
    return 'three';
}

var show = showWords();

show.next() // {done: false, value: "one"}
show.next() // {done: false, value: "two"}
show.next() // {done: true, value: "three"}
show.next() // {done: true, value: undefined}

如上代码,定义了一个showWords的生成器函数,调用之后回到了2个迭代器对象(即show)

调用next方法后,函数内实施第1条yield语句,输出当前的气象done(迭代器是不是遍历完结)以及相应值(一般为yield关键字背后的演算结果)

每调用2回next,则履行一次yield言辞,并在该处暂停,return达成现在,就退出了生成器函数,后续若是还有yield操作就不再实施了

正文

在stackoverflow中观察那样一个题材 What does the “yield” keyword do in
python

里头排行最高的答应对自笔者有非常的大扶持,由此将其翻译下来享用给大家答案。
转眼间是译文:

要明了什么是yield要害字,必要求通晓什么是生成器,而要领悟生成器,首先要了然什么是迭代器

是可以迭代的,可是你
只可以够读取它1回
,因为它并不把装有的值放在内部存款和储蓄器中,它是实时地变化数据:

2. yield和yield*

有时候,大家会看到yield之后跟了三个*号,它是哪些,有怎么样用啊?

好像于生成器前边的*号,yield后边的星号也跟生成器有关,举个大栗子:

function* showWords() {
    yield 'one';
    yield showNumbers();
    return 'three';
}

function* showNumbers() {
    yield 10 + 1;
    yield 12;
}

var show = showWords();
show.next() // {done: false, value: "one"}
show.next() // {done: false, value: showNumbers}
show.next() // {done: true, value: "three"}
show.next() // {done: true, value: undefined}

扩大了叁个生成器函数,大家想在showWords中调用二回,不难的 yield
showNumbers()之后察觉并从未履行函数里面包车型地铁yield 10+1

因为yield只好一点儿也不动地回到右侧运算后值,但近期的showNumbers()不是形似的函数调用,再次来到的是迭代器对象

之所以换个yield* 让它自动遍历进该对象

function* showWords() {
    yield 'one';
    yield* showNumbers();
    return 'three';
}

function* showNumbers() {
    yield 10 + 1;
    yield 12;
}

var show = showWords();
show.next() // {done: false, value: "one"}
show.next() // {done: false, value: 11}
show.next() // {done: false, value: 12}
show.next() // {done: true, value: "three"}

要留意的是,那yield和yield*
只幸好generator函数内部选拔,一般的函数内选取会报错

function showWords() {
    yield 'one'; // Uncaught SyntaxError: Unexpected string
}

虽说换来yield*不会直接报错,但使用的时候照旧会不经常,因为’one’字符串中未有Iterator接口,未有yield提供遍历

function showWords() {
    yield* 'one'; 
}

var show = showWords();

show.next() // Uncaught ReferenceError: yield is not defined

在爬虫开发中,大家平时须要请求三个地方,为了保险顺序,引进Promise对象和Generator生成器函数,看这一个大致的板栗:

var urls = ['url1', 'url2', 'url3'];

function* request(urls) {
    urls.forEach(function(url) {
        yield req(url);
    });

//     for (var i = 0, j = urls.length; i < j; ++i) {
//         yield req(urls[i]);
//     }
}

var r = request(urls);
r.next();

function req(url) {
    var p = new Promise(function(resolve, reject) {
        $.get(url, function(rs) {
            resolve(rs);
        });
    });

    p.then(function() {
        r.next();
    }).catch(function() {

    });
}

上述代码中forEach遍历url数组,匿名函数内部不可能选择yield关键字,改换到注释中的for循环就行了

迭代器

当您生成了三个list,能够多少个接1个地拜会这些list中的成分,那种作为被喻为迭代。

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...     print(i)
0
1
4

地方代码中的mylist正是2个迭代器。list类型是可迭代的。

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...     print(i)
0
1
4

在python中能够透过“for… in
…”
那种艺术遍历的皆以迭代器,像lists,strings,files…

迭代器是很好用的,因为你能够很有益于地遍历个中的元素。不过那个数据都以存在内部存款和储蓄器里的,当数据量非常的大时,那种方法就不是充足卓越了。

 

三. next()调用中的传参

参数值有注入的效力,可改变上二个yield的重回值,如

function* showNumbers() {
    var one = yield 1;
    var two = yield 2 * one;
    yield 3 * two;
}

var show = showNumbers();

show.next().value // 1
show.next().value // NaN
show.next(2).value // 6

率先次调用next之后回到值one为1,但在第一遍调用next的时候one其实是undefined的,因为generator不会活动保存相应变量值,我们必要手动的钦赐,那时two值为NaN,在第一回调用next的时候实施到yield
3 * two,通过传参将上次yield重返值two设为2,获得结果

另贰个板栗:

出于ajax请求涉及到网络,倒霉处理,那里用了setTimeout模拟ajax的伸手再次来到,按顺序进行,并传递每一趟回到的数据

 1 var urls = ['url1', 'url2', 'url3'];
 2 
 3 function* request(urls) {
 4     var data;
 5 
 6     for (var i = 0, j = urls.length; i < j; ++i) {
 7         data = yield req(urls[i], data);
 8     }
 9 }
10 
11 var r = request(urls);
12 r.next();
13 
14 function log(url, data, cb) {
15     setTimeout(function() {
16         cb(url);
17     }, 1000);
18     
19 }
20 
21 
22 function req(url, data) {
23     var p = new Promise(function(resolve, reject) {
24         log(url, data, function(rs) {
25             if (!rs) {
26                 reject();
27             } else {
28                 resolve(rs);
29             }
30         });
31     });
32 
33     p.then(function(data) {
34         console.log(data);
35         r.next(data);
36     }).catch(function() {
37         
38     });
39 }

高达了按顺序请求八个地方的效率,开头直接r.next()无参数,后续通过r.next(data)将data数据传入

www.5929.com 3

留神代码的第三陆行,那里参数用了url变量,是为了和data数据做比较

因为初始next()未有参数,借使直接将url换到data的话,就会因为promise对象的数据判断
!rs == undefined 而reject

故而将第3陆行换到 cb(data || url);

www.5929.com 4

通过模拟的ajax输出,可了然到next的传参值,第二回在log输出的是 url =
‘url一’值,后续将data = ‘url一’传入req请求,在log中输出 data = ‘url1’值

 

生成器

生成器是迭代器的壹种,然则只好被迭代三次。那是因为生成器并不会将具备的数量存在内部存款和储蓄器里,而是在应用的时候生成数据。

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...     print(i)
0
1
4

上面用[]变更了3个迭代器,而那里用()扭转了3个生成器。
但是再举行

for i in mygenerator:
    print(i)

不会有任何输出,因为生成器只好使用一回。在此以前2遍遍历中,生成器总计的到0,不存款和储蓄,然后总计获得1,不存款和储蓄,最终计算获得四。

有人也许会说,笔者间接迭代,遍历多好,为何要用生成器,然后去遍历生成器,那多劳动。

4. for…of循环代替.next()

除开使用.next()方法遍历迭代器对象外,通过ES陆提供的新循环格局for…of也可遍历,但与next差别的是,它会忽略return再次来到的值,如

function* showNumbers() {
    yield 1;
    yield 2;
    return 3;
}

var show = showNumbers();

for (var n of show) {
    console.log(n) // 1 2
}

其它,处理for…of循环,具有调用迭代器接口的点子形式也可遍历生成器函数,如扩小运算符…的使用

function* showNumbers() {
    yield 1;
    yield 2;
    return 3;
}

var show = showNumbers();

[...show] // [1, 2, length: 2]

yield

Yield有点像return,不相同的是yield会重回贰个生成器

>>> def createGenerator():
...     mylist = range(3)
...     for i in mylist:
...         yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

地点的那一个例子未有何样用,不过当你掌握重回数据量相当的大还要只会被用到三次时,yield关键词就很有用了。
要理解yield,你必须询问当再次来到生成器的函数被调用时,里面的代码实际上并未运行。那些函数只是回去了一个生成器的目的。那有点让人难以知晓。

在for循环中,那些生成器才会被采取到。

近来跻身不便的1部分:
for巡回中,生成器第3次被调用到时,重临这几个生成器的函数会顺序执行到yield部分,然后回到这么些轮回的第二个值。每回调用到生成器,都会进行函数中的循环三次,然后回来下四个值,直到未有值被重回。

当函数不再履行到yield的时候,生成器为空。这恐怕是循环结束了依然不再满足”if/else”判断。

那么你要询问,list列表,全数数据是储存在内部存款和储蓄器中的。尽管数据量十分大,会相当耗内部存款和储蓄器。

伍. 越多利用

越多选用可参考 MDN –
Generator

答疑题主的难点

生成器

# Here you create the method of the node object that will return the generator
def node._get_child_candidates(self, distance, min_dist, max_dist):
  # Here is the code that will be called each time you use the generator object:
  # If there is still a child of the node object on its left
  # AND if distance is ok, return the next child
  if self._leftchild and distance - max_dist < self._median:
      yield self._leftchild
  # If there is still a child of the node object on its right
  # AND if distance is ok, return the next child
  if self._rightchild and distance + max_dist >= self._median:
      yield self._rightchild
  # If the function arrives here, the generator will be considered empty
  # there is no more than two values: the left and the right children

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]
# Loop on candidates (they contain only one element at the beginning)
while candidates:
    # Get the last candidate and remove it from the list
    node = candidates.pop()
    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)
    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

地方的代码有多少个好玩的地方

  • 循环迭代了八个list的还要,也在往list里面添台币素。这种措施能够很不难遍历全数相邻的数量,尽管有希望造成极其循环。

candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

下边包车型客车代码会回来全体的生成器,但是while不断得发生新的生成器。

  • extend()方法是list的二个措施,传入3个迭代器,然后将其进入到list中

咱俩壹般那样用extend

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

在地方的代码中,传入了2个生成器,那样做有多个便宜

  1. 不必要读三次数据
  2. 字节点不用都留存内部存款和储蓄器中

上边的代码是立见成效的,因为python并不拥戴传入的参数是还是不是是二个list。它关切传入的是否一个迭代器,所以strings,lists,tuples,generators都是足以看做参数字传送入的!那称之为鸭子类型,也是python如此受欢迎的来头之1。

 

支配生成器

>>> class Bank(): # let's create a bank, building ATMs
...     crisis = False
...     def create_atm(self):
...         while not self.crisis:
...             yield "$100"
>>> hsbc = Bank() # when everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # it's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # build a new one to get back in business
>>> for cash in brand_new_atm:
...      print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

生成器能够做过多事情,下面代码显示了什么样运用yield控制能源的造访

 

Itertools-最佳的情人

itertools模块中有成都百货上千决定生成器的艺术。

看上面包车型地铁事例,看看肆匹马普托跑大概的逐条组合

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4), 
(1, 2, 4, 3), 
(1, 3, 2, 4), 
(1, 3, 4, 2), 
(1, 4, 2, 3), 
(1, 4, 3, 2), 
(2, 1, 3, 4), 
(2, 1, 4, 3), 
(2, 3, 1, 4), 
(2, 3, 4, 1), 
(2, 4, 1, 3), 
(2, 4, 3, 1), 
(3, 1, 2, 4), 
(3, 1, 4, 2), 
(3, 2, 1, 4), 
(3, 2, 4, 1), 
(3, 4, 1, 2), 
(3, 4, 2, 1), 
(4, 1, 2, 3), 
(4, 1, 3, 2), 
(4, 2, 1, 3), 
(4, 2, 3, 1), 
(4, 3, 1, 2), 
(4, 3, 2, 1)]

yield是二个破例的return?

叩问生成器的完成机制

迭代代表,调用可迭代对象的*iter()方法和迭代器的**next*()方法

今非昔比的是举行进程中遇到yield关键字,会阻断,yield
重临的是3个生成器。

率先次迭代中您的函数会执行,从初叶到达
yield 关键字,然后再次回到 yield 后的值作为第1次迭代的归来值.

然后,每一回执行这么些函数都会继续执行你在函数内部定义的十分循环的下3遍,再再次来到那2个值,直到未有能够重返的。

 

小心,当函数中出现yield,该函数重回的正是二个生成器。不在是壹般函数。

def func(num):
    n,a,b = 0,0,1
    while num > n:
        yield b  #阻断,返回b
        a,b = b,a + b
        n+=1

for i in  func(19): #func(19)是一个生成器,生成器只有调用时执行一次。所以这里用循环
    print i

 

除外for循环取值,你也得以通过next()来取下三个值。

t = func(19)
t.next()

 

Leave a Comment.