金沙js333娱乐场ES陆笔记(5)– Generator生成器函数

 

阅读

完全理解 Python
迭代对象、迭代器、生成器
对 Python
迭代的深入研究
Python迭代器和生成器
3.
(译)Python关键字yield的解释(stackoverflow)
Python之列表生成式、生成器、可迭代对象与迭代器

5. 更多使用

更多使用可参考 MDN –
Generator

 

关于生成器的思考

(瞎掰的。。。。)生成器到底起到什么吗作用呢,就算生成一个生成器对象,而生成器对象一定是一个迭代器,所以可以这么说,生成器返回了一个可以用for循环遍历所以子项,可以用next()方法访问下一个子项,可以在访问时动态的生成数据而节省内存的对象。

 

 

2. 生成器

生成器与可迭代对象、迭代器的关系

金沙js333娱乐场 1

图片来自Iterables vs. Iterators vs.
Generators

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

生成器是可以迭代的,但是你 只可以读取它一次
,因为它并不把所有的值放在内存中,它是实时地生成数据,
可以用生成器表达式创建:

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语句那样返回一个值,但是解释器会保存对栈的引用,它会被用来在下一次调用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表达式返回一个值,记录当前状态并停下,下一次的访问时再从这个状态开始执行。

举一个不太恰当的例子,普通的函数就是没有存档的游戏,只要游戏开始,就玩到结尾,下一次再玩还是从头开始,而生成器就是加了存档,下次玩从上次存档的地方开始

1. 声明

Generator的声明方式类似一般的函数声明,只是多了个*号,并且一般可以在函数内看到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的生成器函数,调用之后返回了一个迭代器对象(即show)

调用next方法后,函数内执行第一条yield语句,输出当前的状态done(迭代器是否遍历完成)以及相应值(一般为yield关键字后面的运算结果)

每调用一次next,则执行一次yield语句,并在该处暂停,return完成之后,就退出了生成器函数,后续如果还有yield操作就不再执行了

t = func(19)
t.next()

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笔记系列

然后,每次执行这个函数都会继续执行你在函数内部定义的那个循环的下一次,再返回那个值,直到没有可以返回的。

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循环就行了

经常会看见,python函数中带有yield关键字,那么yield是什么,有什么作用?

1.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

一、简单使用

 

1.3 迭代器

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator,迭代器其内实现了__iter__方法和__next__方法,for循环本质是通过调用可迭代对象的__iter__方法,该方法返回一个迭代器对象,再用__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])

[]

迭代器只能迭代一次,每次调用调用 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]

3. 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数据传入

金沙js333娱乐场 2

注意代码的第16行,这里参数用了url变量,是为了和data数据做对比

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

所以将第16行换成 cb(data || url);

金沙js333娱乐场 3

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

 

 

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

除了使用.next()方法遍历迭代器对象外,通过ES6提供的新循环方式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]

发表评论

电子邮件地址不会被公开。 必填项已用*标注