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 hitta field. Ett field 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 ett ValueError.

    Om klassen redan definierar något av __lt__(), __le__(), __gt__(), eller __ge__(), så kommer TypeError 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 är False.

    __hash__() används av den inbyggda hash(), 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 till None, så kan @dataclass lägga till en implicit __hash__()-metod. Även om det inte rekommenderas kan du tvinga @dataklass att skapa en __hash__`() metod med unsafe_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ätta unsafe_hash=True; detta kommer att resultera i ett TypeError.

    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 till None, 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 är object, 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år TypeError.

  • 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 avsnittet KW_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, kommer TypeError att returneras.

Varning

Att skicka parametrar till en basklass __init_subclass__() när man använder slots=True kommer att resultera i ett TypeError. 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änd fields() 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 instans weakref-able. Det är ett fel att ange weakref_slot=True utan att också ange slots=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 och b 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 eftersom None är ett giltigt värde för vissa parametrar med en distinkt betydelse. Ingen kod bör direkt använda MISSING-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__(). Om None (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 än None avråds.

    En möjlig anledning till att ställa in hash=False men compare=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 i MappingProxyType() 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 vara 10, klassattributet C.t kommer att vara 20 och klassattributen C.x och C.y kommer inte att anges.

class dataclasses.Field

Field-objekt beskriver varje definierat fält. Dessa objekt skapas internt och returneras av metoden fields() på modulnivå (se nedan). Användare bör aldrig instansiera ett Field-objekt direkt. Dess dokumenterade attribut är:

  • name: Namnet på fältet.

  • typ: Typen av fält.

  • default, default_factory, init, repr, hash, compare, metadata och kw_only har samma betydelse och värden som de har i funktionen field().

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 med InitVar betraktas som pseudofält och returneras därför varken av funktionen fields() 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öser TypeError om ingen dataklass eller instans av en dataklass skickas. Returnerar inte pseudofält som är ClassVar eller InitVar.

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 med copy.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 till TypeError 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 till TypeError 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 bara name anges, används typing.Any för type. 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år TypeError.

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. Ett ValueError kommer att uppstå i detta fall.

Var uppmärksam på hur fält med init=False fungerar under ett anrop till replace(). De kopieras inte från källobjektet, utan initialiseras i __post_init__(), om de initialiseras överhuvudtaget. Det förväntas att fält med init=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 anpassad replace() (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 returneras False.

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 typen KW_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 typen KW_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 och z 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 med frozen=True. Det är en underklass till AttributeError.

Behandling efter initiering

dataclasses.__post_init__()

När den definieras i klassen kommer den att anropas av den genererade __init__(), normalt som self.__post_init__(). Men om några InitVar-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

Ändrad i version 3.11: Istället för att leta efter och förbjuda objekt av typen list, dict eller set, tillåts nu inte ohashbara objekt som standardvärden. Unhashability används för att approximera mutability.

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 till AttributeError 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.