1.断言

Python 的断言语句是一种调试辅助功能,不是用来处理运行时错误的机制。

assert 在条件为 False 的时候触发,后面的内容是报错信息。

import sys
assert sys.version_info >= (3, 7), "请在Python3.7及以上环境执行"

如果这个项目最低要求最低是 Python3.7 的环境,那么如果使用 Python3.6 来运行这个项目,就会出现这个错误信息。

Traceback (most recent call last):
File "/Users/chennan/pythonproject/demo/nyandemo.py", line 3, in 
assert sys.version_info > (3, 7), "请在Python3.7以上环境执行"

AssertionError: 请在Python3.7以上环境执行

提前中止项目

2.巧妙的放置逗号

合理的格式化列表里面的元素,更容易维护

一般我们在写列表的时候会这样

l = ["apple", "banana", "orange"]

使用下面的方式可以更加清晰的区分每一个项目,习惯性的在末尾加个逗号,防止下次添加元素遗漏了逗号,看着也更 Pythonic

l = [
"apple",
"banana",
"orange",
]

3.下划线、双下划线以及其他

前置单下划线 : _var

1.是一种约定,前置单下划线的方法和变量只在内部使用

2.在使用通配符导包的使用 from xx import * 这种,不用导入前置单下划线的变量,除非定义了 __all__ 覆盖了这个行为。 PEP8 一般不建议通过这种方式导包。

后置单下划线: var_

如果使用的变量名被 Python 中的关键字占用,比如要声明 class 这个变量,我们这时候可以在其后面加个单下划线 class_

这个也是 PEP8 里约定的

前置双下划线: __var

前置双下划线会让 Python 解释器重写属性名称,防止被子类中的命名覆盖。

class Test:
def __init__(self):
self.foo = 11
self.__bar = 2
t = Test()
print(dir(t))

查看类的属性可以发现 self.__bar 变为了 _Test__bar,这也称之为名称改写 (name mangling),解释器会更改变量的名称,防止拓展这个类型时命名冲突。

['_Test__bar', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'foo']

这个时候如果想访问 __bar, 怎么办呢,我们可以通过 t._Test__bar进行访问。

如果我们继承一下Test然后重写 __bar会咋样呢

class ExtendTest(Test):
def __init__(self):
super().__init__()
self.foo = "overridden"
self.__bar = "overridden"
et = ExtendTest()
print(et.foo)
print(et.__bar)

发现出现了错误

AttributeError: 'ExtendTest' object has no attribute '__bar'

原因就是前面的一样,因为解释器把 __bar的名字给改了防止父类的这个变量被改写了。

我们可以分别访问这两个类的 __bar发现他们是同时存在的,确实没有被覆盖。

print(et._Test__bar)
print(et._ExtendTest__bar)

得到结果

2

overridden

顺便说下 __bar 在英语中一般都是叫做 dunderbar。

除了双下划线的变量,双下划线的方法名也可以被解释器名称改写。

class ManglingMethod:
def __mangled(self):
return 42
def call_it(self):
return self.__mangled()
md = ManglingMethod()
md.call_it()
md.__mangled()

运行之后得到出错信息

AttributeError: 'ManglingMethod' object has no attribute '__mangled'

前后双下划线: _var_

所谓的魔法方法,它的名称不会被解释器所改变,但是就命名约定而言最好避免使用这种形式变量和方法名

单下划线: _

1._ 可以表示变量是临时的或者是无关紧要的

for _ in rang(5):
print("hello")

2.在数字之前使用还可以当作是千位分隔符

for i in range(1000_000):
print(i)

3.在解包元组的时候可以当作是占位符。

car = ("red", "auto", 12, 332.4 )
color,_,_,mileage = car
print(color)
print(_mileage)

4.如果使用命令行模式的话,_ 可以获取先前计算的结果

>>> 20+5
25
>>> _
25
>>> print(_)
25

4.自定义异常类

我们有以下代码

def validate(name):
if len(name) < 10:
raise ValueError

如果在其他文件中调用这个方法,

validate("lisa")

在不理解这个方法的作用的时候,如果名字验证失败时,调用栈会打印出以下信息

Traceback (most recent call last):
File "/Users/chennan/pythonproject/demo/nyandemo.py", line 57, in 
validate("lisa")
File "/Users/chennan/pythonproject/demo/nyandemo.py", line 55, in validate
raise ValueError
ValueError

这个栈调试回溯中的信息指出了,出现了错误的值,但是并不知道为什么出错了,所以这个时候就需要跟进这个 validate 一探究竟,

这个时候我们就可以自己定义一个异常类

class NameTooShortException(ValueError):
def __str__(self):
return "输入的名字长度必须大于等于10"
def validate(name):
if len(name) < 10:
raise NameTooShortException(name)
validate("lisa")

这样如果再出现错误,就可以知道为什么错了,同时调用法也方便捕获指定的异常,不用再使用 ValueError。

try:
validate("lisa")
except NameTooShortException as e:
print(e)

5.Python字节码

Cpython 解释器执行时,首先将其翻译成一系列的字节码指令。字节码是 Python 虚拟机的中间语言,可以提高程序的执行效率

Cpython 不直接执行人类可读的源码,而是执行由编译器解析和语法语义分析产生的紧凑的数、变量和引用。

这样,再次执行相同程序时能节省时间和内存。因为编译步骤产生的字节码会以 .pyc 和 .pyo 文件的形式缓存在硬盘上,所以执行字节码比再次执行相同的Python文件速度更快。

def greet(name):
return 'hello, ' + name + '!'
#__code__可以获取greet函数用到的虚拟机指令,常量和变量
gc = greet.__code__
print(gc.co_code) # 指令流
print(gc.co_consts) # 常量
print(gc.co_varnames) # 传过来的参数
dis.dis(greet)

结果

b'd\x01|\x00\x17\x00d\x02\x17\x00S\x00'
(None, 'hello, ', '!')
('name',)
70 0 LOAD_CONST 1 ('hello, ')
2 LOAD_FAST 0 (name)
4 BINARY_ADD
6 LOAD_CONST 2 ('!')
8 BINARY_ADD
10 RETURN_VALUE

解释器在索引1处('hello, ')查找常量,并放入栈中,然后将 name 的变量内容放入栈

Cpython 虚拟机是基于栈式虚拟机,栈就是虚拟机的内部数据结构。

栈只支持两种动作:入栈和出栈

入栈:将一个值添加到栈顶

出栈:删除并返回栈顶的值。

假设栈初始为空,在执行前两个操作码(opcode)之后,虚拟的内容(0是最上面的元素)

比如我们传入的name为lisa.

0: 'lisa'
1: 'hello, '

BINARY_ADD 指令从栈中弹出两个字符串值,并将他们连接起来

然后再次将结果压入栈中。

0:'hello, lisa'

然后由下一个 LOAD_CONST 将'!'压入栈。

此时的结果

0:'!'
1:'hello, lisa'

下一个 BINARY_ADD 操作码再次将这两个字符串从栈中弹出并连接之后压入栈,生成最终结果

0:'hello, lisa!'

最后字节码 RETURN_VALUE ,告诉虚拟机当前位于栈顶的是该函数的返回值。