51

I would like to create a unique ID for each object I created - here's the class:

class resource_cl :
    def __init__(self, Name, Position, Type, Active):
        self.Name = Name
        self.Position = Position
        self.Type = Type
        self.Active = Active

I would like to have a self.ID that auto increments everytime I create a new reference to the class, such as:

resources = []
resources.append(resource_cl('Sam Sneed', 'Programmer', 'full time', True))

I know I can reference resource_cl, but I'm not sure how to proceed from there...

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
meade
  • 22,875
  • 12
  • 32
  • 36

11 Answers11

73

Trying the highest voted answer in python 3 you'll run into an error since .next() has been removed.

Instead you could do the following:

import itertools

class BarFoo:

    id_iter = itertools.count()

    def __init__(self):
        # Either:
        self.id = next(BarFoo.id_iter)

        # Or
        self.id = next(self.id_iter)
        ...
foxyblue
  • 2,859
  • 2
  • 21
  • 29
  • 7
    this should be the accepted answers. It works like a charm! – lmiguelvargasf Sep 25 '19 at 15:12
  • 1
    I want to bookmark this answer. But SA only allows me to bookmark the question! – lllrnr101 May 16 '21 at 14:23
  • 1
    I can click on follow and then I can get easy access! – lllrnr101 May 16 '21 at 14:24
  • What if you want to make a class `FooBar(BarFoo)` and have it use a separate counter? Do you need to redefine `id_iter` in each child class? – Mike S Mar 17 '22 at 22:53
  • This would work in a single interpreter session. However, when you do repeated interpreter sessions in this way you may generate the same id for different object, don't you? – Nick Aug 06 '23 at 15:02
72

Concise and elegant:

import itertools

class resource_cl():
    newid = itertools.count().next
    def __init__(self):
        self.id = resource_cl.newid()
        ...
Algorias
  • 3,043
  • 5
  • 22
  • 16
  • 3
    +1: This is the only reliable and thread-safe way of doing this among all the current answers. `id` may return non-unique values, and simply increasing a class variable inside `__init__` may lead to race-conditions. I also like the idea of storing a referenec to `count().next` instead of directly storing the result of `count()`. – Florian Brucker Jul 06 '12 at 06:59
  • It happens the same object using the id(), e.g. a="123" b="123" id(a) == id(b) – clsung Jan 10 '13 at 23:39
  • @clsung Python strings are "interned" and they are not mutable. "123" is the same object through your Python session. 'a' and 'b' are named references, not "objects" or "instances" of "123". In this case they point to an interned string. Google python intern for more information. – DevPlayer May 05 '17 at 19:35
  • @FlorianBrucker This is true if no instances are deleted. If I delete the first item in list and then append an item to the list that last item will conceptually have the same metaphoric id as the previous last item in the list. – DevPlayer May 05 '17 at 19:39
  • 12
    Python3 has removed `.next()` so you can try this instead: https://stackoverflow.com/a/54318273/3407256 – foxyblue Jan 23 '19 at 00:22
  • 3
    As @foxyblue says, python3 removed `.next()` and replaced it with the `next` builtin. However the `next` builtin just references the `__next__` method of the iterator that `count` returns, so you can just replace `.next` with `.__next__`. – Brionius Sep 25 '19 at 16:15
26

First, use Uppercase Names for Classes. lowercase names for attributes.

class Resource( object ):
    class_counter= 0
    def __init__(self, name, position, type, active):
        self.name = name
        self.position = position
        self.type = type
        self.active = active
        self.id= Resource.class_counter
        Resource.class_counter += 1
S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • 3
    Inside __init__, the class_counter reference is a property of the class. It should look like this: Resource.class_counter += 1 – Edward Dale Jun 25 '09 at 18:09
  • S.Lott, I did try it. With your first version, every object will have an id of 0, and a class_counter of 1. – Matthew Flaschen Jun 25 '09 at 18:16
  • 2
    self will work in the case of accessing the value. So: self.id= self.class_counter Will behave as desired as the attribute resolution will fall back to the class property. This is not the case for setting the attribute as the value set will be in the scope of the instance and not the underlying class. – Mark Roddy Jun 25 '09 at 18:18
  • The += operator can have very surprising results because it works differently for different types. And not just on [im]mutability lines either, e.g. a mutable class with __add__ but not (the perhaps forgotten) __iadd__. –  Jun 25 '09 at 18:21
  • 3
    If you need to support class inheritance, you might want to use `self.__class__.class_counter += 1` instead of `Resource.class_counter += 1` – Jacklynn Jun 18 '15 at 04:38
21

Using count from itertools is great for this:

>>> import itertools
>>> counter = itertools.count()
>>> a = next(counter)
>>> print a
0
>>> print next(counter)
1
>>> print next(counter)
2
>>> class A(object):
...   id_generator = itertools.count(100) # first generated is 100
...   def __init__(self):
...     self.id = next(self.id_generator)
>>> objs = [A(), A()]
>>> print objs[0].id, objs[1].id
100 101
>>> print next(counter) # each instance is independent
3

The same interface works if you later need to change how the values are generated, you just change the definition of id_generator.

18

Are you aware of the id function in python, and could you use it instead of your counter idea?

class C(): pass

x = C()
y = C()
print(id(x), id(y))    #(4400352, 16982704)
llimllib
  • 3,642
  • 1
  • 29
  • 29
  • 1
    I used id(self) within the class - seems to be the right solution for my needs - thanks – meade Jun 25 '09 at 20:07
  • 20
    Remember that values returned by id() are not guaranteed to be unique within one execution of the program (they may be reused as objects are collected). Nor are they guaranteed to follow any specific pattern (you did ask for auto-incrementing originally). –  Jun 26 '09 at 01:51
  • R. Pate: absolutely correct, and important to keep in mind; I just wanted to make sure he was aware of the id function's existence and evaluated it properly before choosing to do something manually. – llimllib Jun 30 '09 at 16:46
  • @Roger Pate. Did you mean that id() returns unique ONLY within one execution of the program. Because that statement just made is true. Whereas you said "not guarenteed to be unique within one execution" which is false. From the docs: id(object)¶ Return the “identity” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value. http://docs.python.org/library/functions.html#id – DevPlayer Jul 18 '12 at 18:39
  • 3
    It is not wise to use the builtin id() if you are going to pickle the object, either for archive or network io. the computer on the other side may have an object with that id() already. – DevPlayer Jul 18 '12 at 18:44
  • 1
    @DevPlayer That last sentence corresponds to the sentence from Roger Pate which you are challenging. – Marcin Jul 19 '12 at 21:32
4

You could attach the count to the class as a class parameter, and then on init you're able to copy this value to an instance parameter.

This makes count a class param, and id an instance param. This works because integers are immutable in python, therefore the current value is copied and not the attribute itself.

class Counter:
    count = 0

    @classmethod
    def incr(self):
        self.count += 1
        return self.count

    def __init__(self):
        self.id = self.incr()

assert [Counter().id for _ in range(3)] == [1, 2, 3]
Rob
  • 3,333
  • 5
  • 28
  • 71
1

Ids sometimes benefit from using some fields of the object you wanted to reference. This is a old style database technique.

for example if you have a app that keeps records for incoming customer phone calls, then possible use an id generated by time = some thing else

ident = '%s:%.4s:%.9s' % ( time.time(), time.clock(), agent.name )
# don't forget to time.clock() once to initialize it

only beaware the time.time() and time.clock() are individual computer return value unless on generated on the server. And if on the server make sure your server clock is set right; as always.

DevPlayer
  • 5,393
  • 1
  • 25
  • 20
1
def create_next_id(cnt=0):
    def create_next_id_inner():
        nonlocal cnt
        cnt += 1
        return cnt - 1
    return create_next_id_inner
...
next_id = create_next_id()
...
my_data = {'id': next_id(), ...}
my_data2 = {'id': next_id(), ...}
...
sergzach
  • 6,578
  • 7
  • 46
  • 84
0

Another note on id() and rethinking other's answer about it. id() may return a unique number, if and only if it remembers every id ever returned even if an object is deleted; which it (id()) does not do. So therefore...

In support of what others were saying that id() does not return a unique number; It is true that it can not guarentee a unique value if and only if you are storing those id() values as references to objects AND that you are deleting the instances of objects you are getting id()s for. BUT ! using id() as a reference means that you basically have an object that has a key linked somehow with another object.

This is not invalidated by non-uniqueness of id(). It is only invalidated if you do not check if adding a new object has a preexisting id() already stored as a reference to some other instance of the object.

storeit = {}

object1 = object()
print id(object1)
4357891223

storeit[ id(object1) ] = object1

object2 = object()
print id(object2)
9834923411

storeit[ id(object2) ] = object2

storeit[ id(object1) ] = object()
del object1

object3 = object()
print id(object3)
# after some 2 gigawatt tries magically i got
4357891223
# the same id as object1 had

BUT storeit[ 4357891223 ] returns some other object instance not object3; therefore the < link > remains valid but the uniqueness fails.

DevPlayer
  • 5,393
  • 1
  • 25
  • 20
0

I like to use generators for ids. Allow the generator to maintain a list of ids already used.

# devplayer@gmail.com
# 2012-07(jul)-19


class MakeUniqueStr(object):
    '''
    unqstr = MakeUniqueStr(default_name='widget', sep='_')
    print(repr(unqstr('window')))
    print(repr(unqstr('window')))
    print(repr(unqstr('window')))
    print(repr(unqstr('hello')))
    print(repr(unqstr('hello')))
    print(repr(unqstr('window')))
    print(repr(unqstr('hello')))

    'window'
    'window_00000'
    'window_00001'
    'hello'
    'hello_00000'
    'window_00002'
    'hello_00001'
    '''

    def __init__(self, default_name='default', sep='_'):
        self.default_name = default_name
        self.last_base_name = default_name
        self.sep = sep

        self.used_names = []

        self.generator = self.Generator()
        self.generator.next() # initialize

    def __call__(self, name=None):
        if name <> None: self.last_base_name = name
        return self.generator.send(self.last_base_name)

    def _MakeName(self, name, index=1):
        '''_MakeName is called by the Generator. 

        Generator will always have a name and an index to pass to _MakeName. 
        Over ride this method to customize it.'''

        return name + self.sep + '%0.5d' % index

    def Generator(self):
        try_name = yield 'ready' # initialize
        index = 0
        while 1:

            if try_name not in self.used_names:
                self.used_names.append( try_name )
                sent_name = yield try_name
                try_name = sent_name
                continue

            try_name = self._MakeName( sent_name, index )
            while try_name in self.used_names:
                index += 1
                try_name = self._MakeName( sent_name, index )

            index = 0

Although this is not a memory effiecent way for huge in-memory datasets. If you wanted to use something like that then modify this to have the OS handle caching to a file... say via a named pipe.

DevPlayer
  • 5,393
  • 1
  • 25
  • 20
-1

Hello this may be the Lengthy way, But Worked very Fluently for me. Hope it Helps. I have done it using a External Text file named id.txt.

Just create an empty file named as above. Then run this snippet. That will definitely work.

def id_generator():
with open("id.txt", "r") as f:
    f.seek(0)
    fc = f.read(1)
    if not fc:
        with open("id.txt", "w") as f1:
            f1.write("1")
        id = 1
    else:
        f.seek(0)
        fc = f.read(1)
        nv = int(fc) + 1
        with open("id.txt", "w") as f2:
            f2.write(str(nv))
        id = nv

return id

And to get the Value from this Snippet do this.

id = id_generator()

If any of the Reader find it useful, Please pass a Comment and Let me know if my work Paid off.

Hope it helps. Thank You......

Rahul Pandey
  • 115
  • 1
  • 7