TL;DR
dataclass ist eine neue Standardbibliothek, die in Python 3.7 hinzugefügt wurde. Um es kurz zu erklären: Wenn Sie der Klassendeklaration einen "@ dataclass" -Dekorator hinzufügen, handelt es sich um einen sogenannten Dunder (Abkürzung für "double under score". Auf Japanisch wird er als "dunder" gelesen.) ) Eine Bibliothek, die Methoden generiert. Es kann verwendet werden, um langwierige Klassendefinitionen erheblich zu reduzieren und ist schneller als schlechte Implementierungen. Die Datenklasse hat verschiedene andere als die hier vorgestellten Funktionen. Weitere Informationen finden Sie unter Offizielles Dokument und [Python 3.7]. "Datenklassen" können zum Standard für Klassendefinitionen werden](https://qiita.com/tag1216/items/13b032348c893667862a).
Für diejenigen, die python3.7 immer noch nicht verwenden können, hat PyPI einen Backport für 3.6.
from dataclasses import dataclass, field
from typing import ClassVar, List, Dict, Tuple
import copy
@dataclass
class Foo:
i: int
s: str
f: float
t: Tuple[int, str, float, bool]
d: Dict[int, str]
b: bool = False #Standardwert
l: List[str] = field(default_factory=list) #Standard für Liste[]Zu
c: ClassVar[int] = 10 #Klassenvariable
#Generiert`__init__`Instanziiert mit
f = Foo(i=10, s='hoge', f=100.0, b=True,
l=['a', 'b', 'c'], d={'a': 10, 'b': 20},
t=(10, 'hoge', 100.0, False))
#Generiert`__repr__`Drucken Sie die Zeichenfolgendarstellung von h mit aus
print(f)
#Erstellen Sie eine Kopie und schreiben Sie neu
ff = copy.deepcopy(f)
ff.l.append('d')
#Generiert`__eq__`Vergleichen mit
assert f != ff
Ich habe die Ausführungszeit von DataclassFoo gemessen, das mit der von Hand geschriebenen Datenklasse und ManualFoo erstellt wurde: "init", "repr", "eq".
Quellcode für die Messung code> b> summary>
import timeit
from dataclasses import dataclass
@dataclass
class DataclassFoo:
i: int
s: str
f: float
b: bool
class ManualFoo:
def __init__(self, i, s, f, b):
self.i = i
self.s = s
self.f = f
self.b = b
def __repr__(self):
return f'ManualFoo(i={self.i}, s={self.s}, f={self.f}, b={self.b})'
def __eq__(self, b):
a = self
return a.i == b.i and a.s == b.s and a.f == b.f and a.b == b.b
def bench(name, f):
times = timeit.repeat(f, number=100000, repeat=5)
print(name + ':\t' + f'{sum(t)/5:.5f}')
bench('dataclass __init__', lambda: DataclassFoo(10, 'foo', 100.0, True))
bench('manual class __init__', lambda: ManualFoo(10, 'foo', 100.0, True))
df = DataclassFoo(10, 'foo', 100.0, True)
mf = ManualFoo(10, 'foo', 100.0, True)
bench('dataclass __repr__', lambda: str(df))
bench('manual class __repr__', lambda: str(mf))
df2 = DataclassFoo(10, 'foo', 100.0, True)
mf2 = ManualFoo(10, 'foo', 100.0, True)
bench('dataclass __eq__', lambda: df == df2)
bench('manual class __eq__', lambda: mf == mf2)
Durchschnittlich 5 Sätze zu je 100.000 Mal ausführen
Messergebnis(sec) | |
---|---|
dataclass __init__ | 0.04382 |
Handschriftliche Klasse__init__ | 0.04003 |
dataclass __repr__ | 0.07527 |
Handschriftliche Klasse__repr__ | 0.08414 |
dataclass __eq__ | 0.04755 |
Handschriftliche Klasse__eq__ | 0.04593 |
Es kann gesagt werden, dass es fast keinen Unterschied gibt, wenn es 500.000 Mal ausgeführt wird.
Auch die Bytecodes stimmten überein.
>>> import dis
>>> dis.dis(DataclassFoo.__init__)
2 0 LOAD_FAST 1 (i)
2 LOAD_FAST 0 (self)
4 STORE_ATTR 0 (i)
3 6 LOAD_FAST 2 (s)
8 LOAD_FAST 0 (self)
10 STORE_ATTR 1 (s)
4 12 LOAD_FAST 3 (f)
14 LOAD_FAST 0 (self)
16 STORE_ATTR 2 (f)
5 18 LOAD_FAST 4 (b)
20 LOAD_FAST 0 (self)
22 STORE_ATTR 3 (b)
24 LOAD_CONST 0 (None)
26 RETURN_VALUE
>>> dis.dis(ManualFoo.__init__)
13 0 LOAD_FAST 1 (i)
2 LOAD_FAST 0 (self)
4 STORE_ATTR 0 (i)
14 6 LOAD_FAST 2 (s)
8 LOAD_FAST 0 (self)
10 STORE_ATTR 1 (s)
15 12 LOAD_FAST 3 (f)
14 LOAD_FAST 0 (self)
16 STORE_ATTR 2 (f)
16 18 LOAD_FAST 4 (b)
20 LOAD_FAST 0 (self)
22 STORE_ATTR 3 (b)
24 LOAD_CONST 0 (None)
26 RETURN_VALUE
Ich möchte die wichtigen Teile bei der Erläuterung der Datenklasse erläutern.
PEP526: Syntax for Variable Annotations
PEP526 beschreibt die Typdeklarationsmethode, jedoch die Typinformationen der Variablen, die durch diesen Spezifikationszusatz in der Klasse deklariert wurden Es ist jetzt möglich, es zu erhalten, wenn das Programm ausgeführt wird.
from typing import Dict
class Player:
players: Dict[str, Player]
__points: int
print(Player.__annotations__)
# {'players': typing.Dict[str, __main__.Player],
# '_Player__points': <class 'int'>}
Ich denke, viele Leute kennen eval. Grob gesagt ist der Unterschied zur Bewertung
eval
: Wertet die Argumentzeichenfolge als Ausdruck aus
exec
: Wertet die Argumentzeichenfolge als Anweisung aus
Dies allein macht keinen Sinn. Schauen wir uns also das nächste Beispiel an.
Es ist leicht vorstellbar, dass dies "Typing Rocks!" Ausgibt.
>>> exec('print("typing rocks!")')
"typing rocks!"
Was ist das dann?
exec('''
def func():
print("typing rocks!")
''')
Dann versuchen Sie dies
>>> func()
"typing rocks!"
damit. Tatsächlich wertet exec eine Zeichenfolge als Ausdruck aus, sodass sogar Python-Funktionen dynamisch definiert werden können. Toll.
Wenn eine Klasse mit einem Datenklassendekorator importiert wird, wird der Code mithilfe der oben erläuterten Typanmerkungen und exec generiert. Es ist super rau, aber der Fluss ist wie folgt. Weitere Informationen finden Sie unter Dieser Bereich der Cpython-Quelle.
__init__
Funktionsdefinition ** Zeichenfolge ** unter Verwendung von Typinformationen__init __
in der Klasse einDer Code, der 3, 4 und 5 vereinfacht, sieht folgendermaßen aus.
nl = '\n' # f-Sie können nicht innerhalb einer Zeichenfolge entkommen, definieren Sie sie also außerhalb
#Erstellung von Funktionsdefinitionszeichenfolgen
s = f"""
def func(self, {', '.join([f.name for f in fields(Hoge)])}):
{nl.join(' self.'+f.name+'='+f.name for f in fields(Hoge))}
"""
#Versuchen Sie, die Zeichenfolge für die Funktionsdefinition an die Konsole auszugeben
print(s)
# def func(self, i, s, f, t, d, b, l):
# self.i=i
# self.s=s
# self.f=f
# self.t=t
# self.d=d
# self.b=b
# self.l=l
#Codegenerierung mit exec.`func`Im Geltungsbereich definierte Funktion
exec(s)
setattr(Foo, 'func', func) #Legen Sie die in der Klasse generierte Funktion in der Klasse fest
Das Obige ist ein vereinfachtes Beispiel, aber in Wirklichkeit
In Anbetracht all dieser Punkte wird die Funktionsdefinitionszeichenfolge erstellt und der Code sorgfältig generiert, damit er in jedem Fall ordnungsgemäß funktioniert.
Beachten Sie auch, dass diese ** Codegenerierung nur in dem Moment erfolgt, in dem das Modul geladen wird **. Sobald die Klasse importiert wurde, kann sie ** genau wie eine handschriftliche Klasse ** verwendet werden.
# [ableiten]
Rust hat ein Derive-Attribut (# [derive]
), das beim Definieren einer Struktur hinzugefügt wird. Dies kann ungefähr gleich oder besser als die Datenklasse sein. Wenn Sie sich beispielsweise Folgendes ansehen:
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
struct Foo {
i: i32,
s: String,
b: bool,
}
Fügen Sie einfach # [ableiten (Debug, Clone, Eq, PartialEq, Hash)]
hinzu und es werden so viele Methoden generiert.
__repr__
in Python)__eq__
und __gt__
in Python)__hash__
in Python)Rust ist noch erstaunlicher, da Sie Ihr eigenes benutzerdefiniertes Derivat offiziell unterstützt implementieren können, was es relativ lässig macht. Ermöglicht typbasierte Metaprogrammierung.
Es gibt viele andere Funktionen in Rust, die diese Programmierer einfacher machen, und ich denke, deshalb ist Rust selbst bei schwierigen Typbeschränkungen und Besitzverhältnissen so produktiv. Rust ist eine wirklich großartige Sprache, daher ermutige ich Pythonistas, sie auszuprobieren.
Ich persönlich denke, dass die Datenklasse ein gutes Beispiel für die Nützlichkeit und das Potenzial der typbasierten Metaprogrammierung ist.
Ich habe auch ungefähr zwei Bibliotheken basierend auf Datenklassen erstellt. Wenn Sie also interessiert sind, schauen Sie bitte.
Eine Bibliothek, die die Werte von Umgebungsvariablen Feldern in der Datenklasse zuordnet. Nützlich, wenn Sie die Python-Konfigurationsklasse mithilfe eines Containers mit einer Umgebungsvariablen überschreiben möchten.
Eine auf Datenklassen basierende Serialisierungsbibliothek. In der Entwicklung, um dieselbe Funktion wie Rusts God Library serde unter Verwendung einer Datenklasse zu implementieren.
Wie bei Rust hoffe ich, dass Python von diesem Bereich begeistert ist und viele gute Bibliotheken entwickelt.
Recommended Posts