空格
括号内侧,左括号,右括号前面,不加空格,多重括号不加空格
a = ((b + c)*d - 5)
导入
# 不写成一行导入,分多行
import sys
import os
# 不能使用 from xxx import *
from xxx import yyy
注释
函数
def load_batch(path):
"""
功能描述
参数
返回值
异常描述
"""
all
判断可迭代参数是否全是true,是的话返回true,否则返回false
all(['a', 'b', 'c', 'd']) true
Counter
计数器,追踪元素的出现次数
obj = collections.Counter("aabbcc") # Counter({'a': 2, 'b': 2, c: '2'})
obj.elements() ['a', 'a', 'b', 'b', ...] # 遍历打印所有元素
obj.most_common(2) # 打印前n个次数最多的
obj.update(['22', '55']) # 增加元素 subtrace 减少元素
items 枚举
类
class TreeError():
"""
功能描述
接口
"""
命名
Classes CapWords
Exceptions CapWords
Global/Class Constants CAPS_WITH_UNDER 常量用大写
Modules lower_with_under
Packages lower_with_under
instance Variables lower_with_under
method names lower_with_under
method parameters lower_with_under
local variables lower_with_under
# 私有方法
self._member = 1 # 类的私有变量
self._privite_func(self) # 类的私有函数
self.__update() # 会被解释器修改为_ClassName__update,防止与子类的同名函数冲突
编码
判断
is 判断是否指向一个对象
== 调用eq方法判断是否等价(判断两对象的值是否相等)
# 与None比较用is
bad_inst is None
_ all_
__all__可以放入 from module import *导出的方法
__all__ = ["external_function"]
def external_function():
pass
def internal_function():
pass
字符串
浮点
# 精确计算使用decimal模块
from decimal import Decimal, getcontext
print('%.20f' % 3.14)
print(Decimal('3.14')) # 精确始终以两位小数表示
getcontext().prec = 6
Demical(1) / Demical(7) # Decimal('0.142857')
拼接
使用% format join代替+ +=完成字符串格式化
x = '{}, {}'.format(impreactive, expletive)
函数
@static和@class
class A(object):
def m1(self, n):
print("self:", self)
@classmethod
def m2(cls, n):
print("cls", cls)
@staticmethod
def m3(n):
pass
a=A()
a.m1(1) # self: <__main__.a object at>
a.m2(1) # cls:
a.m3(1)
代码首先运行class创建类A对象,初始化类属性方法,接着执行a=A(),调用构造器,构造实例对象a,调用a.m1(1),self和a都指向实例对象,A.m2(1),python内部隐式的姜磊对象传给cls,cls和A都指向类对象
# 静态方法使用场景:不需要访问任何实例方法或属性
# 类方法:使用工厂方法创建实例对象
class date:
def __new__(cls, year, month=None. day=None):
self = object.__new__(cls)
self._year = year
self._month = month
self._day = day
return self
@classmethod
def fromtimestamp(cls, t):
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
return cls(y, m, d)
可变参数不使用默认值
参数的默认值在方法定义执行时就设定了,默认值只会被设定一次,当函数定义后,每次调用会进行预计算,如果参数是列表或字典,方法修改这个值会影响到下一次调用
def foo(bar=[]): #
bar.append("baz")
return bar
foo() # ["baz"]
foo() # ["baz", "baz"]
# 修改
def foo(bar=None):
if bar is None: not
bar = []
列表
预留空间
list在append时会分配一块更大的内存,将原来成员拷贝到新内存,再将新append的成员也拷贝到新内存空间,释放来内存
members = []
for i in range(1, 10000000):
members.append(i)
改为:
members = [None] * 1000000
切片
a = [1,2,3,4,5,6,7,8]
a[2::2] # 3 5 7
a[-2::-2] # 7 5 3 1
a[-2:2:-2] # 7 5
a[2:2:-2] # []
元祖
内容不变用tuple代替list
list是动态的数组,而tuple是静态数组,list需要更多空间跟踪成员状态,另外,小于等于20的元祖,python会对其缓存,当元祖不再使用,并不会立即释放内存,而是保留以备后用
字典
使用dict.get()获取
检查
类型检查
# 用isinstace去检查参数类型,不能用is, len(), 均不安全
def sample_sort_list(sample_inst):
if sample_inst is []: # 会被()绕过
...
if not isinstace(sample_inst, list):
raise TypeError()
循环
推导式
# 推导式(comprehension)精炼写法
odd_num_list = [i for i in range(100) if i % 2 == 1]
# for太多场景用itertools
cubes = [(x, y, z) for x in lengths for y in widths for x in heights]
cubes = list(itertools.product(lengths, widths, heights))
enumerate
# 在处理单个元素时,可以使用enumerate给元素加上序号形成元祖
my_list = ['a', 'b', 'c']
for x in enumerate(my_list):
print(x)
迭代器
list comprehension可以用来替代lamda表达式的map,reduce,从已有list生成新数据,而generator comperhension无需定义一个包含yield语句的函数,就可以生成新的generator,二者在内存上相差悬殊,时间上差不多
错误:even_cnt = len([x for x in range(10) if x % 2 == 0])
正确:enen_cnt = sum(1 for x in range(10) if x % 2 == 0)
异常
finally
# 使用finally保证对象释放
handle = open(r"/tmp/sample_data.txt")
try:
data = handle.read()
except UnicodeDecodeError as decode_error:
print(decode_error)
finally:
handle.close()
# finally中避免使用return或break,会将前面暂存的异常丢弃掉
try:
1/0
finally:
return 42
返回42 不会报错
try:
return 'try'
finally:
return 'finally'
返回finally
并行
GIL
python通过CPython, PyPy, Psyco等不同的解析器来解析执行,GIL是global interpreter lock,为了防止多线程并发执行机器码的互斥锁(mutex)
IO IO io IO
thread1 运行 | 运行 |
thread1 | 运行 |
thread3 | 运行 |
release GIL|acquire GIL
单核CPU调度多个任务,共享一个全局锁,在CPU执行的线程得到锁,直到IO操作或timer tick让出CPU,其余时间只能等待
但在多核中,线程2需要在CPU2中执行,必须等待CPU1上执行的线程1释放GIL(全局),如果线程1是由于IO阻塞让出GIL,线程2必定拿到锁,但是如果1是因为timer ticks计数满100让出GIL,线程1会和2公平竞争,python2.x中线程1不会动态调整优先级,因此大概率2抢不过1,2只能眼睁睁看着1在CPU执行。。
扩展到8核,一核有难,八核围观,多核多线程比单核多线程更差,因为单核下多线程每次释放GIL,唤醒的那个线程能立即获取到GIL无缝执行,而多核下CPU0释放GIL后,其他核的线程会竞争锁,但GIL可能又会被CPU0拿到,导致其他核被唤醒的线程会醒着等待到切换时间后进入待调度状态,造成线程颠簸(thrashing),效率更低
因此,多核下python要想充分利用多进程,就用多进程,每个进程都有各自的GIL,效率自然更高
# 例
def counter():
i = 0
for _ in range(100000):
i = i + 1
return True
def main1():
for tid in range(2):
t = threading.Thread(target=counter)
t.start()
t.join()
def main2():
thread_dict = {}
for tid in range(2):
t = threading.Thread(target=counter)
t.start()
thread_dict[tid] = t
for i in range(2):
thread_dict[i].join()
main1()
main2()
python2中7s,24s
python3中14.39s,14.10s
因为main1在第一次循环就阻塞了,第二个线程还没起,相当于串行,main2两个线程并行,因为GIL的存在,1和2不断的抢占切换消耗时间
线程安全
线程释放GIL有两种情况:
1. 线程A进入IO操作主动释放GIL,由于A的IO操作等待时间不定,等待的B一定能得到GIL锁
2. 线程A因为解释器执行了1000字节码的指令或不间断运行了15ms放弃了GIL,A和B会同时竞争GIL锁,python3会动态调整线程的优先级,线程B的优先级较高,也不一定B就抢到锁
n = 0
def add():
global n
n = n + 1
def sub():
global n
n = n - 1
加法,减法非原子操作,分为
1.LOAD_GLOBAL(加载全局变量) 2.LOAD_CONST(加载常量) 3.二进制加/减法 4.STORE_GLOBAL(存储全局变量),当加减法并行,加法首先拿到锁,执行到第三步,减法抢到GIL,此时n还是0,也执行到第三步,加法抢去,将运算结果给全局n=1,反之开始是减法,被加法抢去后,最后变成-1,这样导致线程1和2分别对n进行了10000次运算后不是0
总结:多线程不是IO密集型,并且计算不是原子级操作时,才需要考虑线程安全问题
使用场景
1. 在IO密集型(文件操作)应用中,由于存在GIL,线程中的IO操作会使得线程立即释放GIL,切换到其他非IO线程继续操作,提高执行效率
def slow_systemcall(n):
for x in range(100):
open("test_%s" % n, "a").write(os.urandom(10) * 100000)
for _ in range(N):
slow_systemcall(_) 5.179
for _ in range(N):
thread = Thread(target=slow_systemcall, args=("t_%s"%_,)) 1.451
2. 计算密集型(循环,计数)应用,ticks会很快达到计数阈值,触发GIL释放再竞争,多个线程来回切换消耗资源,效率低下
def factorize(number):
for i in range(1, number + 1):
if number % i == 0:
yield i
class FactorizeThread(Thread): def __init__(self, number):
Thread.__init__(self)
self.number = number
def run(self):
self.factors = list(factorize(self.number))
for number in numbers:
list(factorize(number)) 0.319
for number in numbers:
thread = FactorizeThread(number)
thread.start()
threads.append(thread) 0.539
for t in threads:
t.join()
文件操作
with
推荐使用with,自动关闭文件
whith open(r'') as somefile:
for line in somefile:
print(line)
拷贝
copy,deepcopy
copy浅拷贝:拷贝一个对象,但对象的属性还是引用原来的,对于可变类型,列表字典,只复制引用,基于引用所做的改变还是会影响到被引用对象
deepcopy:深拷贝,创建一个新容器对象,包含原有对象元素全新拷贝的引用
对于字符串和其他原子对象,不存在拷贝说法,只是新创建对象,替换老的
a = [1, 2, ['x', 'y']]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
a.append(3)
a[2].append('z')
a.append(['x', 'y'])
a # [1, 2, [x, y, z], [x, y]]
b # [1, 2, [x, y, z], [x, y]]
c # [1, 2, [x, y, z]]
d # [1, 2, ['x', 'y']]