本部分主要介绍 Python 的部分高级特性,包括切片、迭代器、推导式、生成器、匿名函数、装饰器等。阅读本文预计需要 15 min。

一文了解Python部分高级特性

  • 1. 前言
  • 2. 切片
  • 3. 迭代、可迭代对象、迭代器
  • 4. 推导式
    • 4.1 列表推导式
    • 4.2 集合推导式
    • 4.3 字典推导式
  • 5. 生成器和生成器表达式
    • 5.1 生成器
    • 5.2 生成器表达式
  • 6. 匿名函数
  • 7. 装饰器
  • 8. 巨人的肩膀

1. 前言

Python 非常灵活强大,跟它具有一些特性有关,如匿名函数、列表推导式、迭代器、装饰器等。本文主要简单介绍:

  • 切片
  • 迭代、可迭代对象、迭代器
  • 推导式(列表推导式、集合推导式、字典推导式)
  • 生成器和生成器表达式
  • 匿名函数
  • 装饰器

2. 切片

切片(slice)在 Python 中非常强大,可以轻松对字符串、列表和元组进行切割,完成拷贝。注意切片是浅拷贝,关于浅拷贝和深拷贝留作以后讨论。

切片的语法是:obj[start: end: step]

  • obj 是支持切片的对象,如:列表、字符串、元组等。
  • start 是开始切的索引位置,索引是从 0 开始标记的。start 可以省略,默认值是 0.
  • end 是切片结束的位置,实际上切不到 obj[end]。end 也可以省略,默认值是对象的长度。
  • step 是切片的步长,也可以省略,默认值是 1。

对字符串、列表、元组进行切片。

>>> word = "Python"
>>> word[:]
'Python'
>>> word[1:3]
'yt'
>>> word[::2]
'Pto'
>>> ls = [1, 2, 3, 4, 5, 6]
>>> ls[::2]
[1, 3, 5]
>>> t = (1, 2, 3, 4, 5, 6)
>>> t[2:6]
(3, 4, 5, 6)

切片也支持负数,使用 obj[::-1] 可以轻松实现翻转,如把列表翻转:

>>> ls = [1, 2, 3, 4, 5, 6, 7]
>>> ls[::-1]
[7, 6, 5, 4, 3, 2, 1]

obj[end] 是取不到的。

>>> ls = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> ls[1:9]
[2, 3, 4, 5, 6, 7, 8, 9]

可以看到 ls[1:9], ls[1],即 2 可以取到,ls[9],即 10 是取不到的。

3. 迭代、可迭代对象、迭代器

迭代(iteration):迭代是一种操作,可以理解为遍历,如用 for 循环遍历列表或者元组。
可迭代对象(iterable object):可以用 for 循环迭代的对象。如列表、元组、字符串、字典、集合等。
到目前为止,可以看到大多数容器对象都可以使用 for 语句:

for element in [1, 2, 3]:print(element)
for element in (1, 2, 3):print(element)
for key in {'one':1, 'two':2}:print(key)
for char in "123":print(char)
for line in open("myfile.txt"):print(line, end='')

我们还可以用 collections 模块中的 Iterable 类型判断一个对象是否是可迭代对象。

>>> from collections import Iterable
>>> isinstance([1, 2], Iterable)
True
>>> isinstance((1, 2), Iterable)
True
>>> isinstance('abc', Iterable)
True

迭代器(iterator):是遵循迭代器协议的可迭代对象就称为迭代器。迭代器协议机制是:for 语句会在容器对象上调用 iter()。 该函数返回一个定义了 __next__() 方法的迭代器对象,此方法将逐一访问容器中的元素。 当元素用尽时,__next__() 将引发 StopIteration 异常来通知终止 for 循环。你可以使用 next() 内置函数来调用 __next__() 方法。
通俗理解就是:能被 next()函数调用并不断返回下一个值的对象成为迭代器。

迭代器的使用非常普遍并使得 Python 成为一个统一的整体。下面这个例子显示了迭代器的运作方式:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):File "<stdin>", line 1, in <module>next(it)
StopIteration

我们可以使用 isinstance()方法判断一个对象是否是 Iterator 对象。

>>> from collections import Iterator
>>> ls = [1, 2, 3, 4]
>>> isinstance(ls, Iterator)
False
>>> isinstance(iter(ls), Iterator)
True
>>> t = (1, 2, 3, 4, 5)
>>> isinstance(t, Iterator)
False
>>> isinstance(iter(t), Iterator)
True
>>> s = 'abcde'
>>> isinstance(s, Iterator)
False
>>> isinstance(iter(s), Iterator)
True

通过上面的例子我们可以看到,列表、元组、字符串等是可迭代对象,但是不是迭代器。可以使用 iter()函数,轻松把列表、元组、字符串等转为迭代器。

为什么列表、元组、字符串等不是迭代器呢?
因为 Python 的 Iterator 对象表示的是一个数据流,迭代器可以被 next()函数不断调用并返回下一个数据,直到没有数据时抛出 StopIteration 错误。我们可以把这个数据流看成一个有序序列,但是我们却不能提前知道序列的长度,只能不断通过 next()函数实现按需计算下一个数据,因此 Iterator 的计算是惰性的,只有在需要返回下一个数据时它才会计算。

所以 Iterator 可以表示一个无限大的数据流,如全部整数,但是列表等容器由于内存空间限制,用于不可能存储全体整数。

小结:

  1. 迭代(iteration):迭代是一种操作,用 for 循环遍历。
  2. 可迭代对象(iterable object):可以用 for 循环迭代的对象。
  3. 迭代器(iterator):可以作用于 next()函数的可迭代对象,它们是一个惰性计算序列。
  4. 可以用 iter()函数把 list、str、tuple、dict 转为迭代器。

4. 推导式

推导式(comprehension)是 Python 非常重要的一个特性,提供了更加简单的创建列表、集合、字典的方式。其中列表推导式(list comprehensions)是用的最多的。

4.1 列表推导式

列表推导式是 Python 非常重要的一个特性之一。列表推导式提供了一个更简单的创建列表的方法。

常见的用法是把某种操作应用于序列或可迭代对象的每个元素上,然后使用其结果来创建列表,或者通过满足某些特定条件元素来创建子序列。

比如:假设我们想创建一个平方列表。

通常我们是这么做:

>>> squares = []
>>> for x in range(10):
...     squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

这样做尽管可以达到目的,但是这里创建(或被重写)的名为 x 的变量在 for 循环后仍然存在,可能存在副作用。我们可以通过以下方法计算平方列表的值而不会产生任何副作用。

方法一:squares = list(map(lambda x: x**2, range(10)))

方法一等价于:

方法二:squares = [x**2 for x in range(10)]

这里的方法二就是列表推导式,我们可以看到列表推导式更加简洁易读。

列表推导式的结构是:

[ 表达式 for子句(必须有一个) 0 或多个 for 或者 if 子句]

说明:由一对方括号([])所包含以下内容:一个表达式,后面跟一个 for 子句,然后是零个或多个 for 或 if 子句。根据后面的 for 等子句计算表达式的值,然后把所有计算的值存为一个新列表。如:以下列表推导式会将两个列表中不相等的元素组合起来变为一个新的列表:

[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]

它等价于

>>> combs = []
>>> for x in [1,2,3]:
...     for y in [3,1,4]:
...         if x != y:
...             combs.append((x, y))
...
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

可见,使用列表推导式非常简洁。注意在上面两个代码片段中, for 和 if 的顺序是相同的。如果表达式是一个元组(例如上面的 (x, y)),那么就必须加上括号。

列表推导式可以使用复杂的表达式和嵌套函数。如:

>>> from math import pi
>>> [str(round(pi, i)) for i in range(1, 6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']

嵌套的列表推导式:列表推导式中的初始表达式可以是任何表达式,包括另一个列表推导式。

考虑下面这个 3x4 的矩阵,它由 3 个长度为 4 的列表组成

>>> matrix = [
...     [1, 2, 3, 4],
...     [5, 6, 7, 8],
...     [9, 10, 11, 12],
... ]

下面的列表推导式将交换其行和列

>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

如上节所示,嵌套的列表推导式是基于跟随其后的 for 进行求值的,所以这个例子等价于:

>>> transposed = []
>>> for i in range(4):
...     transposed.append([row[i] for row in matrix])
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

反过来说,也等价于

>>> transposed = []
>>> for i in range(4):
...     # the following 3 lines implement the nested listcomp
...     transposed_row = []
...     for row in matrix:
...         transposed_row.append(row[i])
...     transposed.append(transposed_row)
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

实际应用中,使用内置函数去组成复杂的流程语句是更好的选择。 zip() 函数将会很好地处理这种情况

>>> list(zip(*matrix))  # *matrix 是列表解包,会去除最外层的[]
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

列表推导式小结:

  1. 列表推导式格式: [表达式(可嵌套列表推导式) for子句 0或多个 for 或 if 子句]
  2. 列表推导式创建新列表更加简洁易读,没有副作用。

注意,虽然元组和列表类似,但是列表是可变的,元组是不可变的,所以元组没有推导式。
元组是不可变的,其序列通常包含不同种类的元素,并且通过解包或者索引来访问。一个元素的元组的创建必须在一个元素后面添加一个逗号,如 t = (1, )

列表是可变的,一般列表的元素都是同种类型的,并且通过迭代访问。

4.2 集合推导式

集合是无序、确定、互异的。它通常用于成员测试和去重操作。此外还可以进行并集、交集、差集、补集等操作。

集合也和列表一样支持推导式,集合推导式(set comprehensions)如下:

>>> {x for x in 'abracadabra' if x not in 'abc'}
{'r', 'd'}

即:把[] 变为 {} 即可,其他和列表规则一样。

4.3 字典推导式

字典也支持字典推导式(dict comprehensions),如:

>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}
>>> {x: y for x, y in zip("abcd", [1, 2, 3, 4])}
{'a': 1, 'b': 2, 'c': 3, 'd': 4}

即:把[] 变为 {},同时表达式符合字典的键值对形式 key: value。其他规则同列表推导式。

5. 生成器和生成器表达式

5.1 生成器

生成器(Generator):是一个用于创建迭代器的简单而强大的工具。 它们的写法类似标准的函数,但当它们要返回数据时会使用 yield 语句。 每次对生成器调用 next() 时,它会从上次离开位置恢复执行(它会记住上次执行语句时的所有数据值)。就是把函数中 return 关键字换成了 yield 关键字,这样定义出来的就不是函数了,而是一个生成器,通常我们用 for 循环去迭代生成器,而不是用 next()函数一个一个调用,示例如下:

def reverse(data):for index in range(len(data)-1, -1, -1):yield data[index]>>> for char in reverse('golf'):
...     print(char)
...
f
l
o
g

可以用生成器来完成的操作同样可以用基于类的迭代器来完成。 但生成器的写法更为紧凑,因为它会自动创建 __iter__()__next__() 方法。

另一个关键特性在于局部变量和执行状态会在每次调用之间自动保存。

除了会自动创建方法和保存程序状态,当生成器终结时,它们还会自动引发 StopIteration。 这些特性结合在一起,使得创建迭代器能与编写常规函数一样容易。

生成器比较难理解的一点在于生成器的执行流程和函数流程不一样,函数是顺序执行,遇到 return 语句,或者最后一行函数语句就返回。而变成生成器的函数时,在每次调用 next()函数的时候执行,遇到 yield 语句返回,再次执行时从上次返回的 yield 语句处继续执行。

举个栗子:

>>> def language():
...     print("Step 1")
...     yield "Python"
...     print("Step 2")
...     yield "Java"
...     print("Step 3")
...     yield "C"
...
>>> lang = language()
>>> next(lang)
Step 1
'Python'
>>> next(lang)
Step 2
'Java'
>>> next(lang)
Step 3
'C'
>>> next(lang)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
StopIteration

调用 language 生成器时,首先要生成一个 generator 对象,然后用 next()函数不断获得下一个返回值。在执行的过程中,遇到 yield 就中断,下次又继续执行,执行 3 次 yield 后,没有 yield 可以执行了,所以第 4 次调用 next(lang)就报错了。

5.2 生成器表达式

生成器除了用类似于函数的定义方法外,还可以用类似于列表推导式的方式生成,所用语法类似列表推导式,就是把外层的方括号换成圆括号即可。这种表达式被设计用于生成器将立即被外层函数所使用的情况。
生成器表达式相比完整的生成器更紧凑但较不灵活,相比等效的列表推导式则更为节省内存。因为生成器表达式是生成迭代器,迭代器是惰性计算,它存储的是算法,需要多少就计算多少,而列表推导式是直接全部计算出来,放到内存。

例如:

>>> sum(i*i for i in range(10))                 # sum of squares
285>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
260>>> unique_words = set(word for line in page  for word in line.split())>>> valedictorian = max((student.gpa, student.name) for student in graduates)>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

6. 匿名函数

当我们传入函数时,有些时候不需要显示的定义函数,直接传入匿名函数( anonymous functions)更方便。

匿名函数是通过 lambda 关键字来创建的。基本语法是:lambda 参数: 表达式
其中参数可以是多个,用逗号分隔。如:lambda a, b: a+b,这个匿名函数返回两个参数的和。再举个栗子看看匿名函数的常用用法:

>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

Lambda 函数可以在需要函数对象的任何地方使用。它们在语法上限于单个表达式。从语义上来说,它们只是正常函数定义的语法糖,不用写 return,返回值就是表达式的结果。

匿名函数的优势在于没有名字,不用担心命名冲突。此外匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。如:

>>> s = lambda x, y: x + y  # 计算两数之和
>>> s
<function <lambda> at 0x000002B457B3AF78>
>>> s(2, 3)
5
>>> s(3, 7)

此外,与嵌套函数定义一样,lambda 函数可以引用所包含域的变量,把匿名函数作为返回值返回,举个官网栗子

>>> def make_incrementor(n):
...     return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43

7. 装饰器

装饰器(decorator)是一个非常有用的设计。它可以在代码运行期间动态增加功能。本质上,装饰器是一个返回函数的高阶函数。
比如:我们现在要定义一个能打印日志的装饰器,可以定义如下:

def log(func):def wrapper(*args, **kwargs):print(f"call {func.__name__}()")return func(*args, **kwargs)return wrapper

这个 log 其实就是一个装饰器,它接收一个函数作为参数,并返回一个函数。我们借助 Python 的 @ 语法,把装饰器置于函数的定义处:

def log(func):def wrapper(*args, **kwargs):print(f"call {func.__name__}()")  # func.__name__获取该函数的名字return func(*args, **kwargs)return wrapper@log
def now():print("2020-05-05")if __name__ == '__main__':now()结果输出:
call now()
2020-05-05

@log 放到 now() 函数的定义处,相当于执行了语句:`now = log(now)。
由于 log()是一个装饰器,返回一个函数,所以,原来的 now()函数仍然存在,只是现在同名的 now 变量指向了新的函数,于是调用 now()将执行新函数,即在 log()函数中返回的 wrapper()函数。

wrapper()函数的参数定义是(*args, **kwargs),因此 wrapper()可以接收任意参数的调用,在 wrapper()函数内,首先打印日志,再紧接着调用原始函数。

如果 decorator 本身需要传入参数,那就需要编写一个返回 decorator 的高阶函数,写出来会更复杂,比如,自定义 log 的文本:

def log(text):def decorator(func):def wrapper(*args, **kwargs):print(f"{text} call {func.__name__}")return func(*args, **kwargs)return wrapperreturn decorator@log("开始")
def now():print("2020-05-05")if __name__ == '__main__':now()结果输出:
开始 call now
2020-05-05

与 2 层嵌套相比,3 层嵌套效果如:now = log("开始")(now)
说明:首先执行 log(“我们”),然后返回 decorator 函数,在调用返回的函数,参数是 now 函数,返回值最终是 wrapper 函数。

这两种 decorator 定义方式都没有问题,不过还差最后一步,因为函数也是对象,它有__name__等属性,经过 decorator 装饰之后的函数,它们的__name__已经从原来的 now 变成了,wrapper:

def log(text):def decorator(func):def wrapper(*args, **kwargs):print(f"{text} call {func.__name__}")return func(*args, **kwargs)return wrapperreturn decorator@log("开始")
def now():print("2020-05-05")if __name__ == '__main__':now()print(now.__name__)结果输出:
开始 call now
2020-05-05
wrapper

这是因为返回的 wrapper()函数名字就是 wrapper,所以需要把原始函数的__name__等属性复制到 wrapper()函数中,否则,有些依赖函数签名的代码执行会出错。Python 内置的 functools.wraps 就是干这个事的,所以一个完整的 decorator 的写法如下:

import functoolsdef log(func):@functools.wraps(func)def wrapper(*args, **kwargs):print(f"call {func.__name__}()")  # func.__name__获取该函数的名字return func(*args, **kwargs)return wrapper@log
def now():print("2020-05-05")if __name__ == '__main__':now()print(now.__name__)结果输出:
call now()
2020-05-05
now

带参数的装饰器:

import functoolsdef log(text):def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):print(f"{text} call {func.__name__}")return func(*args, **kwargs)return wrapperreturn decorator@log("现在")
def now():print("2020-05-05")if __name__ == '__main__':now()print(now.__name__)结果输出:
现在 call now
2020-05-05
now

8. 巨人的肩膀

  1. The Python Tutorial
  2. 廖雪峰 Python3 教程

推荐阅读:

  1. 编程小白安装Python开发环境及PyCharm的基本用法
  2. 一文了解Python基础知识
  3. 一文了解Python数据结构
  4. 一文了解Python流程控制
  5. 一文了解Python函数
  6. 一文了解Python的模块和包

后记:
我从本硕药学零基础转行计算机,自学路上,走过很多弯路,也庆幸自己喜欢记笔记,把知识点进行总结,帮助自己成功实现转行。
2020下半年进入职场,深感自己的不足,所以2021年给自己定了个计划,每日学一技,日积月累,厚积薄发。
如果你想和我一起交流学习,欢迎大家关注我的微信公众号每日学一技,扫描下方二维码或者搜索每日学一技关注。
这个公众号主要是分享和记录自己每日的技术学习,不定期整理子类分享,主要涉及 C – > Python – > Java,计算机基础知识,机器学习,职场技能等,简单说就是一句话,成长的见证!

一文了解Python部分高级特性相关推荐

  1. python的高级特性:切片,迭代,列表生成式,生成器,迭代器

    python的高级特性:切片,迭代,列表生成式,生成器,迭代器 1 #演示切片 2 k="abcdefghijklmnopqrstuvwxyz" 3 #取前5个元素 4 k[0:5 ...

  2. python 的高级特性:函数式编程,lambda表达式,装饰器

    一.Python语言的高级特性 函数式编程 基于Lambda演算的一种编程方式 程序中只有函数 函数可以作为参数,同样可以作为返回值 纯函数式编程语言:LISP,Haaskell Python函数式编 ...

  3. 一文搞懂K8S高级特性

    K8S高级特性 K8S中还有一些高级特性有必要了解下,比如弹性扩缩应用(见上文).滚动更新(见上文).配置管理.存储卷.网关路由等. 在了解这些高级特性之前有必要先看几个K8S的核心概念: Repli ...

  4. python的高级特性3:神奇的__call__与返回函数

    __call__是一个很神奇的特性,只要某个类型中有__call__方法,,我们可以把这个类型的对象当作函数来使用. 也许说的比较抽象,举个例子就会明白. In [107]: f = absIn [1 ...

  5. python 11高级特性

    切片 取列表中前3个元素我们常见的方法有两种: [L[0],L[1],L[2]] 采用循环的方式如下图所示. 对这种经常取指定索引范围的操作,用循环十分繁琐,因此,Python提供了切片(Slice) ...

  6. 【Python】高级特性 一

    切片 取一个list或tuple的部分元素 常规方法: 循环方法: 切片操作符: #切片 #取一个list或者tuple的部分元素是非常常见的操作 #取前三个元素 #利用切片操作,实现一个trim() ...

  7. 一文了解Python流程控制

    本文主要介绍 Python 的流程控制,包括 if.while.for 的用法以及一些注意事项等.阅读本文预计需要 8 min. Python入门之Python流程控制 1. 前言 2. 条件控制语句 ...

  8. python中io中的+模式_Python的高级特性,模块和IO操作

    今天我们学习Python的高级特性.模块和IO操作,通过学习这些,我们可以更快的了解Python,使用Python. 高级特性中会讲述列表生成式.生成器.和一些高级函数,学习这些方便我们快速的生成列表 ...

  9. Python高级特性

    我一直认为Python是一门很神奇的语言.神奇之处在于其既可阳春白雪,亦可下里巴人.也就是其简单到几乎所有的人稍加学习即可上手,但是你如果细细品味,就会发现他还有很多高深的东西.正如一位漂亮的姑娘,一 ...

最新文章

  1. 深入理解Java内存模型(四)——volatile
  2. 所有类是object的子类,但是又可以继承一个其他类解析
  3. Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化-[Android取经之路]
  4. windows下用easybcd引导ubuntu出现grub的解决方案
  5. 阿里妈妈首次公开新一代自研智能检索模型 | WWW 2018论文解读
  6. SpringBoot_数据访问-整合MyBatis(二)-配置版MyBatis
  7. python相对路径找不到文件_Python相对路径从子文件夹导入
  8. sklearn综合示例7:集成学习与随机森林
  9. CruiseControl中应用NCover和NCoverExplore
  10. [CTSC2017]吉夫特(思维+巧妙)
  11. [转载] Flask+Celery+Redis简单操作
  12. 实现网页布局的自适应 利用@media screen
  13. 【Android 逆向】加壳技术识别 ( VMP 加壳示例 | Dex2C 加壳示例 )
  14. 基于韦尔奇·鲍威尔法对图着色 含c++代码
  15. TARA(威胁分析和风险评估方法)
  16. BUAA_4:Kevin·Feng的正确@姿势
  17. HarmonyOS resources目录中“限定词目录”命名要求
  18. AARRR模型——变现:终极目标(下)
  19. 2020.12.10丨cufflinks 简介及使用说明
  20. 页面倒计时按钮的两种实现方式

热门文章

  1. 数学物理方程 第二章 线性偏微分方程的通解
  2. jvm 之 国际酒店 8 月 19 一次full GC 导致的事故
  3. MagicBox 20150701
  4. ZGC-一款为开拓JAVA新疆土的垃圾回收器
  5. 性格内向的人,是否适合做产品经理 ?
  6. 你从来没有真正爱过自己
  7. python整段代码注释-Python中注释(多行注释和单行注释)的用法实例
  8. 线上教育核心竞争力是什么?声网发布在线素质、职业教育解决方案
  9. Web Services:惊世未了缘
  10. python好学吗自学要学多久,python自学多久能学会