ReThinking In Python

ReThinking In Python

0x00 前言

本文诞生于利用 Topic Reading 方法读 Python 和 JavaScript 若干本技术书籍这个过程中结合自己的开发常见场景记录下来的一些笔记。

0x01 Python 胡论

Why Python

是要用一门编程语言无非是两种原因:

  • 这门技术很火,能挣钱
  • 写起来很舒服,开发效率高

这也是我在涉猎了很多编程语言为什么选择了 Python 和 TypeScript 作为自己的主要技能树。

Python 具备这两点,TS (更加准确的说是 JavaScript)具备前一点。

Python 写起来真的特别舒服,语法简洁,第三方库丰富,而且也比较火。

有什么东西比,写代码效率高、生态圈好还重要了。

生态圈好,比如

  • Web 开发用 Django/Flask
  • 数据抓取用 Requests
  • 数据分析清洗用 Pandas
  • 机器学习 Tensorflow SCIPY

工具链

Pythonista 的工具集

文档

社区

书籍

0x02 基础概念

2.1 数据类型

常量

1
2
3
4
5
False
True
None
NotImplemented
Elilipsis ...
布尔

布尔常量

1
2
3
4
5
6
None
False
0 0.0 0j
'' () []
{}
一个对象 __bool__() = False , 如果上一个为 True 则__len__()

布尔运算符

1
2
3
x or y
x and y
not x

布尔比较值

1
2
3
4
# 可以定制
< <= >= > == !=
# 无法定制
is / is not
字符串

五种 format 方式

  1. 古代 %
  2. 近代 format
  3. 现代 f 字符串
  4. 内置的 template
  5. jinja2 的模板

数字类型

  • int
  • float
  • complex
操作符
1
2
3
4
5
6
7
8
9
10
+ - * / // % -n +n abs() int() float()
complex(re,im)
c.conjugate()
divmod(x,y)
pow(x,y) x ** y
math.trunc(x)
math.round(x[,n])
math.floor(x) <=x
math.ceil(x) >=x
| ^ & << >> ~x

注意

1
2
(-1) / 2 # -1
1 / (-2) # -1
数值哈希

// TODO : 麻蛋居然没看懂 4.4.4. Hashing of numeric types

迭代器类型

迭代器类型

序列类型

C 实现的按照 item 是否为同一类型分为:

  • Container sequences: list, tuple, and collections.deque can hold items of different types.
  • Flat sequences: str, bytes, bytearray, memoryview, and array.array hold items of one type.

C 实现按照 item 是否可修改分为:

  • Mutable sequences: list, bytearray, array.array, collections.deque, and memoryview
  • Immutable sequences: tuple, str, and bytes
通用序列操作
1
2
3
4
5
6
7
8
9
10
11
12
13
x in s
x not in s
s + t
s * n 或者 n * s
s[i]
s[i:j]
s[i:j:s]
len(s)
min(s)
max(s)
s.index(x,i,j)
s.count(x)
// TODO 封装 deepEqual

切片

为何 Slice 和 Range 会排除 最后一个 Item?

书中讲的太复杂,其实这个和尺子是一个作用,尺子从 0 刻度开始,这样方便丈量。

比如说:

  • items[0:10] 为 10 厘米
  • items[10] 为 10 刻度后一个单位,即 items[10:11]
  • items[2:] 为 2 刻度后面若干个单位
  • items[::3] 以三为单位,从 0 刻度开始,最后为结尾,每三个
1
a[i, j] # 调用 a.__getitem__((i, j))
不可变序列
  • 解包赋值
  • 不要手贱加逗号
  • 下划线可以用作临时变量 (但是 django 中下划线用于中英文)

    1
    2
    3
    4
    5
    6
    a, b, *rest = range(5) # (0, 1, [2, 3, 4])
    a, b, *rest = range(3) # (0, 1, [2])
    a, b, *rest = range(2) # (0, 1, [])
    a, *body, c, d = range(5) # (0, [1, 2], 3, 4)
    *head, b, c, d = range(5) # ([0, 1], 2, 3, 4)
  • namedtuple

    1
    2
    3
    4
    5
    6
    City = namedtuple('City', 'name country population coordinates')
    tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
    tokyo.population
    tokyo.coordinates
    tokyo[1]
    City._fields # tuple
可变序列
1
2
3
4
5
6
7
8
9
s[i] = x
s[i:j] = t
del s[i:j]
s[i:j:k] = t
del s[i:j:k]
s.append(x)
s.clear()
s.copy()
s.extend(t) 或者 s += t
List Comprehensions and Generator Expressions
1
2
3
4
5
new_items = [func(a) for item in items]
new_items = [ str(x) for x in range(100) if x % 2 == 0]
new_items = list(map(str,list(filter(lambda x: x % 2 == 0 , list(range(100))))))
# 可写成
new_items = list(map(str,filter(lambda x: x % 2 == 0 , range(100))))

list 往往和 map filter 以及 listcomp 用于创建简单的序列

序列赋值
1
2
3
4
5
6
7
8
9
对于不可变类型 赋值 l *= 2 在内存中则是创建了新的两个长度的元祖,然后赋值
而由于字符串则需要注意,str_a += "str b" , 虽然为不可变变量,但并不需要拷贝整个字符串(特殊情况), 但字符串的拼接建议还是"".join()
t = (1, 2, [30, 40])
t[2] += [50, 60]
# 结果为既赋值成功,又报错
# 但 t[2].extend([50, 60]) 可以赋值成功
import dis
dis.dis('s[a] += b') # 可以查看字节码
  1. 尽量不要在不可变变量内保存可变变量
  2. t[2] += [50,60] 并不是原子操作,因为,当做了一半的时候,抛出的错误。
objs.sort 与 sorted()

对于 sort 和 sorted 来说,reverse 代表 desc,key 为单参数用于计算每一个值的结果的函数。
list.sort 直接针对列表排序,并且返回 None(出于编程习惯的问题,直接返回 None 的函数大多是对程序有一定的修改)

二分搜索
1
2
3
4
5
6
7
8
9
10
11
import bisect
bisect -> bisect_right
bisect_left
insort -> insort_left
insort_right
# 搜索可以用来划分档次
def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
i = bisect.bisect(breakpoints, score)
return grades[i]
[grade(score) for score in [33, 99, 77, 70, 89, 90, 100]] # ['F', 'A', 'C', 'C', 'B', 'A', 'A']
列表

list 是一种 mix-typed 的数据结构,即可以存放不同种类型的数据结构,由此带来的问题自然是性能问题:

  • list 第一是 mix-typed 的数据结构
  • 动态数组,并非数组

当考虑性能的时候,则需要考虑是不是要换一个更好的数据结构:

  • 适用于类型单一的 array
  • 增删比较多,或者需要使用 FIFO,LIFO, 则使用 deque (double-ended queue)
1
2
3
4
5
# Arrays
floats = array('d', (random() for i in range(10**7)))
# NumPy and SciPy
# Deques and Other Queue
Queue 与 Deque
  • Deque
  • queue 线程安全 Queue, LifoQueue, and PriorityQueue
  • multiprocessing Queue 和 JoinableQueue
  • asyncio Queue, LifoQueue, PriorityQueue, and JoinableQueue

2.2 语句

语句

2.3 函数

参数

闭包与作用域

本部分具备一定文字量,故单独抽取出来到文章,请参考 Python 中的闭包和作用域。

高阶函数

  • map
  • reduce
  • filter

特殊方法

1
2
3
4
obj.__len__()
len()
obj.__

对于内置类型 (list, str , bytearray) 解释器在调用特殊方法的时候调用 C 库,比如 CPython 实现的 len 方法一般直接会调用 PyVarObject C Struct ob_size

特殊方法往往并不是显示调用,而是被隐式调用。比如 init 在 new 中的作用,比如 for item in items 世界上会调用 iter(items), 这也会隐式调用 items.iter() .

一般当大量使用特殊方法的时候,都是在进行元编程。

1
2
bool(x) 先调用 x.__bool__() , 如果 x.__bool__() 没有实现,则调用 x.__len__(), 如果为 0 则返回 False
sorted(arr) 可以直接返回 arr,arr.sort() 是排序内部。

特殊方法名 (有操作符)

种类 方法名
String/Bytes repr , str , format , bytes
Conversion to number abs , bool , complex , init , float , hash , index
Emulating collections len , getitem , setitem , delitem , contains
Iteration iter, reversed , next
Emulating callables call
Context management enter, exit
Instance creation & destruction new , init , del
Attribute management getattr , getattribute , setattr , delattr, dir
Attribute descriptors get , set ,delete
Class service prepare , instancecheck , subclasscheck

特殊方法名 (无操作符)

种类 方法名
Unary numeric operators neg , pos , abs
Rich comparison operators lt , le , eq , ne , gt , ge
Arithmetic operators add ,sub , mul ,truediv ,floordiv ,mod , divmod , pow , round round
Reversed arithmetic operators radd , rsub , rmul , rtruediv, rfloordiv , rmod , rdivmod, rpow
Augmented assignment mathmatic iadd , isub , imul , itruediv, ifloordiv
Bitwise operators invert , lshift , rshift , and , or
Reversed bitwise operators rlshift , rrshift , rand , rxor , ror
Augmented assignment bitwise ilshift , irshift , iand , ixor , ior

Why len Is Not a Method

1
因为对 不同类型并不是一定调用 __len__ , 对于基本类型查看 c struct 中长度,对于其他类型直接调用 __len__ , 这种区分对待

2.4 生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def gen():
yield 1
yield 2
yield 3
# 这里为了省事,标记 123, 但是一般会有个循环,或者多个循环
gen # <function __main__.gen>
# 显式调用,返回方法对象
g = gen() # <generator object gen at 0x10ec23dc0>
next(g)
next(g)
next(g)
next(g) # 执行到结尾部分或者其他报错 StopIteration
for i in gen():
print(i)

生成器的作用就在于将遍历 lazy 化。嗯?其实编写代码的时候完全不中写生成器也可以 lazy 化很多操作。

需要注意的是,generator 后面支持了一个方法叫做 send(), 是 next() 的升级版本。将原来的数据的单向流通变成了双向流通。

协程

0x03 中级概念

类和对象

包含元编程

模块与包

单下划线与双下划线

1
2
3
4
5
6
7
8
9
10
11
12
前缀单下划线 _var # 在类中被认为是私有变量,在模块中可以通过 amodule._func() 来使用,但是没有办法 from xx.amodule import * 然后调用。
后缀单下划线 var_ # 一般用于表示被占用的关键字 比如 default_ int_ class_ object_
前缀双下划线 __var # 放在类中的话,实例化的时候会被转成'_A__size', 这个解释器进行的操作叫做 name mangling
In [13]: class A:
...: def __init__(self):
...: self.__eq__ = 2
...: self.__size = 34
...: self.__size__ = 44
前后双下划线 __var__
单下划线 _

错误 / 调试测试

IO 编程

正则表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
. ^ $ * + ? { } [ ] \ | ( )
Regular String
"ab*"
"\\\\section"
"\\w+\\s+\\1"
Raw string
r"ab*"
r"\\section"
r"\w+\s+\1"

0x04 高级概念

元编程

装饰器

1
2
3
4
enforcing access control and authentication
instrumentation and timing functions
rate-limiting
caching, and more”

Dynamic Attributes and Properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
obj.attr
重写__getattr__
// TODO: 什么时候完成 python 的 DICT 以及 JSON 的相等?
accessor?
__new__ 是一个 class method, 但是并没有 xxx
x = Foo('a')
def object_maker(the_class, some_arg):
new_object = the_class.__new__(some_arg)
if isinstance(new_object, the_class):
the_class.__init__(new_object, some_arg)
return new_object
x = object_maker(Foo,'a')

Attributes Descriptors

1
2
3
4
5
6
7
8
9
10
@property
__class__ # 接近 type()
__dict__
__slot__
dir(obj) # 与__dict__接近
getattr
setattr
hasattr

Class MetaProgramming

并发编程

GIL - Global Interpreter Lock

并不是所有的解释器语言都有 GIL (尽管 Python 和 Ruby 里面都有), 也并不是没有尝试过去除 GIL, 但是每次去除都会导致单线程性能的下降。所以暂时保留。

GIL 对程序中的影响:

一个线程运行 Python , 而其他 N 个睡眠或者等待 I/O - 同一时刻只有一个线程对共享资源进行存取 , Python 线程也可以等待 threading.Lock 或者线程模块中的其他同步对象;

协同式多任务处理

如果有两个线程,同时进行 IO 请求,当其中一个线程连接之后,立即会主动让出 GIL, 其他线程就可以运行。

当 N 个线程在网络 I/O 堵塞,或等待重新获取 GIL,而一个线程运行 Python。

让出之后还要执行代码呀,所以要有个收回 GIL 的动作。

抢占式多任务处理

Python 2 GIL , 尝试收回 GIL 为 执行 1000 字节码。
Python 3 GIL , 尝试收回 GIL 检测间隔为 15ms

线程安全

原子操作:sort 之类不需要
非原子操作:n=n+2 的字节码分为 加载 n , 加载 2 , 相加,存储 n, 四个步骤,由于不是原子性,很可能被由于 15 ms 而被打断。

当然,懒人一向是 : 优先级不决加括号,线程不决加 lock

对于 Java, 程序员努力在尽可能短的时间内加锁存取共享数据,减轻线程的争夺,实现最大并行。但 Python 中,线程无法并行运行,细粒度的锁就没有了优势。

多线程

Python 多线程约等于并发。

多进程

协程

Python 中,协程在语法上接近于生成器(函数内包含 yield 关键字).

1
2
3
4
# 生成器
def g():
yield a
pass
1
2
3
4
5
# 协程
def c():
# b = yield a
b = yield
pass

协程在

0x05 标准库与第三方库

数据结构与算法

字符串与文本

数字日期与时间

迭代器与生成器

  1. Introduction
  2. Built-in Functions
  3. Built-in Constants
  4. Built-in Types
  5. Built-in Exceptions
  6. Text Processing Services
  7. Binary Data Services
  8. Data Types
  9. Numeric and Mathematical Modules
  10. Functional Programming Modules
  11. File and Directory Access
  12. Data Persistence
  13. Data Compression and Archiving
  14. File Formats
  15. Cryptographic Services
  16. Generic Operating System Services
  17. Concurrent Execution
  18. Interprocess Communication and Networking
  19. Internet Data Handling
  20. Structured Markup Processing Tools
  21. Internet Protocols and Support
  22. Multimedia Services
  23. Internationalization
  24. Program Frameworks
  25. Graphical User Interfaces with Tk
  26. Development Tools
  27. Debugging and Profiling
  28. Software Packaging and Distribution
  29. Python Runtime Services
  30. Custom Python Interpreters
  31. Importing Modules
  32. Python Language Services
  33. Miscellaneous Services
  34. MS Windows Specific Services
  35. Unix Specific Services
  36. Superseded Modules
  37. Undocumented Modules

0xAA 测试

单元测试

单元测试我只用 pytest

  1. 给对象打补丁
  2. 测试异常情况
  3. 测试输出到日志文件
1
2
pytest --pdb
pytest --tb=long # exhaustive, informative traceback formatting

0xBB 调试技巧

IPython

IPdb

0xCC 优化技巧

1
cProfile

统计型优化

https://github.com/what-studio/profiling

0xDD 代码质量

社区推崇的代码风格 Pythonic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Python 代码质量

1
https://github.com/ambv/black

正确性

  • 外部不该引用 protected member (单下划线)
  • lambda 为一次使用,最好不要赋值。
  • 不要给 buildin 函数赋值
  • py3 直接 super()
  • for in else 如果不内置 break 则出会在最后 for in 为 empty 的时候再执行 else 中的语句
  • context exit 如果不 catch 掉异常让其自然向上一级抛出错误的话,必须为 (self, exception_type, exception_value, traceback):
  • 不要在 init 里面 return 数据
  • 不要混用 tab 和 space
  • 4 个 space 缩进
  • staticmethod 直接是 参数,classmethod 第一个参数为 cls
  • 可变的 default value 是不能作为 参数的。(可能是解释器在确定函数的定义的时候完成赋值?)
  • 遵循 exception hierachy https://docs.python.org/3/library/exceptions.html#exception-hierarchy
  • defaultdict defaultdict(lambda : 6) , 必须 callable
  • 尽量 unpack 赋值
  • 字典用获取用 get(“myk”,None) , 赋值用 dictionary.setdefault(“list”, []).append(“list_item”)

可维护性

  • 避免使用 import * , 我觉得这点值得商榷 , 如果是某个模块下,完全可以先把模块拆分成多个,最后 import 进来,接着使用 all.
  • getxxx 获取实际值,如果不为实际值,返回 None 显然不如 try catch 来的实在。
  • 避免使用 global
  • 命名要注意
  • 动态创建方法 , 我觉得这点值得商榷。

可读性

  • 不要检查,如果可能有异常,尽量抛出异常来 trycatch 解决。
  • a is None , if flag
  • isinstance , not type(r) is types.ListType
  • “{name}{city}”.format(**info_dict)
  • for k , v in infodict.items()
  • 使用 poiinfo = namedtuple(“poiinfo”,[“name”,”lng”,”lat”]) 返回 poiinfo[‘上海’,121.00,23] 最后返回值打印 poi.name , poi.lng , poi lat
  • for numbers_value, letters_value in zip(numbers, letters):
  • enumerate
  • 如果能用 listcomp 则不使用 map 和 filter

性能

  • 用 set
  • d.iteritems() 比 items() 省内存

0xEE 文章更新

  • 2017-05-11 19:43:00 : 增加代码质量模块
  • 2017-08-04 19:43:00 : 增加部分 Fluent Python 的笔记