Python黑魔法—学习笔记

王炳明的《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 envgrepPATH的路径里依次查找名为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')

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/studyeboy/article/details/108069617