TL;DR
dataclass est une nouvelle bibliothèque standard ajoutée dans python 3.7. Pour expliquer brièvement, si vous ajoutez un décorateur @ dataclass
à la déclaration de classe, c'est un soi-disant dunder (abréviation de double under score. En japonais, il se lit comme un dunder. ) Une bibliothèque qui génère des méthodes. Il peut être utilisé pour réduire considérablement les définitions de classe fastidieuses et est plus rapide que les implémentations médiocres. Dataclass a diverses fonctions autres que celles présentées ici, donc pour plus de détails, voir Official Document et [Python 3.7] Les «classes de données» peuvent devenir la norme pour les définitions de classe](https://qiita.com/tag1216/items/13b032348c893667862a).
Pour ceux qui ne peuvent toujours pas utiliser python3.7, PyPI a un backport pour 3.6.
classe de données
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 #Valeur par défaut
l: List[str] = field(default_factory=list) #par défaut pour la liste[]À
c: ClassVar[int] = 10 #Variable de classe
#Généré`__init__`Instancié avec
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))
#Généré`__repr__`Imprimez la représentation sous forme de chaîne de h avec
print(f)
#Faire une copie et réécrire
ff = copy.deepcopy(f)
ff.l.append('d')
#Généré`__eq__`Comparer avec
assert f != ff
J'ai mesuré le temps d'exécution de DataclassFoo créé en utilisant dataclass et ManualFoo écrit à la main, __init __
, __repr__
, __eq__
.
Code source utilisé pour la mesure 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)
Moyenne de 5 séries de 100 000 fois chacune
Résultat de la mesure(sec) | |
---|---|
dataclass __init__ | 0.04382 |
Classe manuscrite__init__ | 0.04003 |
dataclass __repr__ | 0.07527 |
Classe manuscrite__repr__ | 0.08414 |
dataclass __eq__ | 0.04755 |
Classe manuscrite__eq__ | 0.04593 |
On peut dire qu'il n'y a presque aucune différence si elle est exécutée 500 000 fois.
Les codes d'octet correspondaient également.
<détails> <détails> Je voudrais expliquer les parties importantes lors de l'explication de la classe de données. PEP526: Syntax for Variable Annotations PEP526 décrit la méthode de déclaration de type, mais les informations de type de la variable déclarée dans la classe par cet ajout de spécification Il est maintenant possible de l'obtenir lorsque le programme est exécuté. Je pense que beaucoup de gens connaissent eval. En gros, la différence avec eval est ʻEval Cela seul n'a pas de sens, alors regardons l'exemple suivant. Il est facile d'imaginer que cela produira "des pierres de frappe!". Alors qu'est-ce que c'est? Alors essayez ceci alors. En fait, exec évalue une chaîne comme une expression, de sorte que même les fonctions python peuvent être définies dynamiquement. Génial. Lorsqu'une classe avec un décorateur de classe de données est importée, le code est généré à l'aide des annotations de type et de l'exécution expliquées ci-dessus. C'est super rugueux, mais le flux est le suivant. Pour plus d'informations, lisez Cette zone de la source cpython. Le code qui simplifie 3, 4 et 5 ressemble à ceci. Ce qui précède est un exemple simplifié, mais en réalité En considération de tout ce qui précède, la chaîne de caractères de définition de fonction est créée et le code est généré avec soin afin qu'il fonctionne correctement dans tous les cas. Une autre chose à garder à l'esprit est que cette ** génération de code n'a lieu qu'au moment où le module est chargé **. Une fois la classe importée, elle peut être utilisée ** comme une classe manuscrite **. Rust a un attribut Derive ( Ajoutez simplement Rust est encore plus incroyable, avec la possibilité d'implémenter votre propre dérivé personnalisé officiellement pris en charge, ce qui le rend relativement décontracté. Permet la métaprogrammation basée sur le type. Il existe de nombreuses autres fonctionnalités dans Rust qui facilitent ces programmeurs, et je pense que c'est pourquoi Rust est si productif, même avec des contraintes de type et de propriété difficiles. Rust est un très bon langage, j'encourage donc les pythonistes à l'essayer. Je pense personnellement que la classe de données est un bon exemple de l'utilité et du potentiel de la métaprogrammation basée sur le type. J'ai également créé environ deux bibliothèques basées sur des classes de données, donc si vous êtes intéressé, jetez un œil. Une bibliothèque qui mappe les valeurs des variables d'environnement aux champs de la classe de données. Utile lorsque vous souhaitez remplacer la classe de configuration Python par une variable d'environnement à l'aide d'un conteneur. Une bibliothèque de sérialisation basée sur les classes de données. En cours de développement pour implémenter la même fonction que la bibliothèque God de Rust serde en utilisant dataclass. Comme pour Rust, j'espère que Python sera enthousiasmé par ce domaine et proposera de nombreuses bonnes bibliothèques.
Recommended Posts
>>> 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
Avant d'entrer dans l'explication interne de la classe de données
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'>}
Fonction ʻexec` intégrée
: évalue la chaîne d'arguments comme une expression ʻExec
: évalue la chaîne d'arguments comme une instruction>>> exec('print("typing rocks!")')
"typing rocks!"
exec('''
def func():
print("typing rocks!")
''')
>>> func()
"typing rocks!"
Alors, que fait la classe de données en interne?
__init__
** chaîne ** en utilisant les informations de type__init __
dans la classenl = '\n' # f-Puisque l'échappement ne peut pas être utilisé à l'intérieur d'une chaîne, définissez-le à l'extérieur
#Création d'une chaîne de définition de fonction
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))}
"""
#Essayez de sortir la chaîne de caractères de définition de fonction vers la console
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
#Génération de code avec exec.`func`Fonction définie dans le périmètre
exec(s)
setattr(Foo, 'func', func) #Définit la fonction générée dans la classe dans la classe
Rust's
# [derive]
# [derive]
) qui est ajouté lors de la définition d'une structure. Cela peut être à peu près identique ou supérieur à la classe de données. Par exemple, si vous regardez ce qui suit,#[derive(Debug, Clone, Eq, PartialEq, Hash)]
struct Foo {
i: i32,
s: String,
b: bool,
}
# [derive (Debug, Clone, Eq, PartialEq, Hash)]
et cela générera autant de méthodes.
__repr__
en Python)__eq__
et __gt__
en Python)__hash__
en Python)Possibilité de dataclass comme métaprogrammation
en conclusion