41

I've been cleaning up some code from a module I'm extending and I can't seem to find a way to Pythonify this code:

global_next_id = 1

class Obj:
  def __init__(self):
    global global_next_id
    self.id = global_next_id

    global_next_id += 1

This code uses a global id to keep track of instances of a class (I need the variable self.id internally as well, and it needs to be a number).

Can anyone suggest a way to Pythonify this code?

Blender
  • 289,723
  • 53
  • 439
  • 496

7 Answers7

81

You could consider using a class attribute to provide a counter. Each instance needs only to ask for the next value. They each get something unique. Eg:

from itertools import count

class Obj(object):
  _ids = count(0)

  def __init__(self):
    self.id = next(self._ids)
g.d.d.c
  • 46,865
  • 9
  • 101
  • 111
  • 1
    I completely overlooked class-wide variables... Thanks for the `itertools` solution! – Blender Dec 25 '11 at 04:03
  • 1
    I wish I could claim sole credit - I saw it somewhere here on another post. It's elegant and efficient. – g.d.d.c Dec 25 '11 at 04:09
  • 1
    What happens when an instance get deleted, does the count went down? – Negative Zero Jun 04 '15 at 16:56
  • 1
    @NegativeZero - No, there's nothing in this that keeps track of the instances after they're deleted. It simply numerically increases the counter for each new instance as it is created. You'd have to implement some sort of registration mechanism for instances, then deal with reused ids in some way to avoid collisions. That sounds like overkill when the goal is just to number instances as they're created. – g.d.d.c Jun 04 '15 at 18:08
  • 2
    @g.d.d.c - I see. I thought the goal is to count the number of instances that are currently associated with a class. Yeah, if the goal is to count the instances that are ever created, then the solution given is fine. – Negative Zero Jun 04 '15 at 19:22
  • 4
    In Python 3.6 at least, `self._ids.next()` does not work (object has no attribute `next`). Instead, use `next(self._ids)`. This also works in Python 2.7. –  Jun 01 '17 at 09:54
  • 2
    @yucer - According to https://stackoverflow.com/questions/7083348/is-itertools-thread-safe, count is atomically thread safe, yes. The same can't be said for all of itertools, but count is implemented in a way that should be. – g.d.d.c Jun 29 '21 at 16:28
28

This should do the job:

class Obj:
    _counter = 0
    def __init__(self):
        Obj._counter += 1
        self.id = Obj._counter
Andreas K.
  • 9,282
  • 3
  • 40
  • 45
  • Most pythonic answer. – Lucas Vazquez Dec 08 '19 at 19:24
  • I was wondering how it would handle the initialization to zero of `_counter`. It looks like it only initializes it to zero for for the very first instance of this class created, and NOT for the next instances created. This is like a `static` variable in C or C++, including `static` class variables in C++. – Gabriel Staples Mar 14 '21 at 07:03
9

Here is a way to count instances without descendant classes sharing the same id/count. A metaclass is used to create a separate id counter for each class.

Uses Python 3 syntax for Metaclasses.

import itertools

class InstanceCounterMeta(type):
    """ Metaclass to make instance counter not share count with descendants
    """
    def __init__(cls, name, bases, attrs):
        super().__init__(name, bases, attrs)
        cls._ids = itertools.count(1)

class InstanceCounter(object, metaclass=InstanceCounterMeta):
    """ Mixin to add automatic ID generation
    """
    def __init__(self):
        self.id = next(self.__class__._ids)
ratiotile
  • 973
  • 8
  • 16
  • Note that in the `InstanceCounter` class, the incrementing can simplified to `self.id = next(self._ids)`. – martineau Jun 06 '22 at 00:33
5

I found the following solution:

class Obj:
    counter = 0

    def __init__(self):
        type(self).counter += 1

    def __del__(self):
        type(self).counter -= 1

It's better to use type(self).counter instead of Obj.counter

W Stokvis
  • 1,409
  • 8
  • 15
Jordan Russev
  • 57
  • 1
  • 3
  • Why is it better to use `type(self).counter` instead of `Obj.counter` ? – András Aszódi Feb 08 '19 at 15:42
  • 1
    @W Stokvis Below is written about __del__(), "The __del__() method is a known as a destructor method in Python. It is called when all references to the object have been deleted i.e when an object is garbage collected." So, refering through variable "self" or type(self) is risky. Obj.counter is safer option. – manu Mar 06 '19 at 08:08
2

You can use dir() function, which returns all properties and functions in the current script, to count the numbers of instances of a certain class.

len([i for i in dir() if isinstance(eval(i), ClassName)])
  • Thanks. Benjamin Wang. It worked fantastically, and a one-liner too. Excellent! I'm so glad I scrolled down because as someone learning about classes, I didn't really understand the other verbose answers - as amazing and helpful as they obviously are. – Micklos May 01 '23 at 12:57
1

Generator?

def get_next_id():
    curr_id = 1
    while True:
        yield curr_id
        curr_id += 1
jscs
  • 63,694
  • 13
  • 151
  • 195
0
class InstanceCounter(object):
  # the instance counter
  counter = 0

  def __init__(self, val):
    self.val = all
    # incrementing every time an instance is created
    InstanceCounter.counter += 1

  def set_val(self, val):
    self.val = val

  def get_val(self, val):
    return self.val

  # accessing the instance counter should be done through a class method

  @classmethod
  def get_counter(cls):  
    return cls.counter

# See the instance counter as it increments as new instances are created
a=InstanceCounter(5)
print(a.get_counter())
b=InstanceCounter(7)
print(a.get_counter(), b.get_counter())
c=InstanceCounter(9)
print(a.get_counter(), b.get_counter(), c.get_counter())
Nelson
  • 1