Python3 - 开始python编程(十二)

Python3 - 开始python编程(十二)

上一篇文章中,我们介绍了软件包和虚拟环境。今天,我们将回过头来介绍一些尚未介绍的内置类方法。

在开始之前,我们应该介绍一些基本算法。这些都非常基础,但是为我们入门提供了良好的基础。

让我们从先前介绍的while循环开始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
my_list = [1, 2, 3, 4, 5]

# Forward loop
index = 0
while index < len(my_list):
    print(my_list[index])
    index += 1


# Reverse loop
index = len(my_list) - 1
while index >= 0:
    print(my_list[index])
    index -= 1
    

# Get the last item for a list
def get_last_item(some_list):
    last_index = len(some_list) - 1
    return some_list[last_index]


print(get_last_item(my_list))

我将承认有更好的方法可以做到这一点,但这为我们提供了一个简单的示例,为我们为本文的其余部分做准备。

forward looping_和while循环对我们来说并不陌生,但是,我们尚未涵盖_reverse loops。尽管这对您中的某些人来说可能是微不足道的,但我不会假设所有读者都已经了解了循环遍历列表的工作原理。

在反向循环中,我们首先需要获取列表的长度,并将我们的index变量设置为length-1。我们这样做是因为列表基于0,这意味着最后一个可访问的索引将比总数少一个。我们列表中的项目。

而不是像在前向循环中那样将”index”的值与列表的长度进行比较,我们需要确保在循环中”index”的值不小于0。

我们可以执行列表中所需的工作,然后从index中减去1以获得列表中的上一个条目。一旦“索引”小于0,则while循环退出。

最后,我创建了一个函数,该函数返回给定列表中的最后一项。我们提供的列表都没有关系,它将始终返回最后一个项目。我添加此内容是因为这是我们将需要继续熟悉的概念。


迭代器

有时,我们需要创建包含一系列对象的类,这些对象可以循环通过,同时对循环的数据执行一些恒定的操作。这些对象的范围可以从一系列数字到一系列类。迭代器是提供此功能的类。

您需要熟悉两种类方法:

  • __iter __(self)-返回一个迭代器对象
  • __next __(self)-用于返回序列中的下一项

了解__iter____init__之间的区别很重要。

当您要在向类提供数据的同时创建类的实例时,使用__init__。这是我们将数据分配给迭代器的地方。

当您想使用序列(列表,有序_dict等)为迭代器循环通过的数据创建种子类时,使用__iter__。

__iter__返回带有__next__方法的对象,如果您在类内部具有自定义的__next__方法,则只需从__iter__方法返回self即可。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Doubled:
    def __init__(self, data):
        self.length = len(data)
        self.data = data
        self.index = 0
        
    def __iter__(self):
        return self
        
    def __next__(self):
        if self.index == self.length:
            raise StopIteration
        result = self.data[self.index] * 2
        self.index += 1
        return result
        
        
my_list = [1, 2, 3, 4, 5]
doubled = Doubled(my_list)

try:
    while True:
        print(next(doubled))
        
except StopIteration:
    print("Completed")

在这里,我们有一个Doubled类,它具有__init iter__和__next__方法。

__init__在类初始化时要求一个参数data。就我们而言,我们将类设计为将数字列表加倍。为此,我们需要确保将整数或浮点值列表传递给该类。

然后,__init__方法获取数据的长度,因此我们知道何时需要停止遍历传入的列表。它将数据保存到名为data的类属性中,并将当前索引位置设置为0。我们设置了当前索引,因此在创建新的迭代器时,我们始终从0开始。

因为我们在此类中定义了自己的__next__方法,所以我们只需要在__iter__方法内部返回return self即可。

__next__包含用于返回所传递列表中下一个值的逻辑。如果我们要传递字典,它将返回字典中的值,尽管我们需要sp说明如何返回下一个键的值。

在我们的__next__方法中,我们首先检查我们当前的索引位置不等于数据的长度。如果是这样,我们需要引发StopIteration异常。这与”IndexError”不同,后者在到达列表末尾时会收到,因为我们可能将此迭代器用于其他数据类型。

“StopIteration”特定于我们收到的错误,如果我们需要再次遍历该系列,那么我们要么需要创建另一个迭代器对象,要么想出一种不同的方法来遍历我们的数据。

接下来,我们执行此迭代器的逻辑。因为我们将列表中的每一项都加倍,所以将当前索引的值乘以2保存到结果变量中。之所以这样做,是因为我们需要为下一次运行准备索引,我们将在下一行进行准备。最后,如果一切成功,我们将返回结果。

迭代器的存在使我们的工作更加轻松,同时也确保了逻辑的一致性。但是,它们并不是遍历代码的唯一方法。


生成器

生成器类似于迭代器,除了它们的功能类似于迭代器。关于生成器的整洁之处在于它们允许您的函数暂停直到再次调用。从某种意义上说,它们有自己的__next____iter__方法。与函数不同,它们使用关键字”yield”返回数据。让我们看一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ef increment(number):
    yield number + 1
    yield number + 2


incrementor = increment(4)

try:
    print(next(incrementor))
    print(next(incrementor))
    print(next(incrementor))

except StopIteration:
    print("Nothing else...")

为了进行错误处理,我为此添加了一个try / except。

首先,我们创建一个名为”increment”的函数,该函数带有一个参数:”number”。在函数主体中,我们有两个yield语句;第一个返回传入的数字并加1,第二个返回数字加2。

然后,我们需要将增量函数存储在变量”incrementor”中,为上面的增量函数提供初始值。这些都将存储在变量中,并像类对象一样对待。

从这里,我们可以像迭代器一样使用它,通过将incrementor传递给next()函数,我们可以按顺序接收每个yield语句。这意味着我们将在控制台上看到”5”和”6”。如果像上面一样第三次调用next(incrementor),则会收到StopIteration异常。

使生成器如此有趣的是,我们对它们使用了无限循环。由于yield会暂停函数的执行,因此我们一次只收到一个结果。这意味着以下代码有效,不会引起问题。

1
2
3
4
5
6
def increment(number=0):
    n = number
    
    while True:
        yield n + 1
        n = n + 1

在这里,我们有一个新的增量器函数,它接受一个数字。 “= 0”表示如果未提供数字,则使用0作为默认值。然后,我们创建一个局部变量”n”,并为其分配“数字”。

接下来,我们有一个无限循环,尽管通常这些循环是不行的,但在这里还是可以接受的,因为yield会在每次调用时暂停执行。在函数的末尾,我们有n = n + 1,这会增加我们本地的n变量。

由于该函数在传递”yield”时暂停,因此下次我们将递增器传递给”next()”函数时,它将在包含”n = n + 1”的行上恢复。

生成器也可以以接近列表理解的格式编写。唯一的区别是我们在表达式周围使用括号而不是方括号。

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

generator_object = (i * 2 for i in my_list)

list_comp_object = [i * 2 for i in my_list]

从这里我们可以使用简单的for循环访问generator_object中的每个项目,就像我们在list_comp_object中得到的列表一样,或者我们可以使用next(generator_object)获得下一个值。

迭代器和生成器之间的一个重要区别是,迭代器先计算所有值并将它们存储在内存中,生成器仅将每次运行所需的内容存储在内存中。

生成器的运行速度比列表理解要慢,因此除非遇到内存不足的情况,否则应使用列表理解。嘿,如果需要,很容易进行重构。

生成器可用于递归任务,例如在硬盘驱动器上搜索文件或在网络抓取实用程序中浏览网页。


摘要

迭代器和生成器更多地是一个中间主题,但是一旦使用几次,便开始寻找在任何地方使用它们的方法。了解迭代器和生成器之间的区别对于应用程序的性能至关重要。确保在将它们包括在内之前就已经知道它们之间的区别了。


下一步是什么?

接下来是异步,此主题非常高级,并提供了许多用于同步代码的选项。您不仅可以配置时间,还可以同时运行代码。挂在那里,直到那时,继续练习!

Rating: