《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函数.
浙公网安备 33010602011771号