【问题标题】:What explains the different performance of creating objects via normal class, dataclass and namedtuple?通过普通类、数据类和命名元组创建对象的有趣性能
【发布时间】:2020-11-04 20:02:47
【问题描述】:

我正在浏览数据类并命名元组。我发现这种行为在使用 python 的不同特性创建对象时具有不同的性能。

数据类

In [1]: from dataclasses import dataclass
   ...:
   ...: @dataclass
   ...: class Position:
   ...:     lon: float = 0.0
   ...:     lat: float = 0.0
   ...:

In [2]: %timeit for _ in range(1000): Position(12.5, 345)
326 µs ± 34.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

普通班:

In [1]: class Position:
   ...:
   ...:     def __init__(self, lon=0.0, lat=0.0):
   ...:         self.lon = lon
   ...:         self.lat = lat
   ...:

In [2]: %timeit for _ in range(1000): Position(12.5, 345)
248 µs ± 2.89 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

命名元组:

In [2]: Position = namedtuple("Position", ["lon","lat"], defaults=[0.0,0.0])

In [3]: %timeit for _ in range(1000): Position(12.5, 345)
286 µs ± 13.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Python Env - Python 3.7.3  
OS - MacOS Mojave

所有实现都具有相同的对象属性、相同的默认值。

  1. 为什么会出现时间(data_classes)>时间(named_tuple)>时间(normal_class)的趋势?
  2. 每个实现都采取了哪些措施来花费各自的时间?
  3. 哪种实现在什么场景下表现最好?

这里,时间表示创建对象所花费的时间。

【问题讨论】:

    标签: python python-3.x python-internals python-dataclasses python-performance


    【解决方案1】:

    查看here,并转介 PEP-557。还有其他方法 - post_init。要验证,请使用@dataclass(init=False) - 在这种情况下,不应调用该方法。

    更新

    看起来我最初的解释有点天真。 tl;博士:

    在 Python 中,一切都是字典。在数据类的情况下,该字典中有更多条目,因此将它们放在那里需要更多时间。

    这种变化是如何发生的? @Arne 的评论发现我在这里遗漏了一些东西。我做了示例代码:

    from dataclasses import dataclass
    import time
    
    @dataclass
    class Position:
        lon: float = 0.0
        lat: float = 0.0
    
    
    start_time = time.time()
    for i in range(100000):
        p = Position(lon=1.0, lat=1.0)
    elapsed = time.time() - start_time
    print(f"dataclass {elapsed}")
    print(dir(p))
    
    
    class Position2:
        lon: float = 0.0
        lat: float = 0.0
    
        def __init__(self, lon, lat):
            self.lon = lon
            self.lat = lat
    
    
    start_time = time.time()
    for i in range(100000):
        p = Position2(lon=1.0, lat=1.0)
    elapsed = time.time() - start_time
    print(f"just class {elapsed}")
    print(dir(p))
    
    start_time = time.time()
    for i in range(100000):
        p = {"lon": 1.0, "lat": 1.0}
    elapsed = time.time() - start_time
    print(f"dict {elapsed}")
    

    有结果:

    /usr/bin/python3.8 ...../test.py
    dataclass 0.16358232498168945
    ['__annotations__', '__class__', '__dataclass_fields__', '__dataclass_params__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'lat', 'lon']
    just class 0.1495649814605713
    ['__annotations__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'lat', 'lon']
    dict 0.028212785720825195
    
    Process finished with exit code 0
    

    Dict 示例供参考。

    查看dataclass,这个函数:

    (489) def _init_fn(fields, frozen, has_post_init, self_name, globals):
    

    负责创建构造函数。正如 Arne 所发现的 - post_init 代码是可选的,并且不会生成。我有其他想法,关于字段有一些工作,但是:

    In [5]: p = Position(lat = 1.1, lon=2.2)                                                                                                                                                                           
    
    In [7]: p.lat.__class__                                                                                                                                                                                            
    Out[7]: float
    

    所以这里没有额外的包装/代码。从所有这些中,我看到的唯一额外的东西 - 就是更多的方法。

    【讨论】:

    • 谢谢。我会通过它。我已经编辑了问题。
    • @bigbounty 我也会添加简单的字典 - 如果我们谈论性能;)
    • 如果你自己不写__post_init__,就不会产生对它的调用。
    • @Arne 你是对的 - 我已经更新了回复。希望现在没事:)
    猜你喜欢
    • 2010-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 1970-01-01
    • 2015-05-10
    • 1970-01-01
    相关资源
    最近更新 更多