Почему `dataclasses.asdict (obj)`> в 10 раз медленнее, чем `obj .__ dict __ ()` - PullRequest
0 голосов
/ 07 сентября 2018

Я использую Python 3.6 и пакет dataclasses backport от ericvsmith .

Кажется, что вызов dataclasses.asdict(my_dataclass) примерно в 10 раз медленнее, чем вызов my_dataclass.__dict__:

In [172]: @dataclass
     ...: class MyDataClass:
     ...:     a: int
     ...:     b: int
     ...:     c: str
     ...: 

In [173]: %%time
     ...: _ = [MyDataClass(1, 2, "A" * 1000).__dict__ for _ in range(1_000_000)]
     ...: 
CPU times: user 631 ms, sys: 249 ms, total: 880 ms
Wall time: 880 ms

In [175]: %%time
     ...: _ = [dataclasses.asdict(MyDataClass(1, 2, "A" * 1000)) for _ in range(1_000_000)]
     ...: 
CPU times: user 11.3 s, sys: 328 ms, total: 11.6 s
Wall time: 11.7 s

Это ожидаемое поведение? В каких случаях я должен использовать dataclasses.asdict(obj) вместо obj.__dict__?


Редактировать : Использование __dict__.copy() не имеет большого значения:

In [176]: %%time
     ...: _ = [MyDataClass(1, 2, "A" * 1000).__dict__.copy() for _ in range(1_000_000)]
     ...: 
CPU times: user 922 ms, sys: 48 ms, total: 970 ms
Wall time: 970 ms

1 Ответ

0 голосов
/ 07 сентября 2018

В большинстве случаев, когда вы использовали бы __dict__ без dataclasses, вам, вероятно, следует продолжать использовать __dict__, возможно, с вызовом copy. asdict выполняет лот дополнительной работы, которую вы, возможно, на самом деле не хотите. Вот что он делает.


Во-первых, из документов :

Каждый класс данных преобразуется в набор своих полей в виде пар имя: значение. классы данных, диктовки, списки и кортежи возвращаются в. Например:

@dataclass
class Point:
     x: int
     y: int

@dataclass
class C:
     mylist: List[Point]

p = Point(10, 20)
assert asdict(p) == {'x': 10, 'y': 20}

c = C([Point(0, 0), Point(10, 4)])
assert asdict(c) == {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}

Так что если вы хотите рекурсивное диктование класса данных, используйте asdict. Если вы не хотите этого, то все накладные расходы, связанные с его предоставлением, теряются. В частности, если вы используете asdict, то изменение реализации содержащихся объектов для использования dataclass изменит результат asdict для внешних объектов.


Кроме того, asdict создает новый dict, в то время как __dict__ просто напрямую обращается к атрибуту объекта dict. Возвращаемое значение asdict не будет зависеть от переназначения полей исходного объекта. Кроме того, asdict использует fields, поэтому, если вы добавите в экземпляр класса данных атрибуты, которые не соответствуют объявленным полям, asdict не будет включать их.

Наконец, документы вообще не упоминают об этом, но asdict будет вызывать deepcopy на всем, что не является объектом класса данных, dict, list или tuple:

else:
    return copy.deepcopy(obj)

(Объекты, диктанты, списки и кортежи Dataclass проходят через рекурсивную логику, которая также создает копию, только с применением рекурсивного диктовки.)

deepcopy действительно дорого сам по себе, и отсутствие какой-либо обработки memo означает, что asdict может создать несколько копий общих объектов в нетривиальных графах объектов. Следите за этим:

>>> from dataclasses import dataclass, asdict
>>> @dataclass
... class Foo:
...     x: object
...     y: object
... 
>>> a = object()
>>> b = Foo(a, a)
>>> c = asdict(b)
>>> b.x is b.y
True
>>> c['x'] is c['y']
False
>>> c['x'] is b.x
False
...