更多课程 选择中心


Python培训

400-111-8989

Python 学习笔记-表达式-运算符

  • 发布:雨痕
  • 来源:雨痕学堂
  • 时间:2017-08-11 15:12

示例运行环境: CPython 3.6.1, macOS 10.12.5

鉴于不同运行环境差异,示例输出结果会有所不同。尤其是 id,以及内存地址等信息。 请以实际运行结果为准。

运算符

相比其他语言,Python 运算符更接近自然表达方式。也正因如此,优先级导致的错误更加隐蔽,不易察觉和排除。

看下面示例,即便用括号调整优先级,也很难发现是否存在缺陷。

>>> not "a" in ["a", 1] # 谁先谁后 ?False>>> (not "a") in ["a", 1] # not 先 ?False>>> not ("a" in ["a", 1]) # in 先 ? 看不出来。False

可一旦数据变化,其结果就可能不同。除非测试数据完整覆盖,否则导致带病上线。

>>> not "a" in [1]True>>> (not "a") in [1]False>>> not ("a" in [1])True

该示例也提示我们,适当使用括号,不但可避免隐蔽错误,还能提高代码可读性。


每个运算符都有对应函数 (方法) 实现,可像普通函数那样作为逻辑传递。当然,用动态执行也未尝不可。

def calc(x, y, op): return op(x, y)

>>> import operator>>> calc(1, 2, operator.add)3>>> calc(1, 2, operator.mul)2

不仅仅是数学运算符,operator 还有 itemgetter、attrgetter 等用于索引和成员访问函数。

除此之外,还可用标准库提供的辅助函数,简化自定义类型运算符重载代码。

使用 functools.total_order 装饰器,基于 __eq__、__lt__ 方法,补全剩余比较方法。

@functools.total_orderingclass X: def __init__(self, n): self.n = n def __eq__(self, o): return self.n == o.n def __lt__(self, o): return self.n < o.n

>>> a, b = X(1), X(2)>>> a <= bTrue>>> a >= bFalse

Python 3 对运算符做了些调整。

移除 “<>”,统一使用 “!=” 运算符。

移除 cmp 函数,自行重载相关运算符方法。

除法 “/” 表示 True Division,总是返回浮点数。

不再支持反引号 repr 操作,调用同名函数。

不再支持非数字类型混合比较,可自定义相关方法。

不再支持字典相等以外的比较操作。

链式比较

链式比较(chained comparison)将多个比较表达式组合到一起,更符合人类阅读习惯, 而非面向机器。该方式可有效缩短代码,并稍稍提升性能。

>>> a, b = 2, 3>>> a > 0 and b > a and b <= 5True>>> 0 < a < b <= 5 # 可读性好,更易维护。True

反汇编查看两者差异。

>>> dis.dis(compile("1 < a and a < 2", "", "eval"))1

0 LOAD_CONST 0 (1) 2 LOAD_NAME 0 (a) # 载入。 4 DUP_TOP # 直接复制。 6 ROT_THREE 8 COMPARE_OP 0 (<) 10 JUMP_IF_FALSE_OR_POP 18 12 LOAD_CONST 1 (2) 14 COMPARE_OP 0 (<) 16 RETURN_VALUE >> 18 ROT_TWO 20 POP_TOP 22 RETURN_VALUE

显然,链式比较减少了载入指令,更多基于栈数据复制和交换。仅凭这点,其执行性能就有所提高。但整体上看,这点改善远不如代码可读性和可维护性吸引人。

切片

切片(slice)用以表达序列对象的某个片段(或整体),其具体行为与其在出现在语句中的位置有关。当以右值出现时,复制序列数据;而左值则表达要操控的目标范围。

>>> x = [0, 1, 2, 3, 4, 5, 6]>>> s = x[2:5] # 从列表中复制指定范围的引用。>>> s[2, 3, 4]

>>> x.insert(3, 100) # 对原列表的修改,不影响切片。>>> x[0, 1, 2, 100, 3, 4, 5, 6]>>> s[2, 3, 4]

注意,列表存储的是元素对象引用(指针),那么复制的自然也是引用,而非元素对象。切片所 返回新列表与原列表除共享部分元素对象外,其他毫无干系。

完整的切片操作由三个参数构成。

以起始和结束索引构成一个半开半闭区间(不含结束位置)。默认起始位置为 0,结束位置 len(x),以容纳最后一个元素。

>>> x = [100, 101, 102, 103, 104, 105, 106]>>> x[2:5:1][102, 103, 104]

>>> x[:5] # 省略起始索引。[100, 101, 102, 103, 104]>>> x[2:] # 省略结束索引。[102, 103, 104, 105, 106]>>> x[:] # 完整复制。[100, 101, 102, 103, 104, 105, 106]

可指定步进幅度,间隔选取元素。甚至可以是负值,从右至左反向行进。

索引 0 表示正向第一元素,所以反向索引从 -1 起始。

>>> x[2:6:2][102, 104]>>> x[::2][100, 102, 104, 106]>>> x[::-1] # 反向步进,全部复制。[106, 105, 104, 103, 102, 101, 100]>>> x[5:2:-1] # 反向步进,使用正索引表示起始、结束位置。[105, 104, 103]>>> x[-2:-5:-1] [105, 104, 103]

除表达式外,也可使用 itertools.islice 函数执行切片操作。

事实上,负索引不仅用于切片,也可直接访问序列元素。

>>> (0, 1, 2)[-2]1>>> "abcd"[-3]'b'

删除

用切片指定要删除的序列范围。

>>> x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>>> del x[3:7]>>> x[0, 1, 2, 7, 8, 9]

>>> x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>>> del x[3:7:2] # 步进删除。>>> x[0, 1, 2, 4, 6, 7, 8, 9]

赋值

以切片方式进行序列局部赋值,相当于先删除,后插入。

>>> x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>>> x[3:7] = [100, 200]>>> x[0, 1, 2, 100, 200, 7, 8, 9]

如设定步进,则删除和插入元素数量必须相等。

>>> x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>>> x[::2] # 查看要删除的元素。[0, 2, 4, 6, 8]>>> x[::2] = [100, 200, 400, 600, 800] # 步进插入。>>> x[100, 1, 200, 3, 400, 5, 600, 7, 800, 9]

>>> x[::2] = [0, 2, 4]ValueError: attempt to assign sequence of size 3 to extended slice of size 5>>> x[::2] = [0, 2, 4, 6, 8, 10]ValueError: attempt to assign sequence of size 6 to extended slice of size 5

逻辑运算

逻辑运算用于判断多个条件的布尔结果,或返回有效操作数。

分别以 and、or、not 运算符表示逻辑与、或、非三种关系。 其中 and 返回最后,或导致短路的操作数;or 返回第一真值,或最后操作数。

>>> 1 and 2 # 最后操作数。2>>> 1 and 0 and 2 # 导致短路的操作数。0

>>> 1 or 0 # 第一真值。1>>> 0 or 1 or 2 # 第一真值。1>>> 0 or [] # 最后操作数。[]

相同逻辑运算符一旦短路,后续计算被终止。

def x(o): # 输出执行信息。 print("op:", o) return o

>>> x(0) and x(1) # 0 导致短路。op: 00>>> x(1) or x(2) # 返回 1 真值,短路。op: 11

用反汇编可以看得更清楚些。

>>> dis.dis(compile("0 and 1 and 2 and 3", "", "eval"))1 0 LOAD_CONST 0 (0) 2 JUMP_IF_FALSE_OR_POP 14 # 如果为 False,跳转到 14。 4 LOAD_CONST 1 (1) 6 JUMP_IF_FALSE_OR_POP 14 8 LOAD_CONST 2 (2) 10 JUMP_IF_FALSE_OR_POP 14 12 LOAD_CONST 3 (3) >> 14 RETURN_VALUE

>>> dis.dis(compile("1 or 2 or 3 or 4", "", "eval")) 1 0 LOAD_CONST 0 (1) 2 JUMP_IF_TRUE_OR_POP 14 4 LOAD_CONST 1 (2) 6 JUMP_IF_TRUE_OR_POP 14 8 LOAD_CONST 2 (3) 10 JUMP_IF_TRUE_OR_POP 14 12 LOAD_CONST 3 (4) >> 14 RETURN_VALUE

当然,不同运算符需多次计算。

>>> x(0) and x(1) or x(2)op: 0op: 22

>>> dis.dis(compile("0 and 1 and 2 or 9", "", "eval"))1 0 LOAD_CONST 0 (0) 2 POP_JUMP_IF_FALSE 12 # 如果 False,跳转到 12。 4 LOAD_CONST 1 (1) 6 POP_JUMP_IF_FALSE 12 8 LOAD_CONST 2 (2) 10 JUMP_IF_TRUE_OR_POP 14 >> 12 LOAD_CONST 3 (9) >> 14 RETURN_VALUE

条件表达式

另一常见逻辑运算是条件表达式(conditional expression),类似功能在其他语言被称作三元运算符(ternary operator)。

T if X else F :当条件 X 为真时,返回 T,否则返回 F。等同 X ? T : F。

>>> "T" if 2 > 1 else "F" # 等同 2 > 1 ? T : F'T'>>> "T" if 2 < 1 else "F"'F'

也可用逻辑运算符实现同等效果,且方式更接近传统习惯。

>>> 2 > 1 and "T" or "F" # 2 > 1 ? T : F'T'>>> 2 < 1 and "T" or "F"'F'

分解执行步骤,这很容易理解。

>>> 2 < 1 and "T" or "F"'F'>>> 2 < 1 and "T" # ① 2 < 1 导致短路 。False>>> False or "F" # ② 返回真值 F。'F'

可惜此方法存在缺陷:当 T 为假时,那么 or 必然返回最后操作数,与预期不符。

>>> 2 > 1 and "" or "F"'F'>>> 2 > 1 and "" # ① 返回最后操作数 ""。''>>> "" or "F" # ② 返回真值 F,与期望值 "" 不符。'F'

显然,当 T 和 F 是动态数据时,条件表达式更安全一些。

>>> "" if 2 > 1 else "F"''

逻辑运算符还常被用来简化默认值设置。

>>> x = None>>> y = x or 100>>> y100

>>> x = None>>> y = x and x * 2 or 100>>> y100

预约申请免费试听课

填写下面表单即可预约申请免费试听! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!

上一篇:Python数据分析入门指南教程
下一篇:Python学习书籍推荐:Python与机器学习实战

2021年Python全套免费视频教程在哪里?

Python编程学习路线

Python最高有几级?

人工智能与语音遥控的区别?

  • 扫码领取资料

    回复关键字:视频资料

    免费领取 达内课程视频学习资料

Copyright © 2023 Tedu.cn All Rights Reserved 京ICP备08000853号-56 京公网安备 11010802029508号 达内时代科技集团有限公司 版权所有

选择城市和中心
黑龙江省

吉林省

河北省

湖南省

贵州省

云南省

广西省

海南省