7
class Cls:
    counter = 0
    def __init__(self, name):
        self.name = name
        self.counter += 1
    def count(self):
        return self.counter

I'm learning python, what I want to have is a static counter that counts the number of times the class has been instantiated, but every time I create an instance counter gets recreated and count() function always returns 1. I want something that in java would look like this

public class Cls {
    private static int counter = 0;
    private String name;
    public Cls(String name) {
        this.name = name;
        counter ++;
    }
    public static int count(){
        return counter;
    }
}
Duncan
  • 92,073
  • 11
  • 122
  • 156
MrSir
  • 576
  • 2
  • 11
  • 29

2 Answers2

17

There are two ways to access a class attribute: you can either access it directly on a class, or you can read it through self (but not rebind it). Accessing a class attribute through self won't work if there is already a value set directly on the instance so you would normally try to use the class to access a class attribute.

class Cls:
    counter = 0
    def __init__(self, name):
        self.name = name
        Cls.counter += 1
    def count(self):
        return Cls.counter

When you write self.counter += 1 this is a shorthand for self.counter = self.counter + 1 and as with any other binding through self it sets an instance attribute.

This can be useful if you want a default value for instance attributes, you can set them as class attributes and then just update them in the instances which want different values, but to avoid confusion you probably want to avoid using self at all when accessing class attributes.

You can also consider making the count method into a class method and moving the increment into another method:

@classmethod
def increment(cls):
    cls.counter += 1

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

if you do that then each subclass will have its own independent counter. That may or may not be what you want here. The cls parameter here is the class that was actually instantiated, this can be useful if you can a whole class hierarchy, or even just a base class CountsInstances where you can put this code once and reuse it with multiple independent counters.

Decorating each function with @staticmethod will give you something close to the Java code:

class Cls:
    counter = 0
    def __init__(self, name):
        self.name = name
        self.increment()

    @staticmethod
    def increment():
        Cls.counter += 1

    @staticmethod
    def count():
        return Cls.counter
Duncan
  • 92,073
  • 11
  • 122
  • 156
  • It may be worth mentioning the difference between a class vs. static method here—the relevant bit being that a class method optionally lets subclasses each keep their own counter, while a static method forces them all to share a counter. Then again, you’ve already explained quite a bit, so maybe that extra info would just overload the OP? Not sure, but it’s your answer so I don’t have to be sure. :) – abarnert Apr 09 '18 at 15:57
  • @abarnert I expanded it slightly but I'll let your comment lead on that: there's a huge can of worms that could be opened here once you get into inheritance. – Duncan Apr 09 '18 at 16:02
  • I think you found a great balance of raising the issue but not getting bogged down in it. – abarnert Apr 09 '18 at 17:34
0

Do not use Cls.

class MyClass:
    counter = 0
    def __init__(self, name):
        self.name = name
        self.counter += 1  # this creates an instance variable counter 
                           # thats initialized by counter if you do not set it
                           # it is NOT shared between instances, but specific to each 

Instead you should increment the static variable:

    def __init__(self, name):
        self.name = name
        MyClass.counter += 1  # this increments the static class variable  

If you fix

    @staticmethod
    def count():
        return MyClass.counter

this way, you can still call count() on instances as well as directly on the class.

t = MyClass("some")
print( MyClass.count() )  # fine

t1 = MyClass("other")
print( t.count() )        # only allowed if prefix the method with @staticmethod

Output:

1
2

See What is the difference between @staticmethod and @classmethod in Python? for further infos.

Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
  • I guess the explanation would be more understandable and helpfull if u mention how final class looks like... – AviC May 07 '21 at 07:33
  • 1
    @AviC note sure what you mean. The fix is to change `self.counter += 1` to `MyClass.counter += 1` so the CLASS-counter variable is incremented - not the INSTANCE-copy of that value. I provided the changed method. The change using `@staticmethod` on `count()` makes it clear that this accesses the CLASS variable. If you keep using `def count(self): return MyClass.counter` you "suggest" that `count` is a method on the instance of a class - but you return the class-variable and never use the `self` to access anything of the instance - so it is confusing and a bit cheaty. – Patrick Artner May 07 '21 at 08:13