Effective Python 读书笔记

强烈推荐亲自阅读《Effective Python》([美]Brett Slatkin著)

对Python学习有莫大的帮助

不改变列表对象进行列表赋值:

a[:] = [1,2,3,...]

左边a列表的切片操作能使a对象所指的列表对象不变(id(a)不变)的情况下将a列表的值覆盖为右值

翻转列表:

b = a[::-1]

该方法只对字节串和ASCII字符有效,对UTF-8字节串会报错

变量作用域
在表达式中引用变量时,python解释器按照如下顺序遍历各作用域,以解释该引用:
1)当前函数的作用域
2)任何外围的作用域(例如,包含当前函数的其他函数)
3)包含当前代码的那个模块的作用域(也叫全局作用域,global scope)
4)内置作用域(也就是包含len和str等函数的那个作用域)

迭代容器
迭代容器和可迭代对象不同,前者可将__iter__方法实现为生成器, 则每次迭代的时候都能获得一个新的可迭代对象(iter(object)可返回新的迭代对象)。可迭代对象则会返回自身(iter(object) is object)

super方法
super方法调用顺序,可以通过CClass.mro()方法得到,顺序是会从基类开始往上依次调用。(CClass为某个子类的类名)

私有属性
Python会对私有属性的名称做一些简单变换,如

class MyChildObject(MyParentObject)
       def get_private(self)
              return self.__private

class MyParentObject()
       def __init__(self):
              self.__private = 0

其中就会将父类的私有属性改名为_MyParentObject__private,所以我们在子类中也可以直接通过MyChildObject._MyParnteObejct__private来访问该私有变量

魔术方法
Python中使用下标访问元素时,会将代码转译为__getitem__(index),所以我们可以为自定义的容器类实现__getitem__(self, index)方法,实现下标访问元素
实现len()操作的话需要实现__len__方法
继承内置collections.abc中的抽象基类可以提示你需要实现什么方法,只要实现了所需的方法,其余一些该容器应有的方法就会自动实现。

__set__(self, instance, value)和__get__(self, instance, instance_type),__delete__(self, instance)为描述符协议方法,实现了任一方法的类可作为一个具有绑定行为的对象属性,当访问、赋值删除改属性时就会调用对应的方法。

__getattr__(self, name)方法:
如果某个类定义了__getattr__,同时系统在该类的对象实例字典中找不到带查询的属性,则会调用这个方法。可以使用这个方法,将组合的方式表现的像继承一般:

class CWrapObj:
	def __init__(self, obj):
		self.wrap_obj = obj
		
	def __getattr__(self, name):
		def wrap_func(*args, **kwargs):
			if hasattr(self.wrap_obj, name):
				return getattr(self.wrap_obj, name)(*args, **kwargs)  # 可以判断一下是是Method还是变量
		return wrap_func
		
class CObj:
	def hhh(self):
		print('hhh from CObj')
wrap_obj = CWrapObj(CObj())
wrap_obj.hhh()

注意,__getattr__可能会导致hasattr()判断偏离预期。重写过程中,需要判断情况,合适地发起AtrributeError异常

__getattribute__(self, name):
每次访问对象的属性时都会调用这个方法,即便属性字典里已经有了该属性

__setattr__(self, name, value):
无论是直接赋值还是调用内置的setattr函数赋值都会触发__setattr__方法

由于__setattr__和__getattribute__在每次赋值或访问属性时都会触发,因此如果要在这两个方法中访问或赋值属性,可以通过super().__getattribute__和super().__setattr__的方式。

属性装饰器
@property装饰器会将一个类方法装饰成属性
如:

@property
def hsv(self):
       return self._hsv

在调用self.hsv时,则会调用该方法,获得self._hsv的值
而用其setter装饰器装饰方法后,在给该属性赋值的行为都会变为调用该方法

@hsv.setter
def hsv(self, para):
       do_anything
       self._hsv = para

元类
定义元类要继承type,对于其他使用该元类的其他类而言,Python默认会把那些类的class语句体中所含的相关内容发送给元类的__new__方法

class Meta(type):
       def __new__(meta, name, bases, class_dict):
              ...
class MyClassInPython2(object):
       __metaclass__ = Meta

我们可以在元类的new方法中验证子类的参数是否被有效构建(元类的__new__方法在定义子类的语句执行时就会被执行)
借用该元类的功能,我们可以做到在类被定义的同时将类注册到我们想要的地方,甚至可以在类定义同时注解描述符属性对象

GIL
全局解释器锁,该机制导致python程序无法同一时刻只能有一个线程向前运行,无法真正做到并行。但是仍然会有数据不安全的问题

协程(coroutine)
工作原理:每当生成器函数执行到yield表达式的时候,消耗生成器的那段代码,就通过send方法给生成器回传一个值,而生成器收到这个经send方法传进来的值之后,会将其视为yield表达式的执行结果

def minimize():
	current = yield
	while True:
		value = yield current
		current = min(value, current)
it = minimize()
next(it)  # 为了让代码运行到current = yield处
print(it.send(10))
print(it.send(4))
print(it.send(22))

>>>
10
4
4

第一个yield语句中的yield关键字后没有跟随其他内容,这条语句意思是将外界传进来的某个值当成当前的最小值。而while中的yield语句会将current输出,并将外界传入的下一个值作为表达式结果

装饰器

def trace(func):  # 装饰函数
	def wrapper(*args, **kwargs):
		result = func(*args, **kwargs)
		return result
	return wrapper

@trace  # 进行装饰
def fibonacci(n):
	print n

然而装饰后的函数会将trace内部的wrapper函数赋值给当前模块中与原函数同名的fibonacci变量
对于需要使用内省机制的工具来说该行为会干扰其正常工作

print fibonacci.__name__ 
>>>
wrapper

内置模块functools 中的wraps辅助函数可以解决这个问题,将wraps修饰器运用到wrapper函数之后,它会将内部函数相关的重要元数据全部复制到外围函数

from functools import wraps
def trace(func):  # 装饰函数
	@wraps(func)
	def wrapper(*args, **kwargs):
		result = func(*args, **kwargs)
		return result
	return wrapper

@trace  # 进行装饰
def fibonacci(n):
	print n

print fibonacci.__name__
>>>
fibonacci

with语句
用contextlib中的contextmanager装饰器装饰后的函数可以用在with语句中

@contextmanager
def before_you(para):
	print 'before_you'
	try:
		yield para
	finally:
		print 'after_you'

with before_you('kk') as para:
	print para
>>>
before_you
kk
after_you

yield语句的部分即with语句块开始执行的部分,同时yield可返回一个对象给with…as语句,在with语句块执行结束后,执行finally语句块。

__all__变量
模块中__all__变量指定的名称是该模块被from xx import *方式导入时会导入的对象名称

test.py:
def a():
	pass
def b():
	pass
__all__ = ['a']

demo.py
from test import *
# 此时只有test.a被导入了,test.b并没有

import 机制
引入模块时,Python会按照深度优先的顺序执行下列操作:
1、在由sys.path所指定的路径中,搜寻待引入的模块
2、从模块中加载代码,并保证这段代码能够正确编译(此时如果模块有语法问题则会报错)
3、创建与该模块相对应的空对象
4、把这个空的模块对象,添加到sys.modules里面
5、运行模块对象中的代码,以定义其内容
循环依赖所产生的问题是:某些属性必须要等Python系统把对应的代码执行完毕后(第五步后)才具备完成的定义。但是包含该属性的模块只需要第四步完成就可以用import语句引入并添加到sys.module里了。

module1.py:
import module2
class part1():
	...

obj1 = module2.obj2

module2.py:
import module1
obj2 = None
obj3 = module1.part1()

main.py:
import module1

上例中,在main.py执行import module1,会执行module1.py中的import module2.py,而module2.py中执行import module1语句时,又会将仍处于import 执行状态的空的module1模块对象引入,而module1.part1仍未定义,因此执行到obj3 = module1.part1()时则会报错(AttributeError)
临时解决的方法有三种(这三种方法都不推荐):
1、调整引入顺序
在module1.py中,先定义好module2中会用到的module1对象,再执行import module2
2、先引入、再配置、最后运行
不在import时执行任何语句,而将相关语句定位为函数,在import 完成后手动调用
3、动态引入
将import 语句放到要用到该模块的函数中,在函数执行时才调用import
真正的解决方案:
将会互相引入的部分放到第三个模块中,然后两者都import这个第三方模块

几个可能有用的工具:
pyvenv:构建虚拟python运行环境
repr(obj):返回对象obj易于理解的可打印字符串
例如:

print(repr(5))
print(repr('5'))
print('%r'%5)
print('%r'%'5')
>>>
5
'5'
5
'5'

pdb:交互调试
cProfile模块中的Profile对象:收集各函数运行时长和统计被不同调用者调用次数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值