《Python学习笔记本》第三章 表达式 笔记以及摘要(完结)
表达式(expression)由标识符、字面量和操作符组成。其完成运算、属性访问、以及函数调用等。表达式像数学公式那样,总是返回一个结果。
语句(statement)则由一到多行代码组成,其着重于逻辑过程,完成变量复制、类型定义,以及控制执行流方向等。说起来,表达式算是语句的一种,但语句不一定式表达式。
结单归纳:表达式完成计算,语句执行逻辑。
源文件
py2解释器都以ASCII为默认编码,如果源码里出现Unicode字符,就会导致其无法正常解析
所以要在头部加上专门的编码声明
# -*- coding: utf-8 -*-
py3将默认编码位YTF8,免去了我们为每个原码文件添加头编码信息的麻烦。
shijianzhongdeMacBook-Pro:bin shijianzhong$ vim main.py shijianzhongdeMacBook-Pro:bin shijianzhong$ python3 main.py 你好 shijianzhongdeMacBook-Pro:bin shijianzhong$ mail.py -bash: mail.py: command not found shijianzhongdeMacBook-Pro:bin shijianzhong$ chmod a+x main.py shijianzhongdeMacBook-Pro:bin shijianzhong$ main.py 你好 shijianzhongdeMacBook-Pro:bin shijianzhong$ main.py 你好 shijianzhongdeMacBook-Pro:bin shijianzhong$ main.py 你好 shijianzhongdeMacBook-Pro:bin shijianzhong$ cat main.py #! /usr/bin/env python3 print('你好') shijianzhongdeMacBook-Pro:bin shijianzhong$
命令行
sys.flags(读取解释器的参数数量)、sys.argv(读取启动参数)
shijianzhongdeMacBook-Pro:bin shijianzhong$ python3 -O main.py 1 2 'hhello' 1 ['main.py', '1', '2', 'hhello'] 你好 shijianzhongdeMacBook-Pro:bin shijianzhong$ python3 -OO main.py 1 2 'hhello' 2 ['main.py', '1', '2', 'hhello'] 你好 shijianzhongdeMacBook-Pro:bin shijianzhong$ cat main.py #! /usr/bin/env python3 import sys print(sys.flags.optimize) print(sys.argv) print('你好') shijianzhongdeMacBook-Pro:bin shijianzhong$
对于简单的代码测试可以用python3 -c
shijianzhongdeMacBook-Pro:bin shijianzhong$ python3 -c 'import sys; print(sys.platform)' darwin shijianzhongdeMacBook-Pro:bin shijianzhong$ python3 -c 'import sys; print(sys.version_info)' sys.version_info(major=3, minor=7, micro=4, releaselevel='final', serial=0) shijianzhongdeMacBook-Pro:bin shijianzhong$
退出
常见的清零操作包括finally和atexit。前者是结构化异常字句,无论异常是否发生,它总被执行。
而atexit用于注册在进程退出前才执行的清理函数。
import atexit import sys atexit.register(print, 'atexit') try:
# 就算退出了,finally还是执行,atexit第二个执行 sys.exit() finally: print('finally')
代码
可阅读性和可测试性是代码的基本要求
builtins.end = None print(end) def sum(x): n = 0 for i in x: n += 1 end # 块结束符 return n
在builtins模块中添加自己的属性,写在函数的最后面,这样的代码,别人看的会傻掉了。
语句
大多数代码规范在80字符,现在可以适当放宽到100字符
反斜杠续行符不能有空格和注释。
注释
通过#注释,分为块注释(block)和内联注释(inline)两类。块注释与代码块平级缩进,用于描述整块代码的逻辑意图和算法设计。内联注释在代码行尾部,补充说明其作用。
def test(): # block comment # line 2 # line 3 print() x = 1 # inline comment
帮助
与被编译器忽略的注释不同,帮助属于基本元数据,可在运行期查寻和输出。除在交互环境手工查看外,还用于编辑智能提示,改善编码体验,或导出生成开发手册
建议使用''''''三引号
模块顶部为模块帮助,函数中为函数帮助,模块帮助不能放在shebang前面
帮助信息放在__doc__属性中,可直接输出,或通过help输出
In [4]: import test_demo In [5]: help(test_demo) In [6]: test_demo.__doc__ Out[6]: '\n我是模块帮助\n\n' In [7]: test_demo.run.__doc__ Out[7]: '\n 我是函数帮助哈啊哈\n :return:\n ' In [8]: cat test_demo.py """ 我是模块帮助 """ def run(): ''' 我是函数帮助哈啊哈 :return: ''' ... In [9]:
shijianzhongdeMacBook-Pro:第三章 shijianzhong$ python -c 'import test_demo; print(test_demo.__doc__)' abc shijianzhongdeMacBook-Pro:第三章 shijianzhong$ python -OO -c 'import test_demo; print(test_demo.__doc__)' None shijianzhongdeMacBook-Pro:第三章 shijianzhong$
当解释器以'OO'优化方式运行时,帮助信息被移除。
赋值
多名赋值
In [1]: a = b = c = '[]' In [2]: a is b is c Out[2]: True In [3]:
用逗号赋值
In [4]: x = 1, 'abc' ,[123] In [5]: x Out[5]: (1, 'abc', [123]) In [6]:
增量赋值
增量赋值视图直接修改原对象内容,实现累加效果。当谈,其前提是目标对象允许,否则会退化为普通赋值。
看一波执行码,虽然好像看不懂
In [13]: dis.dis(compile('a +=[1,2]', "","exec")) 1 0 LOAD_NAME 0 (a) 2 LOAD_CONST 0 (1) 4 LOAD_CONST 1 (2) 6 BUILD_LIST 2 8 INPLACE_ADD 10 STORE_NAME 0 (a) 12 LOAD_CONST 2 (None) 14 RETURN_VALUE In [14]: dis.dis(compile('a +=(1,2)', "","exec")) 1 0 LOAD_NAME 0 (a) 2 LOAD_CONST 0 ((1, 2)) 4 INPLACE_ADD 6 STORE_NAME 0 (a) 8 LOAD_CONST 1 (None) 10 RETURN_VALUE In [15]:
In [19]: '__iadd__' in dir((1,)) Out[19]: False In [20]: '__iadd__' in dir([1,]) Out[20]: True In [21]:
序列解包
序列解包实际中,我还是用的比较多的。不多写,就写一条
多3个以内的(包含3个)的变量交换,编译器优化成ROT指令,直接交换栈帧数据,而不是构建元祖
In [21]: dis.dis(compile('a,b,c=c,b,a','','exec')) 1 0 LOAD_NAME 0 (c) 2 LOAD_NAME 1 (b) 4 LOAD_NAME 2 (a) 6 ROT_THREE 8 ROT_TWO 10 STORE_NAME 2 (a) 12 STORE_NAME 1 (b) 14 STORE_NAME 0 (c) 16 LOAD_CONST 0 (None) 18 RETURN_VALUE In [22]: dis.dis(compile('a,b,c,d=d,c,b,a','','exec')) 1 0 LOAD_NAME 0 (d) 2 LOAD_NAME 1 (c) 4 LOAD_NAME 2 (b) 6 LOAD_NAME 3 (a) 8 BUILD_TUPLE 4 10 UNPACK_SEQUENCE 4 12 STORE_NAME 3 (a) 14 STORE_NAME 2 (b) 16 STORE_NAME 1 (c) 18 STORE_NAME 0 (d) 20 LOAD_CONST 0 (None) 22 RETURN_VALUE In [23]:
左右式可以以相同的方式嵌套
In [29]: a, ((b, c),(d, e)) = 1,[range(2),'ab'] In [30]: a,b,c,d,e Out[30]: (1, 0, 1, 'a', 'b') In [31]:
星号收集
In [31]: a,*b,c = range(5) In [32]: a,b,c Out[32]: (0, [1, 2, 3], 4) In [33]: a,*b,c = range(3) In [34]: a,b,c Out[34]: (0, [1], 2) In [35]: a,*b,c = range(2) In [36]: a,b,c Out[36]: (0, [], 1) In [37]:
收集不到的时候,返回空列表,这个还是蛮有意思的。
*号收集不能单独出现,要么与其他名字在一起,要么放入列表或元祖内
In [37]: [*a] = 1,2,3,4,5 In [38]: a Out[38]: [1, 2, 3, 4, 5] In [39]:
序列解包和星号收集还用于空值表达式等场合
In [40]: for a, *b in ('abc',range(3)):print(a,b) a ['b', 'c'] 0 [1, 2] In [41]:
星号展开
星号还可用于展开可迭代(iterable)对象
简而言之,可迭代对象就是每次返回一个成员。所有序列类型,以及字典、集合、文件等都是可迭代类型。
In [41]: a = [1,2] In [42]: b = 'ab' In [43]: c = range(5,9) In [44]: [*a,*b,*c] Out[44]: [1, 2, 'a', 'b', 5, 6, 7, 8] In [45]:
对于字典,单星号展开主键,双星号展开键值
In [45]: d = {'a':1,'b':2} In [46]: (*d) File "<ipython-input-46-901b88e959e9>", line 4 SyntaxError: can't use starred expression here In [47]: [*d] Out[47]: ['a', 'b'] In [48]: {'c':1,**d} Out[48]: {'c': 1, 'a': 1, 'b': 2} In [49]: {**d} Out[49]: {'a': 1, 'b': 2} In [50]:
可以用于给函数传递参数
In [50]: def test(a,b,c): ...: print(locals()) ...: In [51]: test(range(3)) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-51-bb28b678f01f> in <module> ----> 1 test(range(3)) TypeError: test() missing 2 required positional arguments: 'b' and 'c' In [52]: test(*range(3)) {'a': 0, 'b': 1, 'c': 2} In [53]: a = {'a':1,'b':2} In [54]: c = {'c':3} In [55]: test(**a,**c) {'a': 1, 'b': 2, 'c': 3} In [56]:
作用域
作为隐式规则,赋值操作默认总是针对当前名字空间。
同一作用域内,名字总属于单一名字空间,不会因执行顺序将其引用到不同名字空间。
In [59]: x = 10 In [60]: def test(): ...: print(x) ...: x += 10 ...: In [61]: test() --------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) <ipython-input-61-fbd55f77ab7c> in <module> ----> 1 test() <ipython-input-60-d73a856c829b> in test() 1 def test(): ----> 2 print(x) 3 x += 10 4 UnboundLocalError: local variable 'x' referenced before assignment In [62]:
In [62]: dis.dis(test) 2 0 LOAD_GLOBAL 0 (print) 2 LOAD_FAST 0 (x) # 本地 4 CALL_FUNCTION 1 6 POP_TOP 3 8 LOAD_FAST 0 (x) # 本地 10 LOAD_CONST 1 (10) 12 INPLACE_ADD 14 STORE_FAST 0 (x) # 本地 16 LOAD_CONST 0 (None) 18 RETURN_VALUE In [63]:
从反汇编结果看,函数test内的x统统从本地名字空间引用
global指向全局名字空间,nonlocal为外层嵌套(enclosing)函数
除非必要,否则应避免直接对外部变量赋值。可用返回值等方式,交由持有者处理.
示例代表比较简单不演示了。
看一下反编译的代码
In [63]: def test(): ...: global x ...: x = 10 ...: In [64]: dis.dis(test) 3 0 LOAD_CONST 1 (10) 2 STORE_GLOBAL 0 (x) # 定义全局变量 4 LOAD_CONST 0 (None) 6 RETURN_VALUE In [65]: x Out[65]: 10 In [66]:
global还能给全局变量赋值。
nonlocal则自内向外依次检索嵌套函数,单不包括全局名字空间
如多层嵌套函数,存在同名变量,按就近原则处理。另nonlocal不能为外层嵌套函数新建变量。
In [66]: def outer(): ...: def inner(): ...: nonlocal x File "<ipython-input-66-1c23c1da3645>", line 3 nonlocal x ^ SyntaxError: no binding for nonlocal 'x' found
运算符
优先级用小括号就好了,是在记不住这么多
每个运算符有以特性的函数和方法实现,可以像普通对象作为传递
In [68]: from operator import mul In [69]: def my_mul(x,y,op): ...: return (op(x,y)) ...: In [70]: my_mul(1,2,mul) Out[70]: 2 In [71]: my_mul(4,2,mul) Out[71]: 8 In [72]:
标准库还提供了辅助函数,用简化定义类型运算符重新实现
使用functools.total_order装饰器,可基与__eq__,__lt__,自动补全摄于方法。
In [72]: import functools In [73]: @functools.total_ordering ...: class 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 ...: In [74]: a,b = X(1),X(2) In [75]: a<b Out[75]: True In [76]: a>b Out[76]: False In [77]: a>=b Out[77]: False
链式比较
链式比较将多个比较表达式组合在一起,更符合人类的阅读习惯。该方式可有效缩短代码,并稍微提升性能。
In [78]: a,b=2,3 In [79]: a>0 and b>a and b<=5 Out[79]: True In [80]: 0<a<b<=5 Out[80]: True In [81]:
反汇编不上了
切片
切片用于表达序列片段或整体。具体行为与其在语句中的位置有关,作为右值时复制序列数据,左值则表达操作范围。
In [81]: x = [1,[2],3] In [82]: y = x[1:] In [83]: y Out[83]: [[2], 3] In [84]: x[1].append(3) In [85]: y Out[85]: [[2, 3], 3] In [86]:
因列表存储的式元素指针,那么复制的自然也是指针,而非元素对象。切片所返回的新列表与原列表除共享部分元素对象外,其他毫无干系。
完整切片操作由3个参数构成
其以开始和结束索引构成一个半开半闭合,不含结束位置。
默认的起始位置为0;结束位置为len(x),以容纳最后一个元素。
索引0表示正向第一元素,反向缩影从-1开始
In [86]: x = list(range(100,107)) In [87]: x Out[87]: [100, 101, 102, 103, 104, 105, 106] In [88]: x[2:5:1] Out[88]: [102, 103, 104] In [89]: x[::-1] Out[89]: [106, 105, 104, 103, 102, 101, 100] In [90]: x[-2:-5:-1] Out[90]: [105, 104, 103] In [91]: x[5:2:-1] Out[91]: [105, 104, 103] In [92]:
删除
用切片指定要删除的序列范围
可切片删除,步进删除
In [101]: ll Out[101]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] In [102]: del ll[3:7] In [103]: ll Out[103]: [0, 1, 2, 7, 8, 9] In [104]: ll = list(range(10)) In [105]: ll Out[105]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] In [106]: del ll[3:7:2] In [107]: ll Out[107]: [0, 1, 2, 4, 6, 7, 8, 9] In [108]:
赋值
以切片方式进行序列局部赋值,相当于先删除,后插入
In [110]: ll = list(range(10)) In [111]: ll[3:7] = [100,200] In [112]: ll Out[112]: [0, 1, 2, 100, 200, 7, 8, 9] In [113]:
如设定步进,则删除和插入的元素数量必须相等
In [113]: ll = list(range(10)) In [114]: ll Out[114]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] In [115]: ll[::2] Out[115]: [0, 2, 4, 6, 8] In [116]: ll[::2] = [100,200,300,400,500] In [117]: ll Out[117]: [100, 1, 200, 3, 300, 5, 400, 7, 500, 9] In [118]: ll[::2] = [100,200,300,400] --------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-118-0435534aa2d3> in <module> ----> 1 ll[::2] = [100,200,300,400] ValueError: attempt to assign sequence of size 4 to extended slice of size 5 In [119]:
逻辑运算
逻辑运算用于判断多条件的布尔结构,或返回有效的操作数。
其中and返回最后,或导致短路的操作数;or返回第一真值,或最后的操作数数。
In [121]: 1 and {1:2} Out[121]: {1: 2} In [122]: 3 and 0 Out[122]: 0 In [123]: 3 and 0 and 1 Out[123]: 0 In [124]: 0 or 1 and 3 Out[124]: 3 In [125]:
相同的逻辑运算符一旦短路,后续计算被终止
In [125]: def x(o): ...: print('op', o) ...: return o ...: In [126]: x(0) and x(1) op 0 Out[126]: 0 In [127]: x(0) or x(1) op 0 op 1 Out[127]: 1 In [128]: x(1) or x(2) op 1 Out[128]: 1 In [129]:
这个有意思,非常有意思。
反汇编代码来一波,假装看的懂
In [129]: dis.dis(compile('0 and 1 and 2 and 3','','eval')) 1 0 LOAD_CONST 0 (0) 2 JUMP_IF_FALSE_OR_POP 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 In [130]: dis.dis(compile('5 or 1 or 2 or 3','','eval')) 1 0 LOAD_CONST 0 (5) 2 JUMP_IF_TRUE_OR_POP 14 4 LOAD_CONST 1 (1) 6 JUMP_IF_TRUE_OR_POP 14 8 LOAD_CONST 2 (2) 10 JUMP_IF_TRUE_OR_POP 14 12 LOAD_CONST 3 (3) >> 14 RETURN_VALUE In [131]: dis.dis(compile('5 and 1 and 2 and 3','','eval')) 1 0 LOAD_CONST 0 (5) 2 JUMP_IF_FALSE_OR_POP 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 In [132]:
不同的运算符须多次计算
In [133]: x(0) and x(1) or x(2) op 0 op 2 Out[133]: 2 In [134]: dis.dis(compile('0 and 1 and 2 or 3','','eval')) 1 0 LOAD_CONST 0 (0) 2 POP_JUMP_IF_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 (3) >> 14 RETURN_VALUE In [135]:
条件表达式
常见逻辑愿运算是条件表达式,类似功能在其他语言被称为三元运算符
In [135]: 'T' if 2> 1 else 'f' Out[135]: 'T' In [136]: 'T' if 2 < 1 else 'f' Out[136]: 'f' In [137]: 2>1 and 'T' or 'F' Out[137]: 'T' In [138]: 2<1 and 'T' or 'F' Out[138]: 'F' In [139]:
下面暂时一种特殊情况
In [139]: [] if 2> 1 else '' Out[139]: [] In [140]: 2>1 and [] or '' Out[140]: '' In [141]:
这种情况下,条件表达式没有任何问题。
运算符还常被用来简化默认值设置,这个很骚
In [147]: x = None In [148]: y = x or 100 In [149]: y Out[149]: 100 In [150]: In [150]: x= None In [151]: y = x and x*2 and 100 In [152]: y In [153]:
控制流
if elif else 多选择分支依次执行条件表达式,最终全部失败,或仅一条得以执行。
无论单个if有多少个分支,最多仅有一条得以执行。而多个if语句,则可能有多条,甚至全部被执行。两种的意义和执行方式完全不同,注意区别
应使用各种方式减少选择语句以及分支,减少缩进层次,避免流程控制里包含太多的细节。
将过长的分支代码重构为函数。相比于细节,有意义的函数名更友好
将复杂或过长的条件表达式重构为函数,更易阅读和维护
代码块阔度太长(比如需要翻屏),容易造成缩进错误
嵌套容易引发混乱,且层次太多可读性比较差,故应避免使用。
简单选择语句,用条件表达式或逻辑运算符替代。
死代码
In [153]: def test(x): ...: if x>0: ...: print('a') ...: elif x >5: # 这个永远不会执行 ...: print('b') ...: In [154]: test(1) a In [155]: test(10) a In [156]: test(3) a In [157]: dis.dis(test) 2 0 LOAD_FAST 0 (x) 2 LOAD_CONST 1 (0) 4 COMPARE_OP 4 (>) 6 POP_JUMP_IF_FALSE 18 3 8 LOAD_GLOBAL 0 (print) 10 LOAD_CONST 2 ('a') 12 CALL_FUNCTION 1 14 POP_TOP 16 JUMP_FORWARD 16 (to 34) 4 >> 18 LOAD_FAST 0 (x) 20 LOAD_CONST 3 (5) 22 COMPARE_OP 4 (>) 24 POP_JUMP_IF_FALSE 34 5 26 LOAD_GLOBAL 0 (print) 28 LOAD_CONST 4 ('b') 30 CALL_FUNCTION 1 32 POP_TOP >> 34 LOAD_CONST 0 (None) 36 RETURN_VALUE In [158]:
循环
循环语句分为while、for两种,不存在替代关系。前者用于执行逻辑循环;而后者则偏于对象内容迭代。
可选分支
Python循环语句可自选else分支,在循环正常结束时执行。
正常结束是指循环没有被break、return中断。当然,循环体没被执行也属正常。
另外,执行continue是允许的,它不是中断。
In [166]: n = 3 In [167]: while n > 0: ...: n -= 1 ...: else: ...: print('over') ...: over In [168]: n = 3 In [169]: while n > 0: ...: print('break') ...: break ...: else: ...: print('over') ...: ...: break In [170]: n = 3 In [171]: while n > 4: # 没有进入到while里面,算正常执行 ...: print('break') ...: break ...: else: ...: print('over') ...: over In [172]:
临时变量
循环语句没有单独的名字空间,其内部临时变量直接影响所在的上下文
In [172]: def test(): ...: while True: ...: x = 100 ...: break ...: for i in range(20): ...: ... ...: print(locals()) ...: print(x,i) ...: In [173]: test() {'x': 100, 'i': 19} 100 19 In [174]:
跳转
停止循环需要设定结束标志,然后在相应位置检查。
推导式
普通推导式用的比较多了,不写了
来一个嵌套的
In [175]: [f'{x}{y}' for x in 'abc' if x != 'c' for y in range(3) if y !=0] Out[175]: ['a1', 'a2', 'b1', 'b2'] In [176]:
双层循环。
性能
除了语法因素外,推导式还有性能上的优势
临时变量
和普通循环语句不同,推导式临时变量不影响上下文名字空间
In [176]: def text(): ...: a = 'abc' ...: data = {a:b for a,b in zip('xyz',range(10,13))} ...: print(locals()) ...: In [179]: text() {'a': 'abc', 'data': {'x': 10, 'y': 11, 'z': 12}} In [180]:
因为推导式变量在编译器自动生成的函数内使用,而非test函数.