7

I need a unique (unsigned int) id for my python data class. This is very similar to this so post, but without explicit ctors.

import attr
from attrs import field
from itertools import count
@attr.s(auto_attribs=True)
class Person:
    #: each Person has a unique id
    _counter: count[int] = field(init=False, default=count())
    _unique_id: int = field(init=False)

    @_unique_id.default
    def _initialize_unique_id(self) -> int:
        return next(self._counter)

Is there any more-"pythonic" solution?

OrenIshShalom
  • 5,974
  • 9
  • 37
  • 87
  • 1
    Just how "unique" should "unique" be? Have you researched the `uuid` module? – DeepSpace Feb 20 '22 at 14:09
  • as far as counters go, I think `itertools.count()` is perfect for me, but the whole mechanism of defining the equivalent of a static cpp data member - I wanted to know if there is any other way of doing it – OrenIshShalom Feb 20 '22 at 14:13
  • Is a static variable that's incremented in the constructor a bad idea? – user3638162 Feb 20 '22 at 14:37

1 Answers1

10

Use a default factory instead of just a default. This allows to define a call to get the next id on each instantiation.
A simple means to get a callable that counts up is to use count().__next__, the equivalent of calling next(...) on a count instance.1

The common "no explicit ctor" libraries attr and dataclasses both support this:

from itertools import count
from dataclasses import dataclass, field

@dataclass
class C:
    identifier: int = field(default_factory=count().__next__)

import attr

@attr.s
class C:
    identifier: int = attr.field(factory=count().__next__)

To always use the automatically generated value and prevent passing one in as a parameter, use init=False.

@dataclass
class C:
    identifier: int = field(default_factory=count().__next__, init=False)

1 If one wants to avoid explicitly addressing magic methods, one can use a closure over a count. For example, factory=lambda counter=count(): next(counter).

MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119