王炳明的《Python 黑魔法手册》的学习笔记。
小整数池
在同一行里,同时给两个变量赋同一值时,解释器知道这个对象已经生成,那么它就会引用大同一个对象。如果分成两行的话,解释器并不直达这个对象已经存在了,就会重新申请内存存放这个对象。
intern机制
site-packages和dist-packages
dist-packages是debian系的Linux系统(如Ubuntu)才有的目录,当使用apt去安装的Python包会使用dist-packages,而使用pip或者easy_install安装的包才会安装在site-packages下。
argument和parameter
- parameter:形参(formal parameter),体现在函数内部,作用域是这个函数体。
- argument:实参(actual parameter),调用函数实际传递的参数。
/usr/bin/python 和 /usr/bin/env python
-
脚本或者项目的入口文件里的第一行 # ! / u s r / b i n / p y t h o n \#!/usr/bin/python #!/usr/bin/python,是执行Python进入console模式里的Python。
-
执行 e n v p y t h o n env python envpython时,会去 e n v ∣ g r e p P A T H env | grep PATH env∣grepPATH的路径里依次查找名为Python的可执行文件。找到一个就执行。
空字典生成{}方法比dict()方法运行速度快
return和try…finally
IDE环境和CMD环境下运行结果不相同
时有时无的切片异常
查看包搜索路径的方法
安装包方法
python3.6 -m pip install xxx
连接列表的方法
-
直接相加
-
itertools方法
itertools内置模块专门用于操作可迭代对象。使用itertools.chain()函数先将可迭代对象串联起来,组成一个更大的可迭代对象,然后再利用list将其转化为列表。
-
使用 ∗ * ∗解包
∗ * ∗可以解包列表,解包后再合并。
-
使用extend
-
使用列表推导式
-
使用heapq
heapq是Python的标准模块,提供了堆排序算法的实现,该模块里面的merge方法,可以用于合并多个列表。
heapq.merge得到的是一个始终有序的列表,因为它采用堆排序,效率非常高。
-
魔法方法
直接相加实际是作用在KaTeX parse error: Expected group after '_' at position 1: _̲_add__魔法上。
-
yield from方法
合并字典的方法
-
原地更新
字典对象内置方法update,用于把另一个字典更新到自己身上。
-
先解包再合并
-
借助itertools
-
借助ChainMap
使用ChainMap时,当字典间有重复的键值时,只会取第一个值,排在后面的键值并不会更新掉前面的。
-
使用dict.items()合并
-
字典解析式
判断是否包含子串的方法
-
使用in和not in
-
使用find方法
使用 字符串对象的find方法,如果有找到子串,就可以返回指定子串在字符串中的出现位置。如果没有找到,就返回-1。
-
使用index方法
字符串对象哟一个index方法,可以返回指定子串在该字符串中第一次出现的索引,如果没有找到会抛出异常,因此使用时需要注意捕获。
-
使用count方法
只要判断结果大于0就说明子串存在于字符串中。
-
通过魔法方法
-
借助operator
operator模块是Python内置的操作符函数接口,它定义了一些算术和比较内置操作的函数。operator模块是用c实现的,所以执行速度比Python代码快。在operator中有一个方法contains,可以很方便的判断子串是否在字符串中。
-
使用正则匹配
装饰器
装饰器的使用方法很固定:
- 定义一个装饰器
- 定义一个函数或者类
- 把装饰器放在该函数上
装饰器并不是编码必须的,它的出现,使代码:
- 更加优雅,代码结构更加清晰
- 将实现特定的功能代码封装成装饰器,提高代码复用率,增强代码可读性。
def decorator(func):
def wrapper(*args, **kw):
return func()
return wrapper
@decorator
def function():
print('hello, decorator')
- 入门:日志打印器
#这是装饰器函数,参数function是被装饰的函数
def logger(func):
def wrapper(*args, **kw):
print('开始执行:{}函数:'.format(func.__name__))
#真正执行的函数
func(*args, **kw)
print('函数执行完了。')
return wrapper
@logger
def add(x, y):
print('{} + {} = {}'.format(x, y, x + y))
add(20, 30)
- 入门:时间计时器
import time
#装饰函数
def timer(func):
def wrapper(*args, **kw):
t1 = time.time()
#执行函数
func(*args, **kw)
t2 = time.time()
#计算时长
cost_time = t2 - t1
print('花费时间:{}'.format(cost_time))
return wrapper
@timer
def want_sleep(sleep_time):
time.sleep(sleep_time)
want_sleep(10)
- 进阶:带参数的函数装饰器
def say_hello(country):
def wrapper(func):
def deco(*args, **kwargs):
if country == 'china':
print('你好!')
elif country == 'america':
print('hello.')
else:
return
#真正习性函数的地方
func(*args, **kwargs)
return deco
return wrapper
#小明,中国人
@say_hello('china')
def xiaoming():
pass
#jack, 美国人
@say_hello('america')
def jack():
pass
xiaoming()
print('-------')
jack()
- 高阶:不带参数的类装饰器
基于类实现的装饰器的实现,必须实现__call__和__init__两个内置函数。- _init_:接受被装饰函数
- _call_:实现装饰逻辑
class logger(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('[INFO]: the function {func}() is running...'.format(func=self.func.__name__))
return self.func(*args, **kwargs)
@logger
def say(something):
print('say {}!'.format(something))
say('hello')
- 高阶:带参数的类装饰器
- _init_:接收传入参数
- _call_:接收被装饰函数,实现装饰逻辑
class logger(object):
def __init__(self, level='INFO'):
self.level = level
def __call__(self, func):
def wrapper(*args, **kwargs):
print('[{level}]: the function {func}() is running...'.format(level=self.level, func=func.__name__))
func(*args, **kwargs)
return wrapper
@logger(level='WARNING')
def say(something):
print('say {}!'.format(something))
say('hello')
- 使用偏函数与类实现装饰器
Python 对某个对象是否能通过装饰器(@decorator)形式使用只有一个要求:decorator必须是一个可被调用(callable)的对象。- 函数
- 实现了__call__函数的类
- 偏函数
import time
import functools
class DelayFunc:
def __init__(self, duration, func):
self.duration = duration
self.func = func
def __call__(self, *args, **kwargs):
print('Wait for {duration} seconds...'.format(duration=self.duration))
time.sleep(self.duration)
return self.func(*args, **kwargs)
def eager_call(self, *args, **kwargs):
print('Call without delay')
return self.func(*args, **kwargs)
def delay(duration):
"""
装饰器:推迟某个函数的执行
同时提供eager_call方法立即执行
"""
#此处是为了避免定义额外函数
#直接使用functools.partial帮助构造DelayFunction实例
return functools.partial(DelayFunc, duration)
@delay(duration=2)
def add(a, b):
return a + b
描述符
一个实现了描述符协议的类就是一个描述符。在类里实现了__get__()、set()、delete()其中至少一个方法。实现保护属性不受修改、属性类型检查的功能,提高代码的复用率。
- get():用于访问属性。返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。
- set():在属性分配操作中个调用。不会返回任何内容。
- delete():控制删除操作,不会返回内容。
class Student:
def __init__(self, name, math, chinese, english):
self.name = name
self.math = math
self.chinese = chinese
self.english = english
@property
def math(self):
return self._math
@math.setter
def math(self, value):
if 0 <= value <= 100:
self._math = value
else:
raise ValueError('Valid value must be in [0, 100]')
@property
def chinese(self):
return self._chinese
@chinese.setter
def chinese(self, value):
if 0 <= value <= 100:
self._chinese = value
else:
raise ValueError('Valid value must be in [0, 100]')
@property
def english(self):
return self._english
@english.setter
def english(self, value):
if 0 <= value <= 100:
self._english = value
else:
raise ValueError('Valid value must be in [0, 100]')
def __repr__(self):
return '<Student:{}, math:{}, chinese:{}, english:{}>'.format(self.name, self.math, self.chinese, self.english)
- 描述符分类
- 数据描述符:实现了__get__()和__set__()两种方法的描述符。
- 非数据描述符:只实现了__get__()一种方法的描述符。
数据描述符和非数据描述符的区别在于:他们相对实例的字典的优先级不同。
#数据描述符
class DataDes:
def __init__(self, default=0):
self._score = default
def __set__(self, instance, value):
self._score = value
def __get__(self, instance, owner):
print('访问数据描述符里的__get__')
return self._score
#非数据描述符
class NoDataDes:
def __init__(self, default=0):
self._score = default
def __get__(self, instance, owner):
print('访问非数据描述符里的__get__')
return self._score
class Student:
math = DataDes(0)
chinese = NoDataDes(0)
def __init__(self, name, math, chinese):
self.name = name
self.math = math
self.chinese = chinese
def __getattribute__(self, item):
print('调用__getattribute__')
return super(Student, self).__getattribute__(item)
def __repr__(self):
return '<Student:{}, math:{}, chinese:{}>'.format(self.name, self.math, self.chinese)
- 基于描述符实现property
通过property装饰的函数,例如示例中的math会变成类Student实例的属性。对math的属性进行复制就会进入使用math.setter装饰函数的逻辑代码块。
class Student:
def __init__(self, name):
self.name = name
@property
def math(self):
return self._math
@math.setter
def math(self, value):
if 0 <= value <= 100:
self._math = value
else:
raise ValueError('Valid value must be in [0, 100]')
class TestProperty(object):
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc
def __get__(self, obj, objtype=None):
print('in __get__')
if obj is None:
return self
if self.fget is None:
raise AttributeError
return self.fget(obj)
def __set__(self, obj, value):
print('in __set__')
if self.fset is None:
raise AttributeError
self.fset(obj, value)
def __delete__(self, obj):
print('in __delete__')
if self.fdel is None:
raise AttributeError
self.fdel(obj)
def getter(self, fget):
print('in getter')
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
print('in setter')
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
print('in deleter')
return type(self)(self.fget, self.fset, fdel, self.__doc__)
class Student:
def __init__(self, name):
self.name = name
#这里改变
@TestProperty
def math(self):
return self._math
@math.setter
def math(self, value):
if 0 <= value <= 100:
self._math = value
else:
raise ValueError('Valid value must be in [0, 100]')
等价的两种写法
- 静态方法–@staticmethod
staticmethod相当于一个描述符类,而myfunc此刻变成了一个描述符。
class staticmethod:
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
print('in staticmethod __get__')
return self.f
class Test:
def myfunc():
print('hello method2')
#描述符的体现
myfunc = staticmethod(myfunc)
#等价写法
class Test:
@staticmethod
def myfunc():
print('hello method1')
- 静态方法–@classmethod
class classmethod(object):
def __init__(self, f):
self.f = f
def __get__(self, instance, owner=None):
print('in classmethod __get__')
def newfunc(*args):
return self.f(owner, *args)
return newfunc
class Test:
def myfunc(cls):
print('hello classmethod')
myfunc = classmethod(myfunc)
- 所有实例共享描述符
Student类里没写构造函数:从下面的实例结果可以看出,std2共享了std1的属性值,只要其中一个实例的变量发生改变,另一个实例的变量也会跟着改变。因为此时的math,Chinese,English三个全部是类变量,导致std1和std2在访问这三个变量时,其实都是访问类变量。
class Score:
def __init__(self, default=0):
self._value = default
def __get__(self, instance, owner):
return self._value
def __set__(self, instance, value):
if 0 <= value <= 100:
self._value = value
else:
raise ValueError
class Student:
math = Score(0)
chinese = Score(0)
english = Score(0)
def __repr__(self):
return '<Student math:{}, chinese:{}, english:{}>'.format(self.math, self.chinese, self.english)
把math,chinese,English变成实例之间相互隔离的属性,这样写:
class Score:
def __init__(self, object):
self.name = object
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if 0 <= value <= 100:
instance.__dict__[self.name] = value
else:
raise ValueError
class Student:
math = Score('math')
chinese = Score('chinese')
english = Score('english')
def __init__(self, math, chinese, english):
self.math = math
self.chinese = chinese
self.english = english
def __repr__(self):
return '<Student math:{}, chinese:{}, english:{}>'.format(self.math, self.chinese, self.english)
上下文管理器
上下文管理器的优点:
- 提高代码的复用率;
- 提高代码的优雅度;
- 提高代码的可读性;
with open('test.txt') as f:
print(f.readlines())
基本语法:
with EXPR as VAR:
BLOCK
上下文表达式:with open(‘test.txt’) as f
上下文管理器:open(‘test.txt’)
f不是上下文管理器,应该是资源对象。
在一个类里,实现了__enter__和__exit__的方法,这个类的实例就是一个上下文管理器。在编写代码的时候,可以将资源的连接或者资源获取放在__enter__中,将资源的关闭写在__exit__中。
class Resource():
def __enter__(self):
print('===connect to resource===')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('===close resource connection===')
def operate(self):
print('===in operation===')
with Resource() as res:
res.operate()
- 上下文管理器隐藏处理异常
异常可以在__exit__中捕获并决定如何处理异常,返回True表示异常已经捕获,不需要Python解释器再往外抛异常了。没有return就默认为return False。
__exit__函数的三个参数:- exc_type:异常类型
- exc_val:异常值
- exc_tb:异常的错误栈信息
当主逻辑代码没有报异常时,这三个参数都将为None。
class Resource():
def __enter__(self):
print('===connect to resource===')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('===close resource connection===')
return True
def operate(self):
1/0
with Resource() as res:
res.operate()
- 装饰器将函数对象编程一个上下文管理器
按照contextlib的协议来实现一个打开文件(with open)的上下文管理器。
在被装饰函数里,必须是一个生成器(带有yield),而yield之前的代码,就相当于__enter__里面的内容。yield之后的代码,就相当于__exit__里的内容。
import contextlib
@contextlib.contextmanager
def open_func(file_name):
#__enter__ method
print('open file:', file_name, 'in __enter__')
file_handler = open(file_name, 'r')
#important yield
yield file_handler
#__exit__ method
print('close file:', file_name, 'in __exit__')
file_handler.close()
return
with open_func('test.txt') as file_in:
for line in file_in:
print(line)
上面的示例只能实现上下文资源管理器的一个目的(资源管理),并不能实现另一个目的(处理异常)。要处理异常可以改成下面的示例:
import contextlib
@contextlib.contextmanager
def open_func(file_name):
#__enter__ method
print('open file:', file_name, 'in __enter__')
file_handler = open(file_name, 'r')
#important yield
try:
yield file_handler
except Exception as exc:
#deal with exception
print('the exception was thrown')
finally:
print('close file:', file_name, 'in __exit__')
file_handler.close()
return
with open_func('test.txt') as file_in:
for line in file_in:
1/0
print(line)
“创建临时目录,使用完后再删除临时目录”功能在一个项目中很多地方都需要用到,如果可以将这段逻辑处理成一个工具函数作为一个上下文管理器,那代码的复用率也大大提高。
import contextlib
import shutil
@contextlib.contextmanager
def tempdir(**kwargs):
argdict = kwargs.copy()
if 'dir' not in argdict:
argdict['dir'] = CONF.tempdir
tmpdir = tempfile.makdtemp(**argdict)
try:
yield tmpdir
finally:
try:
shuil.rmtree(tmpdir)
except OSError as e:
LOG.error(_LE('could not remove tempdir: %s'), e)
嵌套上下文管理器的另类写法
- method1
import contextlib
@contextlib.contextmanager
def test_context(name):
print('enter, my name is {}'.format(name))
yield
print('exit, my name is {}'.format(name))
with test_context('aa'):
with test_context('bb'):
print('=== in main ===')
- method2
with test_context('aaa'), test_context('bbb'):
print('=== in main ===')
嵌套for循环写成单行
用itertools库实现for循环嵌套
list1 = range(1, 3)
list2 = range(4, 6)
list3 = range(7, 9)
for item1 in list1:
for item2 in list2:
for item3 in list3:
print(item1 + item2 + item3)
from itertools import product
list1 = range(1, 3)
list2 = range(4, 6)
list3 = range(7, 9)
for item1, item2, item3 in product(list1, list2, list3):
print(item1 + item2 + item3)
关闭异常自动关联上下文
处理异常时,处理不当或者其他问题,再次抛出另一个异常,抛出的异常会携带元素的异常信息。
异常处理程序或finally块中引发异常,默认情况下,异常机制会隐式将先前的异常附加为新异常的__context__属性。这就是Python默认开启的自动关联异常上下文。
try:
print(1/0)
except Exception as exc:
raise RuntimeError('something bad happend')
控制这个上下文,可以加个from关键字(from语法有个限制,就是第二个表达式必须是另一个异常类或实例),来表明新的异常时直接由哪个异常引起的。
try:
print(1/0)
except Exception as exc:
raise RuntimeError('something bad happend') from exc
通过with_traceback()方法为异常设置上下文__context__属性,也能在traceback更好的显示异常信息。
try:
print(1/0)
except Exception as exc:
raise RuntimeError('bad thing').with_traceback(exc)
彻底关闭这个自动关联异常上下文的机制,可以使用raise…from None。
try:
print(1/0)
except Exception as exc:
raise RuntimeError('something bad happend') from None
自带的缓存机制
缓存是一种将定量数据加以保存,以备迎合后续获取需求的处理方式,加快数据获取的速度。
数据的生成过程可能需要经过计算、规整、远程获取等操作,如果是同一份数据需要多次使用,每次都重新生成会大大浪费时间。所以,如果将计算或者远程请求等操作获得的数据缓存下来,会加快后续的数据获取需求。
Python自带的缓存机制实现于functool模块中的lru_cache装饰器。
@functools.lru_cache(maxsize=None, typed=False)
- maxsize:最多可以缓存多少个此函数的调用结果,如果为None,则无限制,设置为2的幂时,性能最佳。
- typed:若为True,则不同参数类型的调用将分别缓存。
from functools import lru_cache
@lru_cache(None)
def add(x, y):
print('calculating: %s + %s' % (x, y))
return x + y
print(add(1, 2))
print(add(1, 2))
print(add(2, 3))
流式读取超大文件
使用with…open…可以从一个文件中读取数据,但是当使用read函数,Python会将文件的内容一次性的全部载入内存中,如果文件有10个G甚至更多,那么电脑就要消耗巨大的内存。如果用readline做一个生成器来逐行返回,但是如果文件内容就一行,还是会一次性读入全部内容到内存。最优雅的解决方法是,在使用read方法时,指定每次只读取固定大小的内容。
def read_from_file(filename, block_size=1024*8):
with open(filename, 'r') as fp:
with True:
chunk = fp.read(block_size)
if not chunk:
break
yield chunk
优化后:
from functools import partial
def read_from_file(filename, block_size=1024*8):
with open(filename, 'r') as fp:
for chunk in iter(partial(fp.read, block_size), ''):
yield chunk
延迟调用
Python使用上下文管理器实现延时调用。
import contextlib
def callback():
print('B')
with contextlib.ExitStack() as stack:
stack.callback(callback)
print('A')
快速计算函数运行时间
- method1
import time
start = time.time()
#run the function
end = time.time()
print(end - start)
- method2
import time
import timeit
def run_sleep(second):
print(second)
time.sleep(second)
print(timeit.timeit(lambda: run_sleep(2), number=5))
标准错误输出到日志文件中
import sys
import contextlib
log_file = 'test.log'
def task():
pass
@contextlib.contextmanager
def close_stdout():
raw_stdout = sys.stdout
file = open(log_file, 'a+')
sys.stdout = file
yield
sys.stdout = raw_stdout
file.close()
with close_stdout():
task()
反转字符串/列表
mstr = 'abcd'
ml = [1, 2, 3, 4]
mstr[::-1]
ml[::-1]
函数参数
def func(item, item_list=[]):
item_list.append(item)
print(item_list)
func('iphone')
func('xiaomi', item_list=['oppo','vivo'])
func('huawei')