5

I have the following data class.

@dataclass(frozen=True)
class myDataClass:
    x: float
    y: float

What I want is that every time I create an object of this class, it gets labeled with a unique id which increments from 0.

So, the first time I say first = myDataClass(0, 1) then I should have first.id == 0, and then if I say second = myDataClass(0, 1), I should get second.id == 1.

Arne
  • 17,706
  • 5
  • 83
  • 99
adminboss
  • 115
  • 4

3 Answers3

5

A thread-safe unique ID can be generated by using an itertools.count() instance's __next__ method as the default_factory for the field:

from dataclasses import dataclass, field
from itertools import count

@dataclass(frozen=True)
class myDataClass:
    x: float
    y: float
    id: int = field(default_factory=count().__next__, init=False)

It's faster than other options, involves no custom code, and, at least on the CPython reference interpreter, is thread-safe (itertools.count is implemented in C and does not release the GIL, so there's no chance of two instances getting the same id).

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
2

It's a little tricky for frozen dataclasses because any code that you write which dynamically updates a field runs into a FrozenInstanceError, but it's certainly possible:

from dataclasses import dataclass, field

@dataclass(frozen=True)
class myDataClass:
    x: float
    y: float
    id: int = field(init=False)

    def __post_init__(self):
        if not hasattr(myDataClass, "_COUNT"):
            myDataClass._COUNT = 0
        object.__setattr__(self, "id", myDataClass._COUNT)
        myDataClass._COUNT += 1

Which should work as you expected:

>>> (myDataClass(1.0,2.0))
myDataClass(x=1.0, y=2.0, id=0)
>>> (myDataClass(1.0,2.0))
myDataClass(x=1.0, y=2.0, id=1)
>>> (myDataClass(1.0,2.0))
myDataClass(x=1.0, y=2.0, id=2)
>>> (myDataClass(1.0,2.0))
myDataClass(x=1.0, y=2.0, id=3)
Arne
  • 17,706
  • 5
  • 83
  • 99
1

You can use default_factory for attributes of dataclasses that function like you want, and use a class variable to keep track of the maximum id used.

from dataclasses import dataclass, field
from typing import ClassVar


def _assign_id():
    new_id = myDataClass._next_id
    myDataClass._next_id += 1
    return new_id


@dataclass(frozen=True)
class myDataClass:
    x: float
    y: float
    id: int = field(default_factory=_assign_id, init=False)
    _next_id: ClassVar[int] = 0
Jasmijn
  • 9,370
  • 2
  • 29
  • 43