深入functools.wraps、partial

前言:

在装饰器的定义中,经常引用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]

也可以传入多个属性,按多个属性进行排序,例如这

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值