Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[Enhancement] Speed up setting and deleting mutable attributes on non-dataclass subclasses of frozen dataclasses #102578

Closed
Assignees
ericvsmith
Labels
3.12only security fixesperformancePerformance or resource usagestdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement
@XuehaiPan

Description

@XuehaiPan

Feature or enhancement

Thedataclasses library provides an easy way to create classes. The library will automatically generate relevant methods for the users.

Creatingdataclasses with argumentfrozen=True will automatically generate methods__setattr__ and__delattr__ in_frozen_get_del_attr.

This issue proposes to change thetuple-based lookup toset-based lookup. Reduce the time complexity from$O(n)$ to$O(1)$.

In [1]:# tuple-basedIn [2]:%timeit'a'in ('a','b','c','d','e','f','g')9.91ns ±0.0982nsperloop (mean ±std.dev.of7runs,100,000,000loopseach)In [3]:%timeit'd'in ('a','b','c','d','e','f','g')33.2ns ±0.701nsperloop (mean ±std.dev.of7runs,10,000,000loopseach)In [4]:%timeit'g'in ('a','b','c','d','e','f','g')56.4ns ±0.818nsperloop (mean ±std.dev.of7runs,10,000,000loopseach)In [5]:# set-basedIn [6]:%timeit'a'in {'a','b','c','d','e','f','g'}11.3ns ±0.0723nsperloop (mean ±std.dev.of7runs,100,000,000loopseach)In [7]:%timeit'd'in {'a','b','c','d','e','f','g'}11ns ±0.106nsperloop (mean ±std.dev.of7runs,100,000,000loopseach)In [8]:%timeit'g'in {'a','b','c','d','e','f','g'}11.1ns ±0.126nsperloop (mean ±std.dev.of7runs,100,000,000loopseach)

A tiny benchmark script:

fromcontextlibimportsuppressfromdataclassesimportFrozenInstanceError,dataclass@dataclass(frozen=True)classFoo2:a:intb:intfoo2=Foo2(1,2)defbench2(inst):withsuppress(FrozenInstanceError):inst.a=0withsuppress(FrozenInstanceError):inst.b=0@dataclass(frozen=True)classFoo7:a:intb:intc:intd:inte:intf:intg:intfoo7=Foo7(1,2,3,4,5,6,7)defbench7(inst):withsuppress(FrozenInstanceError):inst.a=0withsuppress(FrozenInstanceError):inst.b=0withsuppress(FrozenInstanceError):inst.c=0withsuppress(FrozenInstanceError):inst.d=0withsuppress(FrozenInstanceError):inst.e=0withsuppress(FrozenInstanceError):inst.f=0withsuppress(FrozenInstanceError):inst.g=0classBar(Foo7):def__init__(self,a,b,c,d,e,f,g):super().__init__(a,b,c,d,e,f,g)self.baz=0defbench(inst):inst.baz=1

Result:

set-based lookup:

In [2]:%timeitbench2(foo2)1.08µs ±28.1nsperloop (mean ±std.dev.of7runs,1,000,000loopseach)In [3]:%timeitbench7(foo7)3.81µs ±20.3nsperloop (mean ±std.dev.of7runs,100,000loopseach)In [4]:%timeitbench(bar)249ns ±6.31nsperloop (mean ±std.dev.of7runs,1,000,000loopseach)

tuple-based lookup (original):

In [2]:%timeitbench2(foo2)1.15µs ±10.9nsperloop (mean ±std.dev.of7runs,1,000,000loopseach)In [3]:%timeitbench7(foo7)3.97µs ±15.7nsperloop (mean ±std.dev.of7runs,100,000loopseach)In [4]:%timeitbench(bar)269ns ±4.09nsperloop (mean ±std.dev.of7runs,1,000,000loopseach)
Result:`set`-based lookup:```pythonIn [2]: %timeit bench2(foo2)1.08 µs ± 28.1 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)In [3]: %timeit bench7(foo7)3.81 µs ± 20.3 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

tuple-based lookup (original):

In [2]:%timeitbench2(foo2)1.15µs ±10.9nsperloop (mean ±std.dev.of7runs,1,000,000loopseach)In [3]:%timeitbench7(foo7)3.97µs ±15.7nsperloop (mean ±std.dev.of7runs,100,000loopseach)

Theset-based is constantly faster than the old approach. And the theoretical time complexity is also smaller ($O(1)$ vs. $O(n)$).

Ref:#102573

Pitch

(Explain why this feature or enhancement should be implemented and how it would be used.
Add examples, if applicable.)

In the autogenerate__setattr__ and__delattr__, they have a sanity check at the beginning of the method. For example:

def__setattr__(self,name,value):iftype(self)is {{UserType}}ornamein ({{atupleoffieldnames}}):raiseFrozenInstanceError(f"cannot assign to field{name!r}")super(cls,self).__setattr__(name,value)

If someone inherits the frozen dataclass, the sanity check will take$O(n)$ time on thetuple__contains__(...) and finally callssuper().__setattr__(...). For example:

@dataclass(frozen=True)classFrozenBase:x:inty:int    ...# N_FIELDSclassFoo(FrozenBase):def__init__(self,x,y,somevalue,someothervalue):super().__init__(x,y)self.somevalue=somevalue# takes O(N_FIELDS)self.someothervalue=someothervalue# takes O(N_FIELDS) time againfoo=Foo(1,2,3,4)foo.extravalue=extravalue# takes O(N_FIELDS) time again

Previous discussion

N/A.

Linked PRs

Metadata

Metadata

Assignees

Labels

3.12only security fixesperformancePerformance or resource usagestdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions


    [8]ページ先頭

    ©2009-2025 Movatter.jp