《Effictive python》读书笔记2

做下读后的核心观点记录。

二、函数部分

第14条 尽量用异常来表示特殊情况,而不是None等值

因为none值和0等,在判断语句中都类似false,可能跟正常情况冲突。python更推崇抛出异常的方式来处理特殊情况。所以异常情况可以直接抛出自定义的异常,让外面处理,没有异常,都是正常值。

第15条了解如何在闭包里使用外面域的变量

代码里某条表达式中使用了变量,python解释器如何寻找?

  1. 当前函数的作用域;
  2. 2.任何外围作用域;
  3. 3.包含当前代码的模块的作用域(也叫全局作用域, globle scope)
  4. 4.内置作用域(python内置的,包含len、str等函数的作用域)
  5. 找不到变量时抛NameError

给变量赋值,有所不同:

  1. 如果当前作用域有这个变量,那么变量保存新值。
  2. 没有,就会在当前作用域创建新变量。

python3,nonlocal声明变量表示赋值时在上层作用域中查找该变量,不会延伸到全局作用域

python2,没有nolocal。可以用一个[]中的下标,赋值来替代。例如found[0] = True。这样的赋值会向上层去找作用域。

第16条 可以用生成器来改写返回列表的函数

当调用生成器的next函数时,会执行到下一个yield表达式,并将返回yield的值

这样会节省内存,输入量。

第17条 生成器作为参数的时候要注意

生成器是有状态的,只能迭代1次。造成枯竭。

可以写个容器类,实现__iter__()方法(里面要yield返回每次迭代的东西)。这样for去迭代的时候,每次都会生成一个新的iter对象。

第18条 用可变参数,来防止参数过多

可变数量参数(star args)

def myfunc(*args, **kwargs)

变长参数在传给函数时,先全部转化为元组,这时候生成器会占用大量内存,导致问题。因此适用场景要注意,参数数量不能过多。

在变长参数上增加其它的位置参数,可能产生难以排查的bug。

可以使用*来展开list,**展开字典,传给函数。

第19条 用关键字参数来设置可选参数

增加参数时不影响。

第20条 动态的默认参数可以用None和注释来描述

第21条 确保调用者使用关键字参数

Python3

*号后的参数,只能以关键字参数的形式赋值

python2

使用**kwarg,不定参数,字典形式。pop检查,不符合报错

三、 类部分

第22条 尽量用辅助类来维护程序的状态,而不要用字典或元组

类内部用字典或列表做底层数据结构,嵌套超过2层就要考虑重构了。使用辅助类来简化逻辑。

拆分后,类和代码可能会变多,但可维护性和逻辑简单了。

第23条  简单的接口可以接受函数而不是类

第24条 以@classmethod形式去通用的构建对象

类似于静态的new对象的方法。

第25条 用super初始化父类

python2

super(ConcretClass, self).__init__(paras)

python3

super(__class__, self).__init__(paras)

super().__init(paras)

第26条 只在使用Mixin制作工具类时,进行多重继承

作者不推崇使用多重继承。

mix-in是一种小型类,只定义了一套方法,没有定义实例属性,不要求调用__init__

第27条 类中属性多用public,少用private

两个下划线开头为private。

实例不能访问,子类无法访问父类的private属性,类方法可以访问。

内部机制:定义的私有变量编译器会改名,例如:ExampleClass.__p私有属性,编译器会将此私有变量改名为_ExampleClass__p。

所以私有变量在外部也是可以直接访问的,python无法保证private字段的私密性。

为了更便于继承等,不要使用private类型,有隐私要求的可使用protected类型(代码规范,不是强制),一个下划线。并在文档中详细说明。

有个场景可考虑使用private,父类属性名字很常见,子类又不受自己控制,可能引起子类混淆时。

第28条 自定义容器类,可以继承collections.abc来做

因为可能忘记实现一些方法,例如__len__,__getitem__等,所以通过继承collections.abc模块里的抽象类来防止遗忘。

四、 元类及属性

第29条  直接操作属性,而不是设置set和get方法

编写新类时,可以直接操作简单的public属性,而不是实现set和get方法

如果访问对象的某个属性时,需要表现出特殊的行为,那就用@property来定义;

@property遵循最小惊讶原则,而不应该产生奇怪的副作用。

@property执行得迅速一点,缓慢复杂工作放到普通的方法里。

第30条 考虑用@property来为属性添加新功能

第31条 用描述符descriptor来改写需要复用的@property方法

编译器在一个类的实例字典中找不到这个属性时,会调用__getattr__方法。

访问属性时,每次都会访问__getattribute__

第32条 __getattr__、__getattribute__、__setattr__

__getattr__:当访问某个类的实例变量,不存在时,会回调此函数

__getattribute__:访问某个类的实例变量时,每次都会回调

__setattr__:设置某个类的实例变量前会回调此函数

通过这些函数就可以按需进行动态地对实例属性进行修改了。

如果要在__getattribute__、__setattr__中访问实例属性,不能直接self访问,这样会不断循环。要使用super()

第33条 用元类来验证子类

python把子类的整个class加载后,就会调用其元类的__new__方法,可以在这个__new__方法里添加子类的验证逻辑。

Python2:

class Meta(type):    
    def __new__(meta, name, bases, class_dict):
class MyClasspython2(object):    
    __meta__ = Meta

python3

class Meta(type):
    def __new__(meta, name, bases, class_dict):
        Return type.__new__(meta, name, bases, class_dict)
Class MyClass(object, metaClass=Meta):
    stuff = 123

meta:元类

name:子类名

bases:父类元组

class_dict:class里面的一些东西

第34条 用元类来注册子类

举了个序列化和反序列化的例子

在构建模块化python程序时,类的注册是一种很有用的模式。类的注册可以放在元类中。只要基类的元类里实现了注册,那么子类继承基类时就无需再注册了。

第35条 用元类来动态赋值类的属性

借用元类,我们可以在某个类完全定义好之前,率先修改类的属性。

Orm中定义数据库中某个表的类,里面的Field,实现属性值为Filed的名字。这个在类的父类中的元类中,获取到所有属性值,然后将Filed的值赋好。

五、并发和并行

第36条 用subprocess模块来管理子进程

第37 条 多线程适用于io阻塞较多的场景,多进程用于cpu较多的场景 

由于GIL全局解释器锁存在,每个时刻其实只能一个线程执行。因此计算型任务不适合适用多线程,IO等待型任务适合多线程。

第38 条 多线程加锁

class Test():
    def __init__(slef):
        self.lock = Lock()
        self.count = 0
    def increment(self, offet):
        with self.lock:
            Self.count += offet

第39条 用Queue来协调各线程间的工作

如同生成线一样,使用pipeline 的思想,利用生产者消费者模型,将任务分发、传递最终合并。

自己实现由几个问题:某个阶段持续等待;如何停止工作线程、如何防止内存膨胀

可以使用Queue

第40条 使用concurrent.futures来实现真正的并行计算

底层使用multiprocessing来进行多进程,每个进程都有独立的GIL。

pool = ProcessPoolExecutor(max_workers=2)

results = list(pool.map(function, numbers))

ProcessPoolExecutor类会利用multiprocessing模块提供的底层机制:

  • 1.把numbers中的每一项输入数据都传给map;
  • 2.用pickle模块对数据进行序列化,将其变成二进制形式;
  • 3.通过本地套接字,将序列化后的数据从主进程,发送到子解释器所在进程;
  • 4.在子进程中,用pickle对二进制数据进行反序列化操作,将其还原为python对象;
  • 5.引入包含gcd函数的那个python模块;
  • 6.各子进程分别并行地对自己的数据执行gcd函数;
  • 7.将结果进行序列化操作,转变为字节;
  • 8.将这些字节通过socket复制到主进程中。
  • 9.主进程将字节反序列化为python对象;
  • 10.最后将每条子进程中的计算结果合并到一份列表。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券