前言:
在装饰器的定义中,经常引用functools.wraps,本篇文章将深入其内部源码,由于该方法的定义中,还引入其他重要的函数或者类,因此根据其调用链,对每个函数或者方法或者类进行单独分析,所以文章的结构大致如下:
第一部分内容:
根据其调用链:functools.wraps---->partial---->update_wrapper
functools.wraps需要调用partial,因此需要解析partial的源码
partial调用了update_wrapper函数,因此需要解析update_wrapper的源码
第二部分内容:
patial作为关键函数,在第三方库造轮子里,使用频率较高。这里以borax第三方库里面的fetch方法说明partial的使用场合。考虑到fetch还使用的python内建的attrgetter和itemgetter,故还对这两个类进行解析。
其调用链为:fetch—>partial/attrgetter/itemgetter
第三部分内容:
在python的内建方法中,attrgetter/itemgetter类,里面用了__slots__方法,本文也给出关于该方法作用的内容
考虑到以上有多个知识点混合,本文采用倒叙方式,文章的内容组织如下
- 1、python的魔法方法
__slots__的作用 - 2、attrgetter/itemgetter类的解析
- 3、borax.fetch的用法
- 4、partial的解析和用法
- 5、update_wrapper的解析和用法
- 6、functools.wraps的解析和用法
1、python的魔法方法__slots__的作用
首先看看attrgetter/itemgetter的源代码定义(这里仅给出方法名)
class attrgetter:
__slots__ = ('_attrs', '_call')
def __init__(self, attr, *attrs):
def __call__(self, obj):
def __repr__(self):
def __reduce__(self):
该类里面引用了__slots__方法,仅有两个私有属性:('_attrs', '_call')
对于attrgetter,其作用:
- 给类指定一个固定大小的空间存放属性,用于极致减少对象的内存占用,例如当十几万个小类(数据类),对象占用内存利用率将更有效。
- 更快的属性访问速度
- 实例后限制绑定新属性
为何_slots_方法有以上作用?
这是因为,在定义个对象时(定义类),Python默认用一个字典来保存一个该对象实例属性。然而,对于有着已知属性的小对象类来说(例如一个坐标点类,仅有几个属性即可),当创建几十万个这些实例时,将有几十万个这样的字典占用大量内存,因此可通过slots方法告诉Python不使用字典,使用一个元组作为这几个属性的存放位置,以节省每个小对象的存储空间。
slots这里不建议使用列表,因为列表占用空间比元组大。
1.1、 slots方法保证实例不会创建__dict__方法
class Point(object):
def __init__(self,x,y):
self._x=x
self._y=y
def __str__(self):
return 'Point<{0},{1}>'.format(self._x,self._y)
a=Point(1,2)
print(a)
print(a.__dict__)
a.test=3
print(a.test)
# 输出:
Point<1,2>
{
'_x': 1, '_y': 2}
3
# 加入slot之后
class Point(object):
__slots__ = ('_x','_y')
def __init__(self,x,y):
self._x=x
self._y=y
def __str__(self):
return 'Point<{0},{1}>'.format(self._x,self._y)
a=Point(1,2)
print(a)
print(a.__dict__)
# 输出:
Point<1,2>
#AttributeError: 'Point' object has no attribute '__dict__'
#可见实例没有使用dict字典存放属性
不过需要注意的是:slots魔法方法定义的属性仅对当前类实例起作用,对继承的子类是无效的
1.2、为何列表占用空间比元组大?
- 内存占用有区别
首先列表和元组最重要的区别就是,列表是动态的、可变的对象、可读可写,而元组是静态的、不可变的对象,可读不可写。
下面通过实例看看它们存储以及占用空间的区别
查看列表的内存占用
l=[]
l.__sizeof__()
# 40
加入4个字符,每个字符为8字节空间
list_obj=['a','b','c','d']
list_obj.__sizeof__()
# 结果为72字节=列表自身40+4个字符*8
此外列表的空间是动态增加的,在数据结构与算法里,大家在设计列表这种数据结构应该知道,当调用append方法时,内部会判断当前列表预留空间是否满足用于存放新元素,若空间不足,会再动态申请新内存,申请的逻辑为:
(1)当原底层数组存满时,list类会自动请求一个空间为原列表两倍的新列表
(2)原列表的所有元素将被一次存入新列表里
(3)删除原列表,并初始化新列表
例如下面测试,原list_obj有4个字符元素,总计为72个字节,再加一个字符,是等于80个字节吗?
list_obj.append('e')
list_obj.__sizeof__()
# 结果为104字节=列表自身42+原4个字符*8+新1个字符*8+原4个字符*8的新申请预留空间
这里列表自身从40变为42字节,是因为增加了一个1索引。
查看元组的内存占用
t=()
t.__sizeof__()
# 24
加入4个字符,每个字符为8字节空间
tuple_obj=('a','b','c','d')
tuple_obj.__sizeof__()
# 结果为56字节=元组自身24+4个字符*8
可以看到,存储5个字符,列表用了72个字节,元组只用了56个字节。
- 对象创建时间有区别
import timeit
t1 = timeit.Timer('list_obj=["a", "b", "c", "d"]')
t1.timeit()
# 0.0844487198985604
t2 = timeit.Timer('tuple_obj=("a", "b", "c", "d")')
t2.timeit()
# 0.01631815598959463
因为列表数据结构初始化需要方法逻辑比元组负责,而且需要预占空间,可以看到它们之间创建时间差别大,列表创建时间是元组的5倍左右。
2、attrgetter/itemgetter类的解析
2.1 attrgetter的使用场景
attrgetter主要用于快速获取对象的keys或者属性。
以下以数据类型为BlogItem对象为例,该数据对象有三个attribute,分别博客网址、作者、博客文章数量
class BlogItem(object):
def __init__(self, website, author, blog_nums):
self.website = website
self.author = author
self.blog_nums = blog_nums
def __str__(self):
return "{0}:{1}".format(self.__class__.__name__,self.website)
__repr__ = __str__
blog_object_list = \
[BlogItem("www.aoo.cn", 'aoo', 10),
BlogItem("www.boo.cn", 'boo', 5),
BlogItem("www.coo.cn", 'coo', 20)
]
print(blog_object_list)
# 输出:
[BlogItem:www.aoo.cn, BlogItem:www.boo.cn, BlogItem:www.coo.cn]
现要获取每行数据对象的blog属性,并对其实施排序,通常会使用lambda表达式实现
print (sorted(blog_object_list, key=lambda item: item.blog_nums))
#输出:
[BlogItem:www.boo.cn, BlogItem:www.aoo.cn, BlogItem:www.coo.cn]
有了attrgetter方法后,更方便调用获取对象的属性,例如下面的用法
print (sorted(blog_object_list,key=attrgetter('blog_nums')))
#输出:
[BlogItem:www.boo.cn, BlogItem:www.aoo.cn, BlogItem:www.coo.cn]
也可以传入多个属性,按多个属性进行排序,例如这

最低0.47元/天 解锁文章
3294





