与tuple / dictionary / class相比,namedtuple变慢了

时间:2018-03-22 01:33:07

标签: python-3.x

我很好奇为什么namedtuple比python中的普通类慢。请考虑以下事项:

In [1]: from collections import namedtuple

In [2]: Stock = namedtuple('Stock', 'name price shares')  

In [3]: s = Stock('AAPL', 750.34, 90)

In [4]: %%timeit 
   ...: value = s.price * s.shares
   ...:          
175 ns ± 1.17 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [5]: class Stock2:
   ...:     __slots__ = ('name', 'price', 'shares')
   ...:     def __init__(self, name, price, shares):
   ...:         self.name = name       
   ...:         self.price = price
   ...:         self.shares = shares

In [6]: s2 = Stock2('AAPL', 750.34, 90)

In [8]: %%timeit
   ...: value = s2.price * s2.shares
   ...:                                
106 ns ± 0.832 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [9]: class Stock3:                  
   ...:     def __init__(self, name, price, shares):
   ...:         self.name = name 
   ...:         self.price = price     
   ...:         self.shares = shares

In [10]: s3 = Stock3('AAPL', 750.34, 90)

In [11]: %%timeit                      
    ...: value = s3.price * s3.shares
    ...:         
118 ns ± 3.54 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [12]: t = ('AAPL', 750.34, 90)

In [13]: %%timeit         
    ...: values = t[1] * t[2]          
    ...:
93.8 ns ± 1.13 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [14]: d = dict(name='AAPL', price=750.34, shares=90)                                

In [15]: %%timeit                          
...: value = d['price'] * d['shares']
...:                                   
92.5 ns ± 0.37 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

我期望namedtuple来到没有插槽的课程之前。这是在python3.6上。同样令人惊讶的是,词典的表现与元组相当。

2 个答案:

答案 0 :(得分:1)

我想我知道为什么namedtuple的访问速度比字典访问慢。考虑以下(s是一个namedtuple,s2是一个常规类(没有槽)):

In [12]: %%timeit
    ...: s.name
    ...: 
78.2 ns ± 0.713 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [17]: %%timeit
    ...: s2.name
    ...: 
58.2 ns ± 4.67 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

对namedtuple的简单访问比类成员访问更昂贵。

namedtuple中的

name定义为:

 name = _property(_itemgetter(0), doc='Alias for field number 0')

在执行s.name时,python发现该名称是property,然后必须调用额外的__get__方法(在这种情况下通过itemgetter(0)传递self到它。

这也可能有更多原因。

答案 1 :(得分:0)

对于python类的实例,通过点表示法设置和获取属性主要是通过__dict__[attribute_name]__dict__本身是属性,它是字典)实例,具体取决于值__dict__[attribute_name]称之为v,有不同的行为。

案例一:v不是descriptor,因此点符号只返回v
情况二:v是一个描述符,结果将从描述符的__get__方法中获取。

对于描述中的简单类实例:很容易就是一例

对于namedtuple情况:看一下namedtuple source code,函数namedtuple正在使用这个template创建一个类,其中dict中的命名字段存储作为属性。<登记/> where属性是描述符,itemgetter实例将在描述符__get__方法中使用! 这是python代码中的属性类,位于Cpython中c源代码的注释中:

class property(object):

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        if doc is None and fget is not None and hasattr(fget, "__doc__"):
            doc = fget.__doc__
        self.__get = fget
        self.__set = fset
        self.__del = fdel
        self.__doc__ = doc

    def __get__(self, inst, type=None):
        if inst is None:
            return self
        if self.__get is None:
            raise AttributeError, "unreadable attribute"
        return self.__get(inst)

    def __set__(self, inst, value):
        if self.__set is None:
            raise AttributeError, "can't set attribute"
        return self.__set(inst, value)

    def __delete__(self, inst):
        if self.__del is None:
            raise AttributeError, "can't delete attribute"
        return self.__del(inst)

总结以上内容,我们应该理解为什么namedtupple访问速度较慢,有一些额外的步骤可以从namedtuple的类实例中获取值,而不是简单的类。

如果你想深入挖掘,看看如何存储和获取值,你可以通过上面的链接阅读python3.6的源代码。

提示:
namedtuple创建是一个子类,将字段值存储为元组,并通过属性存储相关索引及其名称。