欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

高级 python 功能:列表生成器、生成器、迭代器。

最编程 2024-03-16 13:26:41
...

Python高级特性

  • 列表生成式:不过一种语法糖
  • 生成器:不过一个方法
  • 迭代器:

 


 

列表生成式

 Python内置的函数,来创建list。

简单的生成:

>>> list(range(1,11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

复杂的生成:增加一个for循环。

>>> a = [x*x for x in range(1, 11)]
>>> a
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

 

还可以在for循环后加if判断,这样对循环后的所有元素进行整除,得到符合条件的一组元素,返回的是list。

>>> a = [x*x for x in range(1, 11) if x%2 == 0]
>>> a
[4, 16, 36, 64, 100]

 

还可以套用两层for循环

>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

 

 

for循环可以同时接收多个变量。比如dict的items()方法:

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
...  print(k, '=', v)
...
x = A
y = B
z = C

 

>>> d.items()
dict_items([('x', 'A'), ('y', 'B'), ('z', 'C')])

 

小结:

list生成式:

[表达式/0~n个变量 for 0~n个变量 in list/dict/相关变体] 

 

本质就是使用循环和条件语句对list/dict进行筛选:

[表达式/变量 条件/循环语句 条件/循环语句] 

 

Ruby类似的方法糖。

Ruby没有列表生成式,但有很多语法糖,来操作array对象。

range对象使用to_a转化为数组。

> (1...11).to_a
 => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

 乘方后,增加一个判断:等同Python上面的列表生成式????。

 > (1...11).to_a.map{|x| x * x}.select {|x| x % 2== 0}
 => [4, 16, 36, 64, 100]

 

 Ruby的each功能很强大:可以替代上面for接收多个变量的情况。

 Ruby的for..in..功能同样可以做到上面Python的功能:⚠️还更简便,无需调用items(), 

> for k,v in d
>   puts "#{k}=#{v}"
> end
x=A
y=B
z=C

 

Python测试:

请修改列表生成式,通过添加if语句保证列表生成式能正确地执行:

L1 = ['Hello', 'World', 18, 'Apple', None]

L2 = [x.lower() for x in L1 if isinstance(x, str)]
>>> L2
>['hello', 'world', 'apple']

 

解释: 就是简化写法,没别的。 经常用一看就直到。太复杂的还是分开写,便于后续维护代码。毕竟视觉上,分层容易理解。

L1 = ['Hello', 'World', 18, 'Apple', None]

L2 = []
for x in L1:
    if isinstance(x, str):
        L2.append(x.lower())

 


 

 

Generator (详情见PEP 255 -- Simple Generators)

参考:

三分钟看懂什么是Python生成器

生成器就是一个在行为上和迭代器非常类似的对象,如果把迭代器比作 Android 系统,那么生成器就是 iOS,二者功能上差不多,但是生成器更优雅。

 

什么是迭代器:

迭代器就是用于迭代操作(for 循环)的对象,它像列表一样可以迭代获取其中的每一个元素,任何实现了 __next__ 方法 (python2 是 next)的对象都可以称为迭代器。

它与列表的区别在于,构建迭代器的时候,不像列表把所有元素一次性加载到内存,而是以一种延迟计算(lazy evaluation)方式返回元素,这正是它的优点。

比如列表含有中一千万个整数,需要占超过400M的内存,而迭代器只需要几十个字节的空间。因为它并没有把所有元素装载到内存中,而是等到调用 next 方法时候才返回该元素(按需调用 call by need 的方式,本质上 for 循环就是不断地调用迭代器的next方法)

 

什么是生成器

普通函数用 return 返回一个值,和ruby等其他语言一样。然而在 Python 中还有一种函数,用关键字 yield 来返回值,这种函数叫生成器函数。

函数被调用时会返回一个生成器对象,生成器本质上还是一个迭代器,也是用在迭代操作中,因此它有和迭代器一样的特性,唯一的区别在于实现方式上不一样,后者更加简洁。

>>> def func(n):
...     yield n*n
...
>>> func
<function func at 0x1042aac10>

>>> g = func(10)
>>> g
<generator object func at 0x1042b4510>

>>> next(g)
100

>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
  • func就是一个生成器函数。<function func at 0x1042aac10>
  • 调用它时返回的就是生成器<generator object func at 0x1042b44a0>。这个生成器可以用在循环等场景中。
  • 可以使用next()函数,把生成器当成参数。这表示执行一次生成器,遇到yield返回。

 

这种一边循环一边计算的机制,称为生成器:generator。

  • 第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator。
  • 第二种方法,如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。

 

除了使用next(), 还可以使用for循环,它的本质也是调用next() , 例子:

def _odd_iter(n):
    while True:
        n += 2
        yield n
a = _odd_iter(1)

#因为生成器generator也是可迭代对象,所以可以使用for..in..循环
for i in a:
    print(i)
    if i > 10: # _odd_iter自身没有退出代码,所以这里加一个判断用于退出循环。
        break

 

 

为什么使用生成器?

生成器其实就是迭代器的高级版本,一种用普通的函数语法定义的迭代器。更好用。

我的理解生成器就是迭代器的一大块语法糖。省掉了自己创建迭代器的时间。

 

用Generator来生成一个杨辉三角函数

          1
         / \
        1   1
       / \ / \
      1   2   1
     / \ / \ / \
    1   3   3   1
   / \ / \ / \ / \
  1   4   6   4   1
 / \ / \ / \ / \ / \
1   5   10  10  5   1

把每一行看做一个list,试写一个generator,不断输出下一行的list:

# 杨辉三角的基本性质:每个数字等于上一行的左右两个数字之和。
# C(n+1,i) = c(n, i) + c(n, i -1)
# def triangles(high):
#     # 得到一个list。内有high个list元素。每个元素记录一行的数字。
#     # 用triangle来储存杨辉三角的数据。
#     triangle = []
#     z = 1
#     while z <= high:
#         triangle.append([])
#         z += 1
#
#     i = 2 #行号。注意不是数组号。
#     s = 1 #一个过渡变量。代表一个位置的数字的大小。
#     # print("1\n")     #第一行的1
#     triangle[0].append(1)
#     while i <= high:  #从第2行开始循环,到high
#         # print("1")   #本行第一个1
#         triangle[i -1].append(1)
#
#         j = 1 #j是列位置
#         while j <= i - 2:  #计算中间的数字。
#         # 根据杨辉三角自身的性质:设当前行为i, 要求得的数字的左侧数字的位置是j并且大小是s。
#         # 那么,要求的数字 = (i-j)*s/j。
#             s = (i-j)*s/j
#             # print("%d" %s)
#             triangle[i-1].append(int(s))
#             j += 1
#         # print("1\n") #本行最后一个1,换行
#         triangle[i-1].append(1)
#         i += 1
#         s = 1
#
#     return triangle

# 把上面的函数,转化为generator:
def triangles():
    # 没有参数,high。执行一次返回一行数字。
    # 用triangle来储存杨辉三角的数据。
    triangle = [[]]

    i = 2 #行号。注意不是数组号。
    s = 1 #一个过渡变量。代表一个位置的数字的大小。
    # print("1\n")     #第一行的1
    triangle[0].append(1)
    yield triangle[0]

    while i :  #从第2行开始循环
        triangle.append([])  #l必须添加一个内部list用于放置本行数字。
        # print("1")   #本行第一个1
        triangle[i -1].append(1)

        j = 1 #j是列位置
        while j <= i - 2:  #计算中间的数字。
        # 根据杨辉三角自身的性质:设当前行为i, 要求得的数字的左侧数字的位置是j并且大小是s。
        # 那么,要求的数字 = (i-j)*s/j。
            s = (i-j)*s/j
            # print("%d" %s)
            triangle[i-1].append(int(s))
            j += 1
        # print("1\n") #本行最后一个1,换行
        triangle[i-1].append(1)

        yield triangle[i -1]
        i += 1
        s = 1

# 期待输出:
# [1]
# [1, 1]
# [1, 2, 1]
# [1, 3, 3, 1]
# [1, 4, 6, 4, 1]
# [1, 5, 10, 10, 5, 1]
# [1, 6, 15, 20, 15, 6, 1]
# [1, 7, 21, 35, 35, 21, 7, 1]
# [1, 8, 28, 56, 70, 56, 28, 8, 1]
# [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
n = 0
results = []
for t in triangles():
    results.append(t)
    n = n + 1
    if n == 10:
        break

for t in results:
    print(t)

if results == [
    [1],
    [1, 1],
    [1, 2, 1],
    [1, 3, 3, 1],
    [1, 4, 6, 4, 1],
    [1, 5, 10, 10, 5, 1],
    [1, 6, 15, 20, 15, 6, 1],
    [1, 7, 21, 35, 35, 21, 7, 1],
    [1, 8, 28, 56, 70, 56, 28, 8, 1],
    [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
]:
    print('测试通过!')
else:
    print('测试失败!')

 

还有更简单的方法,都是通过对杨辉三角的特性来写出代码的

下面的例子使用的就是杨辉三角的基本特性:

一个位置的值 = 上一行相同位置的值+ 这个值的左相邻的值

 C(n+1,i) = c(n, i) + c(n, i -1)  #i代表列位置。

 

# def triangles():
#     t = [1]
#     yield t
#     while True:
#         t = [0] + t + [0]
#
#         new_t = []
#         # range(1, 3),会输出1,2。
#         for i in range(len(t)-1):
#         #len(t) -1 表示最后一个元素。但range(len(t)-1),的范围,不包括最后一个元素。
#             new_t.append(t[i] + t[i + 1])
#         t = new_t
#         yield t

# 上面的代码还可以使用list生成式简化: def triangles(): t = [1] while True: yield t t = [0] + t + [0] t = [t[i] + t[i + 1] for i in range(len(t) -1)]

 

参考:https://www.liaoxuefeng.com/wiki/1016959663602400/1017318207388128#0

 

百度知道:杨辉三角

 

小结:

在Python中,可以简单地把列表生成式改成generator,也可以通过函数实现复杂逻辑的generator。

对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。


 

 

Iterator迭代器。

 

集合数据类型,如listtupledictsetstr.

generator:指的是生成器<generator object func at 0x1042b44a0>和生成器函数<function func at 0x1042aac10>。

上面的数据类型都可以直接用于for循环。

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable

内部函数isinstance():可以判断一个对象,是否符合某个数据类型。

>>> isinstance('abc', Iterable)
True

>>> isinstance("123", str)
True
>>> isinstance(1, int)
True
>>> isinstance(1.1, float)
True
>>> isinstance({"a": 10}, dict)
True

 

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

>> from collections.abc import Iterator

>>> isinstance((x for x in range(10)), Iterator)
True

 

Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。 

listdictstrIterable变成Iterator可以使用iter()函数.