dataclasses
— Dataklasser¶
Källkod: Lib/dataclasses.py
Denna modul tillhandahåller en dekorator och funktioner för att automatiskt lägga till genererade special methods såsom __init__()
och __repr__()
till användardefinierade klasser. Den beskrevs ursprungligen i PEP 557.
De medlemsvariabler som ska användas i dessa genererade metoder definieras med hjälp av PEP 526 typannoteringar. Till exempel den här koden:
from dataclasses import dataclass
@dataklass
klass InventoryItem:
"""Klass för att hålla reda på ett objekt i inventering."""
name: str
unit_price: float
kvantitet_på_hand: int = 0
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
kommer bland annat att lägga till en __init__()
som ser ut så här:
def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0):
self.name = namn
self.unit_price = enhetspris
self.quantity_on_hand = kvantitet_on_hand
Notera att denna metod automatiskt läggs till i klassen: den är inte direkt specificerad i InventoryItem
-definitionen som visas ovan.
Tillagd i version 3.7.
Modulens innehåll¶
- @dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False)¶
Denna funktion är en decorator som används för att lägga till genererade special methods till klasser, enligt beskrivningen nedan.
Dekoratorn
@dataclass
undersöker klassen för att hittafield
. Ettfield
definieras som en klassvariabel som har en type annotation. Med två undantag som beskrivs nedan, undersöker ingenting i@dataclass
den typ som anges i variabelannoteringen.Ordningen på fälten i alla de genererade metoderna är den ordning i vilken de förekommer i klassdefinitionen.
Dekoratorn
@dataclass
kommer att lägga till olika ”dunder”-metoder till klassen, beskrivna nedan. Om någon av de tillagda metoderna redan finns i klassen beror beteendet på parametern, vilket dokumenteras nedan. Dekoratorn returnerar samma klass som den anropas på; ingen ny klass skapas.Om
@dataclass
bara används som en enkel dekorator utan parametrar, fungerar den som om den har de standardvärden som dokumenteras i denna signatur. Det vill säga, dessa tre användningar av@dataclass
är likvärdiga:@dataklass klass C: ... @dataklass() klass C: ..: ... @dataklass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False) klass C: ...
Parametrarna till
@dataclass
är:init: Om true (standard) kommer en
__init__()
-metod att genereras.Om klassen redan definierar
__init__()
, ignoreras denna parameter.repr: Om true (standard) kommer en
__repr__()
-metod att genereras. Den genererade repr-strängen innehåller klassnamnet samt namn och repr för varje fält, i den ordning de definieras i klassen. Fält som är markerade som uteslutna från repr inkluderas inte. Till exempel:InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10)
.Om klassen redan definierar
__repr__()
, ignoreras denna parameter.eq: Om true (standard) kommer en
__eq__()
-metod att genereras. Denna metod jämför klassen som om den vore en tupel av dess fält, i ordning. Båda instanserna i jämförelsen måste vara av samma typ.Om klassen redan definierar
__eq__()
, ignoreras denna parameter.order: Om true (standardvärdet är
False
) kommer metoderna__lt__()
,__le__()
,__gt__()
och__ge__()
att genereras. Dessa jämför klassen som om den vore en tupel av dess fält, i ordning. Båda instanserna i jämförelsen måste vara av samma typ. Om order är sant och eq är falskt, genereras ettValueError
.Om klassen redan definierar något av
__lt__()
,__le__()
,__gt__()
, eller__ge__()
, så kommerTypeError
att tas upp.unsafe_hash: Om true, tvinga
dataclasses
att skapa en__hash__()
-metod, även om det kanske inte är säkert att göra det. Annars genereras en__hash__()
-metod enligt hur eq och frozen är inställda. Standardvärdet ärFalse
.__hash__()
används av den inbyggdahash()
, och när objekt läggs till i hashade samlingar som dictionaries och sets. Att ha en__hash__()
innebär att instanser av klassen är oföränderliga. Föränderlighet är en komplicerad egenskap som beror på programmerarens avsikt, förekomsten av och beteendet hos__eq__()
samt värdena på flaggorna eq och frozen i dekoratorn@dataclass
.Som standard kommer
@dataclass
inte att implicit lägga till en__hash__()
-metod om det inte är säkert att göra det. Inte heller kommer den att lägga till eller ändra en befintlig explicit definierad__hash__()
-metod. Att ställa in klassattributet__hash__ = None
har en specifik betydelse för Python, som beskrivs i__hash__()
-dokumentationen.Om
__hash__()
inte är explicit definierad, eller om den är satt tillNone
, så kan@dataclass
lägga till en implicit__hash__()
-metod. Även om det inte rekommenderas kan du tvinga@dataklass
att skapa en__hash__`()
metod medunsafe_hash=True
. Detta kan vara fallet om din klass är logiskt oföränderlig men fortfarande kan muteras. Detta är ett specialiserat användningsfall och bör övervägas noggrant.Här är reglerna för implicit skapande av en
__hash__()
-metod. Observera att du inte både kan ha en explicit__hash__()
-metod i din dataklass och sättaunsafe_hash=True
; detta kommer att resultera i ettTypeError
.Om eq och frozen båda är true, kommer
@dataclass
som standard att generera en__hash__()
-metod åt dig. Om eq är sant och frozen är falskt, kommer__hash__()
att sättas tillNone
, vilket markerar att den inte är hashbar (vilket den är, eftersom den är mutabel). Om eq är false, kommer__hash__()
att lämnas orörd vilket innebär att__hash__()
metoden i superklassen kommer att användas (om superklassen ärobject
, innebär detta att den kommer att falla tillbaka till id-baserad hashing).frozen: Om true (standard är
False
), kommer tilldelning till fält att generera ett undantag. Detta emulerar skrivskyddade frysta instanser. Se diskussion nedan.Om
__setattr__()
eller__delattr__()
är definierad i klassen och frozen är true, så uppstårTypeError
.match_args: Om true (standardvärdet är
True
) skapas tupeln__match_args__
från listan över parametrar som inte innehåller nyckelord till den genererade metoden__init__()
(även om__init__()
inte genereras, se ovan). Om false, eller om__match_args__
redan är definierad i klassen, kommer__match_args__
inte att genereras.
Tillagd i version 3.10.
kw_only: Om true anges (standardvärdet är
False
) kommer alla fält att markeras som keyword-only. Om ett fält markeras som endast nyckelord är den enda effekten att parametern__init__()
som genereras från ett fält med endast nyckelord måste anges med ett nyckelord när__init__()
anropas. Se ordlistan parameter för mer information. Se även avsnittetKW_ONLY
.Fält som endast innehåller nyckelord ingår inte i
__match_args__
.
Tillagd i version 3.10.
slots: Om true (default är
False
), kommer attributet__slots__
att genereras och en ny klass kommer att returneras istället för den ursprungliga. Om__slots__
redan är definierat i klassen, kommerTypeError
att returneras.
Varning
Att skicka parametrar till en basklass
__init_subclass__()
när man använderslots=True
kommer att resultera i ettTypeError
. Använd antingen__init_subclass__
utan parametrar eller använd standardvärden som en lösning. Se gh-91126 för fullständig information.Tillagd i version 3.10.
Ändrad i version 3.11: Om ett fältnamn redan ingår i
__slots__
för en basklass kommer det inte att ingå i den genererade__slots__
för att förhindra att överskriver dem. Använd därför inte__slots__
för att hämta fältnamnen i en dataklass. Användfields()
istället. För att kunna bestämma ärvda slots kan basklassen__slots__
vara vilken iterabel som helst, men inte en iterator.weakref_slot: Om true (standard är
False
), lägg till en slot med namnet ”__weakref__”, som krävs för att göra en instansweakref-able
. Det är ett fel att angeweakref_slot=True
utan att också angeslots=True
.
Tillagd i version 3.11.
field
kan valfritt ange ett standardvärde, med normal Python-syntax:@dataklass klass C: a: int # 'a' har inget förinställt värde b: int = 0 # tilldela ett förinställt värde för 'b'
I detta exempel kommer både
a
ochb
att inkluderas i den tillagda__init__()
-metoden, som kommer att definieras som:def __init__(self, a: int, b: int = 0):
TypeError
kommer att uppstå om ett fält utan ett standardvärde följer ett fält med ett standardvärde. Detta gäller oavsett om det sker i en enda klass eller som ett resultat av klassarv.
- dataclasses.field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=MISSING, doc=None)¶
För vanliga och enkla användningsfall krävs ingen annan funktionalitet. Det finns dock vissa dataklassfunktioner som kräver ytterligare information per fält. För att tillgodose detta behov av ytterligare information kan du ersätta standardvärdet för fältet med ett anrop till den medföljande funktionen
field()
. Till exempel:@dataklass klass C: mylist: list[int] = field(default_factory=list) c = C() c.mylist += [1, 2, 3]
Som visas ovan är
MISSING
-värdet ett sentinel-objekt som används för att upptäcka om vissa parametrar tillhandahålls av användaren. Denna sentinel används eftersomNone
är ett giltigt värde för vissa parametrar med en distinkt betydelse. Ingen kod bör direkt användaMISSING
-värdet.Parametrarna till
field()
är:default: Om det anges kommer detta att vara standardvärdet för fältet. Detta behövs eftersom
field()
-anropet i sig ersätter den normala positionen för standardvärdet.default_factory: Om det anges måste det vara en nollargumentskallelse som anropas när ett standardvärde behövs för detta fält. Detta kan bland annat användas för att ange fält med föränderliga standardvärden, vilket diskuteras nedan. Det är ett fel att ange både default och default_factory.
init: Om true (standard) inkluderas detta fält som en parameter i den genererade
__init__()
-metoden.repr: Om true (standard) inkluderas detta fält i den sträng som returneras av den genererade metoden
__repr__()
.hash: Detta kan vara en bool eller
None
. Om true, inkluderas detta fält i den genererade metoden__hash__()
. Om false, exkluderas detta fält från den genererade__hash__()
. OmNone
(standard), använd värdet för compare: detta skulle normalt vara det förväntade beteendet, eftersom ett fält bör ingå i hashen om det används för jämförelser. Att ställa in detta värde till något annat änNone
avråds.En möjlig anledning till att ställa in
hash=False
mencompare=True
skulle vara om ett fält är dyrt att beräkna ett hashvärde för, det fältet behövs för likhetstestning och det finns andra fält som bidrar till typens hashvärde. Även om ett fält utesluts från hashvärdet kommer det ändå att användas för jämförelser.compare: Om true (standard) inkluderas detta fält i de genererade metoderna för likhet och jämförelse (
__eq__()
,__gt__()
, etc.).metadata: Detta kan vara en mappning eller
None
.None
behandlas som en tom dict. Detta värde är inkapslat iMappingProxyType()
för att göra det skrivskyddat och exponeras påField
-objektet. Det används inte alls av dataklasser, utan tillhandahålls som en tilläggsmekanism från tredje part. Flera tredje parter kan var och en ha sin egen nyckel, för att användas som ett namnrymd i metadata.kw_only: Om true, markeras detta fält som keyword-only. Detta används när parametrarna för den genererade
__init__()
-metoden beräknas.Fält som endast innehåller nyckelord ingår inte heller i
__match_args__
.
Tillagd i version 3.10.
doc: valfri docstring för detta fält.
Tillagd i version 3.14.
Om standardvärdet för ett fält anges genom ett anrop till
field()
, kommer klassattributet för detta fält att ersättas med det angivna default-värdet. Om default inte anges kommer klassattributet att tas bort. Avsikten är att efter att dekoratorn@dataclass
har körts, kommer alla klassattribut att innehålla standardvärden för fälten, precis som om standardvärdet självt hade angetts. Till exempel, efter:@dataklass klass C: x: int y: int = fält(repr=False) z: int = fält(repr=False, default=10) t: int = 20
Klassattributet
C.z
kommer att vara10
, klassattributetC.t
kommer att vara20
och klassattributenC.x
ochC.y
kommer inte att anges.
- class dataclasses.Field¶
Field
-objekt beskriver varje definierat fält. Dessa objekt skapas internt och returneras av metodenfields()
på modulnivå (se nedan). Användare bör aldrig instansiera ettField
-objekt direkt. Dess dokumenterade attribut är:name
: Namnet på fältet.typ
: Typen av fält.default
,default_factory
,init
,repr
,hash
,compare
,metadata
ochkw_only
har samma betydelse och värden som de har i funktionenfield()
.
Andra attribut kan finnas, men de är privata och får inte inspekteras eller åberopas.
- class dataclasses.InitVar¶
typannoteringarna
InitVar[T]
beskriver variabler som är init-only. Fält annoterade medInitVar
betraktas som pseudofält och returneras därför varken av funktionenfields()
eller används på något annat sätt än att de läggs till som parametrar i__init__()
och en valfri__post_init__()
.
- dataclasses.fields(class_or_instance)¶
Returnerar en tupel av
Field
-objekt som definierar fälten för denna dataklass. Accepterar antingen en dataklass eller en instans av en dataklass. UtlöserTypeError
om ingen dataklass eller instans av en dataklass skickas. Returnerar inte pseudofält som ärClassVar
ellerInitVar
.
- dataclasses.asdict(obj, *, dict_factory=dict)¶
Konverterar dataklassen obj till en dict (med hjälp av fabriksfunktionen dict_factory). Varje dataklass konverteras till en dict av dess fält, som
namn: värde
-par. dataklasser, dicts, listor och tupler recursas in i. Andra objekt kopieras medcopy.deepcopy()
.Exempel på användning av
asdict()
på nästlade dataklasser:@dataklass klass Punkt: x: int y: int @dataklass klass C: mylist: lista[Punkt] p = Punkt(10, 20) assert asdict(p) == {'x': 10, 'y': 20} c = C([Punkt(0, 0), Punkt(10, 4)]) assert asdict(c) == {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}
Om du vill skapa en ytlig kopia kan du använda följande lösning:
{field.name: getattr(obj, field.name) for field in fields(obj)}
asdict()
ger upphov tillTypeError
om obj inte är en dataklassinstans.
- dataclasses.astuple(obj, *, tuple_factory=tuple)¶
Konverterar dataklassen obj till en tupel (med hjälp av fabriksfunktionen tuple_factory). Varje dataklass konverteras till en tupel av dess fältvärden. dataklasser, dicts, listor och tuplar recursas till. Andra objekt kopieras med
copy.deepcopy()
.Fortsättning från föregående exempel:
assert astuple(p) == (10, 20) assert astuple(c) == ([(0, 0), (10, 4)],)
Om du vill skapa en ytlig kopia kan du använda följande lösning:
tuple(getattr(obj, field.name) for field in dataclasses.fields(obj))
astuple()
ger upphov tillTypeError
om obj inte är en dataklassinstans.
- dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False, module=None, decorator=dataclass)¶
Skapar en ny dataklass med namnet cls_name, fält som definieras i fields, basklasser som anges i bases och initialiseras med ett namnrymd som anges i namespace. fields är en iterabel vars element är antingen
name
,(name, type)
eller(name, type, Field)
. Om baraname
anges, användstyping.Any
förtype
. Värdena för init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots och weakref_slot har samma betydelse som de har i@dataclass
.Om module är definierat, kommer attributet
__module__
i dataklassen att sättas till det värdet. Som standard sätts det till modulnamnet för den som anropar.Parametern decorator är en callable som kommer att användas för att skapa dataklassen. Den bör ta klassobjektet som ett första argument och samma nyckelordsargument som
@dataclass
. Som standard används funktionen@dataclass
.Denna funktion är inte absolut nödvändig, eftersom alla Python-mekanismer för att skapa en ny klass med
__annotations__
sedan kan använda funktionen@dataclass
för att konvertera klassen till en dataklass. Denna funktion tillhandahålls som en bekvämlighet. Till exempel:C = make_dataclass('C', [('x', int), 'y', ('z', int, field(default=5))], namespace={'add_one': lambda self: self.x + 1})
Är likvärdig med:
@dataklass klass C: x: int y: 'typing.Any' z: int = 5 def add_one(self): returnera self.x + 1
Tillagd i version 3.14: Parametern decorator har lagts till.
- dataclasses.replace(obj, /, **changes)¶
Skapar ett nytt objekt av samma typ som obj och ersätter fälten med värden från changes. Om obj inte är en dataklass, uppstår
TypeError
. Om nycklarna i changes inte är fältnamn i den angivna dataklassen, uppstårTypeError
.Det nyligen returnerade objektet skapas genom anrop av metoden
__init__()
i dataklassen. Detta säkerställer att__post_init__()
, om sådan finns, också anropas.Init-only variabler utan standardvärden, om sådana finns, måste anges i anropet till
replace()
så att de kan skickas till__init__()
och__post_init__()
.Det är ett fel om changes innehåller några fält som definieras som att de har
init=False
. EttValueError
kommer att uppstå i detta fall.Var uppmärksam på hur fält med
init=False
fungerar under ett anrop tillreplace()
. De kopieras inte från källobjektet, utan initialiseras i__post_init__()
, om de initialiseras överhuvudtaget. Det förväntas att fält medinit=False
används sällan och med omdöme. Om de används kan det vara klokt att ha alternativa klasskonstruktörer eller kanske en anpassadreplace()
(eller liknande) metod som hanterar kopiering av instanser.Dataklassinstanser stöds också av den generiska funktionen
copy.replace()
.
- dataclasses.is_dataclass(obj)¶
Returnerar
True
om dess parameter är en dataklass (inklusive underklasser av en dataklass) eller en instans av en sådan, annars returnerasFalse
.Om du behöver veta om en klass är en instans av en dataklass (och inte en dataklass i sig), lägg då till ytterligare en kontroll för
not isinstance(obj, type)
:def is_dataclass_instance(obj): return is_dataclass(obj) och inte isinstance(obj, type)
- dataclasses.MISSING¶
Ett sentinelvärde som visar att default eller default_factory saknas.
- dataclasses.KW_ONLY¶
Ett sentinelvärde som används som en typannotering. Alla fält efter ett pseudofält av typen
KW_ONLY
markeras som fält som endast innehåller nyckelord. Observera att ett pseudofält av typenKW_ONLY
annars ignoreras helt. Detta gäller även namnet på ett sådant fält. Av konvention används namnet_
för ett fält av typenKW_ONLY
. Fält som endast innehåller nyckelord betecknar__init__()
-parametrar som måste anges som nyckelord när klassen instansieras.I det här exemplet markeras fälten
y
ochz
som fält med endast nyckelord:@dataklass klass Punkt: x: float _: KW_ONLY y: float z: float p = Punkt(0, y=1,5, z=2,0)
I en enskild dataklass är det ett fel att ange mer än ett fält vars typ är
KW_ONLY
.Tillagd i version 3.10.
- exception dataclasses.FrozenInstanceError¶
Uppstår när en implicit definierad
__setattr__()
eller__delattr__()
anropas på en dataklass som definierades medfrozen=True
. Det är en underklass tillAttributeError
.
Behandling efter initiering¶
- dataclasses.__post_init__()¶
När den definieras i klassen kommer den att anropas av den genererade
__init__()
, normalt somself.__post_init__()
. Men om någraInitVar
-fält är definierade, kommer de också att skickas till__post_init__()
i den ordning de definierades i klassen. Om ingen__init__()
-metod genereras kommer__post_init__()
inte att anropas automatiskt.Detta gör det bland annat möjligt att initiera fältvärden som är beroende av ett eller flera andra fält. Till exempel:
@dataklass klass C: a: float b: float c: float = fält(init=False) def __post_init__(self): self.c = self.a + self.b
Metoden __init__()
som genereras av @dataclass
anropar inte basklassens __init__()
-metoder. Om basklassen har en __init__()
-metod som måste anropas är det vanligt att anropa denna metod i en __post_init__()
-metod:
klass Rektangel:
def __init__(self, höjd, bredd):
self.height = höjd
self.width = bredd
@dataklass
klass Kvadrat(Rektangel):
sida: float
def __post_init__(self):
super().__init__(self.side, self.side)
Observera dock att de dataklassgenererade __init__()
-metoderna i allmänhet inte behöver anropas, eftersom den härledda dataklassen tar hand om initieringen av alla fält i alla basklasser som själva är en dataklass.
Se avsnittet nedan om init-only variabler för sätt att skicka parametrar till __post_init__()
. Se även varningen om hur replace()
hanterar init=False
fält.
Klassvariabler¶
Ett av de få ställen där @dataclass
faktiskt kontrollerar typen av ett fält är för att avgöra om ett fält är en klassvariabel enligt definitionen i PEP 526. Detta görs genom att kontrollera om fältets typ är typing.ClassVar
. Om ett fält är en ClassVar
är det uteslutet från att betraktas som ett fält och ignoreras av dataklassmekanismerna. Sådana pseudofält av typen ClassVar
returneras inte av funktionen fields()
på modulnivå.
Init-only-variabler¶
Ett annat ställe där @dataclass
inspekterar en typannotering är för att avgöra om ett fält är en init-only-variabel. Detta görs genom att se om fältets typ är av typen InitVar
. Om ett fält är av typen InitVar
, betraktas det som ett pseudofält som kallas ett init-only-fält. Eftersom det inte är ett riktigt fält returneras det inte av funktionen fields()
på modulnivå. Init-only-fält läggs till som parametrar i den genererade metoden __init__()
och skickas till den valfria metoden __post_init__()
. De används inte på annat sätt av dataklasser.
Anta t.ex. att ett fält initieras från en databas om inget värde anges när klassen skapas:
@dataklass
klass C:
i: int
j: int | Ingen = Ingen
databas: InitVar[DatabaseType | None] = None
def __post_init__(self, databas):
om self.j är None och databasen inte är None:
self.j = databas.lookup('j')
c = C(10, databas=min_databas)
I det här fallet kommer fields()
att returnera Field
-objekt för i
och j
, men inte för database
.
Frysta instanser¶
Det är inte möjligt att skapa verkligt oföränderliga Python-objekt. Men genom att skicka frozen=True
till @dataclass
dekoratorn kan du emulera oföränderlighet. I så fall kommer dataclasses att lägga till __setattr__()
och __delattr__()
metoder till klassen. Dessa metoder kommer att ge upphov till ett FrozenInstanceError
när de anropas.
Det finns en liten prestandaförlust när man använder frozen=True
: __init__()
kan inte använda enkel tilldelning för att initiera fält, utan måste använda object.__setattr__()
.
Ärftlighet¶
När dataklassen skapas av dekoratorn @dataclass
går den igenom alla klassens basklasser i omvänd MRO (dvs. börjar med object
) och för varje dataklass som den hittar lägger den till fälten från basklassen i en ordnad mappning av fält. När alla basklassens fält har lagts till, lägger den till sina egna fält i den ordnade mappningen. Alla genererade metoder kommer att använda denna kombinerade, beräknade ordnade mappning av fält. Eftersom fälten är i inmatningsordning åsidosätter härledda klasser basklasser. Ett exempel:
@dataklass
klass Bas:
x: Valfritt = 15.0
y: int = 0
@dataklass
klass C(Bas):
z: int = 10
x: int = 15
Den slutliga listan av fält är, i ordning, x
, y
, z
. Den slutliga typen av x
är int
, som specificeras i klassen C
.
Den genererade __init__()
-metoden för C
kommer att se ut så här:
def __init__(self, x: int = 15, y: int = 0, z: int = 10):
Omordning av parametrar som endast innehåller nyckelord i __init__()
¶
Efter att de parametrar som behövs för __init__()
har beräknats, flyttas alla parametrar med endast nyckelord till att komma efter alla vanliga (icke-nyckelordsbara) parametrar. Detta är ett krav på hur parametrar med endast nyckelord implementeras i Python: de måste komma efter parametrar utan endast nyckelord.
I det här exemplet är Base.y
, Base.w
och D.t
fält med endast nyckelord, och Base.x
och D.z
är vanliga fält:
@dataklass
klass Bas:
x: Valfritt = 15.0
_: KW_ONLY
y: int = 0
w: int = 1
@dataklass
klass D(Bas):
z: int = 10
t: int = fält(kw_only=True, default=0)
Den genererade __init__()
-metoden för D
kommer att se ut så här:
def __init__(self, x: Any = 15.0, z: int = 10, *, y: int = 0, w: int = 1, t: int = 0):
Observera att parametrarna har ordnats om från hur de visas i listan över fält: parametrar som härrör från vanliga fält följs av parametrar som härrör från fält som endast innehåller nyckelord.
Den relativa ordningsföljden för parametrar som endast innehåller nyckelord bibehålls i den omordnade parameterlistan __init__()
.
Fabriksinställda funktioner¶
Om en field()
specificerar en default_factory, anropas den med noll argument när ett standardvärde för fältet behövs. Om du t.ex. vill skapa en ny instans av en lista använder du:
mylist: lista = fält(default_factory=lista)
Om ett fält utesluts från __init__()
(med init=False
) och fältet också anger default_factory, kommer default factory-funktionen alltid att anropas från den genererade __init__()
-funktionen. Detta sker eftersom det inte finns något annat sätt att ge fältet ett initialt värde.
Föränderliga standardvärden¶
Python lagrar standardvärden för medlemsvariabler i klassattribut. Tänk på detta exempel, utan att använda dataklasser:
klass C:
x = []
def add(self, element):
self.x.append(element)
o1 = C()
o2 = C()
o1.add(1)
o2.add(2)
assert o1.x == [1, 2]
assert o1.x är o2.x
Observera att de två instanserna av klassen C
delar samma klassvariabel x
, som förväntat.
Med hjälp av dataklasser, om denna kod var giltig:
@dataklass
klass D:
x: list = [] # Denna kod ger upphov till ValueError
def add(self, element):
self.x.append(element)
skulle det generera kod som liknar:
klass D:
x = []
def __init__(self, x=x):
self.x = x
def add(self, element):
self.x.append(element)
assert D().x är D().x
Detta har samma problem som det ursprungliga exemplet med klassen C
. Det vill säga, två instanser av klassen D
som inte anger ett värde för x
när de skapar en klassinstans kommer att dela samma kopia av x
. Eftersom dataklasser bara använder normal Python-klassskapande delar de också detta beteende. Det finns inget allmänt sätt för dataklasser att upptäcka detta tillstånd. Istället kommer dekoratorn @dataklass
att ge upphov till ett ValueError
om den upptäcker en standardparameter som inte går att hasha. Antagandet är att om ett värde är ohashable, så är det mutable. Detta är en partiell lösning, men den skyddar mot många vanliga fel.
Att använda standardfabriksfunktioner är ett sätt att skapa nya instanser av föränderliga typer som standardvärden för fält:
@dataklass
klass D:
x: list = fält(default_factory=list)
assert D().x är inte D().x
Deskriptortypade fält¶
Fält som tilldelas descriptor objects som standardvärde har följande speciella beteenden:
Värdet för det fält som skickas till dataklassens metod
__init__()
skickas till deskriptorns metod__set__()
i stället för att skriva över deskriptorobjektet.På samma sätt anropas deskriptorns
__get__()
- eller__set__()
-metod när fältet hämtas eller ställs in, i stället för att returnera eller skriva över deskriptorobjektet.För att avgöra om ett fält innehåller ett standardvärde anropar
@dataclass
deskriptorns__get__()
-metod med hjälp av dess klassåtkomstform:descriptor.__get__(obj=None, type=cls)
. Om deskriptorn returnerar ett värde i det här fallet kommer det att användas som fältets standardvärde. Å andra sidan, om deskriptorn ger upphov tillAttributeError
i denna situation, kommer inget standardvärde att tillhandahållas för fältet.
klass IntConversionDescriptor:
def __init__(self, *, default):
self._default = default
def __set_name__(self, ägare, namn):
self._name = "_" + namn
def __get__(self, obj, type):
om obj är None:
return self._default
return getattr(obj, self._name, self._default)
def __set__(self, obj, värde):
setattr(obj, self._name, int(värde))
@dataklass
klass InventoryItem:
quantity_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)
i = InventoryItem()
print(i.kvantitet_på_hand) # 100
i.quantity_on_hand = 2.5 # anropar __set__ med 2.5
print(i.quantity_on_hand) # 2
Observera att om ett fält är annoterat med en deskriptortyp, men inte har tilldelats ett deskriptorobjekt som standardvärde, kommer fältet att fungera som ett vanligt fält.