我很好奇为什么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上。同样令人惊讶的是,词典的表现与元组相当。
答案 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
创建是一个子类,将字段值存储为元组,并通过属性存储相关索引及其名称。