Using class or static method as default_factory in dataclasses
Question:
I want to populate an attribute of a dataclass using the default_factory
method. However, since the factory method is only meaningful in the context of this specific class, I want to keep it inside the class (e.g. as a static or class method). For example:
from dataclasses import dataclass, field
from typing import List
@dataclass
class Deck:
cards: List[str] = field(default_factory=self.create_cards)
@staticmethod
def create_cards():
return ['King', 'Queen']
However, I get this error (as expected) on line 6:
NameError: name ‘self’ is not defined
How can I overcome this issue? I don’t want to move the create_cards()
method out of the class.
Answers:
One possible solution is to move it to __post_init__(self)
. For example:
@dataclass
class Deck:
cards: List[str] = field(default_factory=list)
def __post_init__(self):
if not self.cards:
self.cards = self.create_cards()
def create_cards(self):
return ['King', 'Queen']
Output:
d1 = Deck()
print(d1) # prints Deck(cards=['King', 'Queen'])
d2 = Deck(["Captain"])
print(d2) # prints Deck(cards=['Captain'])
I adapted momo’s answer to be self contained in a class and without thread-safety (since I was using this in asyncio.PriorityQueue context):
from dataclasses import dataclass, field
from typing import Any, ClassVar
@dataclass(order=True)
class FifoPriorityQueueItem:
data: Any=field(default=None, compare=False)
priority: int=10
sequence: int=field(default_factory=lambda: {0})
counter: ClassVar[int] = 0
def get_data(self):
return self.data
def __post_init__(self):
self.sequence = FifoPriorityQueueItem.next_seq()
@staticmethod
def next_seq():
FifoPriorityQueueItem.counter += 1
return FifoPriorityQueueItem.counter
def main():
import asyncio
print('with FifoPriorityQueueItem is FIFO')
q = asyncio.PriorityQueue()
q.put_nowait(FifoPriorityQueueItem('z'))
q.put_nowait(FifoPriorityQueueItem('y'))
q.put_nowait(FifoPriorityQueueItem('b', priority=1))
q.put_nowait(FifoPriorityQueueItem('x'))
q.put_nowait(FifoPriorityQueueItem('a', priority=1))
while not q.empty():
print(q.get_nowait().get_data())
print('without FifoPriorityQueueItem is no longer FIFO')
q.put_nowait((10, 'z'))
q.put_nowait((10, 'y'))
q.put_nowait((1, 'b'))
q.put_nowait((10, 'x'))
q.put_nowait((1, 'a'))
while not q.empty():
print(q.get_nowait()[1])
if __name__ == '__main__':
main()
Results in:
with FifoPriorityQueueItem is FIFO
b
a
z
y
x
without FifoPriorityQueueItem is no longer FIFO
a
b
x
y
z
One option is to wait until after you define the field
object to make create_cards
a static method. Make it a regular function, use it as such to define the cards
field, then replace it with a static method that wraps the function.
from dataclasses import dataclass, field
from typing import List
@dataclass
class Deck:
# Define a regular function first (we'll replace it later,
# so it's not going to be an instance method)
def create_cards():
return ['King', 'Queen']
# Use create_cards as a regular function
cards: List[str] = field(default_factory=create_cards)
# *Now* make it it a static method
create_cards = staticmethod(cards)
This works because the field
object is created while the class is being defined, so it doesn’t need to be a static method yet.
I want to populate an attribute of a dataclass using the default_factory
method. However, since the factory method is only meaningful in the context of this specific class, I want to keep it inside the class (e.g. as a static or class method). For example:
from dataclasses import dataclass, field
from typing import List
@dataclass
class Deck:
cards: List[str] = field(default_factory=self.create_cards)
@staticmethod
def create_cards():
return ['King', 'Queen']
However, I get this error (as expected) on line 6:
NameError: name ‘self’ is not defined
How can I overcome this issue? I don’t want to move the create_cards()
method out of the class.
One possible solution is to move it to __post_init__(self)
. For example:
@dataclass
class Deck:
cards: List[str] = field(default_factory=list)
def __post_init__(self):
if not self.cards:
self.cards = self.create_cards()
def create_cards(self):
return ['King', 'Queen']
Output:
d1 = Deck()
print(d1) # prints Deck(cards=['King', 'Queen'])
d2 = Deck(["Captain"])
print(d2) # prints Deck(cards=['Captain'])
I adapted momo’s answer to be self contained in a class and without thread-safety (since I was using this in asyncio.PriorityQueue context):
from dataclasses import dataclass, field
from typing import Any, ClassVar
@dataclass(order=True)
class FifoPriorityQueueItem:
data: Any=field(default=None, compare=False)
priority: int=10
sequence: int=field(default_factory=lambda: {0})
counter: ClassVar[int] = 0
def get_data(self):
return self.data
def __post_init__(self):
self.sequence = FifoPriorityQueueItem.next_seq()
@staticmethod
def next_seq():
FifoPriorityQueueItem.counter += 1
return FifoPriorityQueueItem.counter
def main():
import asyncio
print('with FifoPriorityQueueItem is FIFO')
q = asyncio.PriorityQueue()
q.put_nowait(FifoPriorityQueueItem('z'))
q.put_nowait(FifoPriorityQueueItem('y'))
q.put_nowait(FifoPriorityQueueItem('b', priority=1))
q.put_nowait(FifoPriorityQueueItem('x'))
q.put_nowait(FifoPriorityQueueItem('a', priority=1))
while not q.empty():
print(q.get_nowait().get_data())
print('without FifoPriorityQueueItem is no longer FIFO')
q.put_nowait((10, 'z'))
q.put_nowait((10, 'y'))
q.put_nowait((1, 'b'))
q.put_nowait((10, 'x'))
q.put_nowait((1, 'a'))
while not q.empty():
print(q.get_nowait()[1])
if __name__ == '__main__':
main()
Results in:
with FifoPriorityQueueItem is FIFO
b
a
z
y
x
without FifoPriorityQueueItem is no longer FIFO
a
b
x
y
z
One option is to wait until after you define the field
object to make create_cards
a static method. Make it a regular function, use it as such to define the cards
field, then replace it with a static method that wraps the function.
from dataclasses import dataclass, field
from typing import List
@dataclass
class Deck:
# Define a regular function first (we'll replace it later,
# so it's not going to be an instance method)
def create_cards():
return ['King', 'Queen']
# Use create_cards as a regular function
cards: List[str] = field(default_factory=create_cards)
# *Now* make it it a static method
create_cards = staticmethod(cards)
This works because the field
object is created while the class is being defined, so it doesn’t need to be a static method yet.