本文诞生于利用 Topic Reading 方法读 Python 和 JavaScript 若干本技术书籍这个过程中结合自己的开发常见场景记录下来的一些笔记。
是要用一门编程语言无非是两种原因:
这也是我在涉猎了很多编程语言为什么选择了 Python 和 TypeScript 作为自己的主要技能树。
Python 具备这两点,TS (更加准确的说是 JavaScript)具备前一点。
Python 写起来真的特别舒服,语法简洁,第三方库丰富,而且也比较火。
有什么东西比,写代码效率高、生态圈好还重要了。
生态圈好,比如
False
True
None
NotImplemented
Elilipsis ...
布尔常量
None
False
0 0.0 0j
'' () []
{}
一个对象 __bool__() = False , 如果上一个为 True 则__len__()
布尔运算符
x or y
x and y
not x
布尔比较值
# 可以定制
< <= >= > == !=
# 无法定制
is / is not
五种 format 方式
+ - * / // % -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
1 / (-2) # -1
// TODO : 麻蛋居然没看懂 4.4.4. Hashing of numeric types
迭代器类型
C 实现的按照 item 是否为同一类型分为:
C 实现按照 item 是否可修改分为:
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 刻度开始,这样方便丈量。
比如说:
a[i, j] # 调用 a.__getitem__((i, j))
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)
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
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
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 用于创建简单的序列
对于不可变类型 赋值 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') # 可以查看字节码
对于 sort 和 sorted 来说,reverse 代表 desc,key 为单参数用于计算每一个值的结果的函数。
list.sort 直接针对列表排序,并且返回 None(出于编程习惯的问题,直接返回 None 的函数大多是对程序有一定的修改)
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 的数据结构,即可以存放不同种类型的数据结构,由此带来的问题自然是性能问题:
当考虑性能的时候,则需要考虑是不是要换一个更好的数据结构:
# Arrays
floats = array('d', (random() for i in range(10**7)))
# NumPy and SciPy
# Deques and Other Queue
语句
本部分具备一定文字量,故单独抽取出来到文章,请参考 Python 中的闭包和作用域。
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() .
一般当大量使用特殊方法的时候,都是在进行元编程。
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
因为对 不同类型并不是一定调用 __len__ , 对于基本类型查看 c struct 中长度,对于其他类型直接调用 __len__ , 这种区分对待
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() 的升级版本。将原来的数据的单向流通变成了双向流通。
见 协程
包含元编程
前缀单下划线 _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__
单下划线 _
. ^ $ * + ? { } [ ] \ | ( )
Regular String
"ab*"
"\\\\section"
"\\w+\\s+\\1"
Raw string
r"ab*"
r"\\section"
r"\w+\s+\1"
enforcing access control and authentication
instrumentation and timing functions
rate-limiting
caching, and more”
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')
@property
__class__ # 接近 type()
__dict__
__slot__
dir(obj) # 与__dict__接近
getattr
setattr
hasattr
并不是所有的解释器语言都有 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 关键字).
# 生成器
def g():
yield a
pass
# 协程
def c():
# b = yield a
b = yield
pass
协程在
单元测试我只用 pytest
pytest --pdb
pytest --tb=long # exhaustive, informative traceback formatting
cProfile
https://github.com/what-studio/profiling
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!
https://github.com/ambv/black