wadkar
wadkar

Reputation: 960

python refer to class variable while passing arguments to init

What I want to achieve:
1. Have a class variable keeping count of number of objects created
2. That variable should not be available to objects/others i.e. private to the class
3. If specific ID is not provided during init, use this counter variable to assign object.ID
I have following python code

class UserClass(object) :
    __user_id_counter = 0
    def __init__(self, UserID=__user_id_counter) :
        self.UserID = UserID
        __user_id_counter += 1

myuser = UserClass()

but I am getting UnboundLocalError: local variable '_UserClass__user_id_counter' referenced before assignment
I am new to python so kindly help me here :)

Upvotes: 1

Views: 3080

Answers (3)

Chris Morgan
Chris Morgan

Reputation: 90742

To start with, I'm renaming UserClass to User (we know it's a class) and UserID to id (it's in the User class so no need to repeat the "User" bit, and Python style recommends lowercase), and __user_id_counter to __id_counter (ditto). Read PEP 8 for Python style guidelines.

So then, our starting point is this:

class User(object):
    __id_counter = 0
    def __init__(self, id=None) :
        self.id = self.__id_counter if id is None else id
        __id_counter += 1
myuser = User()
myuser2 = User()
myuser3 = User()

Now, the double leading underscore (without a double trailing underscore as in things like __init__ and __metaclass__) is special, and is used for an implementation of class-private variables - read about private variables in the Python manual for more info. It triggers what's called "name mangling". What it ends up as is with __id_counter being renamed to _User__id_counter throughout the class. This is both useful and dangerous. In this case, I think it's probably just not necessary.

This can lead to a problem with subclasses.

class SpecialUser(User):
    def __init__(self):
        self.__id_counter

Now, SpecialUser() will trigger an AttributeError: 'SpecialUser' object has no attribute '_SpecialUser__id_counter'.

So then the question is, do you want subclasses to have the same ID counter, or their own ID counters?

  • If you want them to have the same ID counter, use User.__id_counter. Don't use self.__class__.__id_counter or type(self).__id_counter with the += 1, as this will, for a SpecialUser instance, set SpecialUser._User__id_counter to User._User__id_counter + 1, and then SpecialUser will use its own _User__id_counter from this point onwards, which is far from what you want.

  • If you want them to have their own ID counters, start by using _id_counter rather than __id_counter, as the name mangling will take you in a direction you don't want to go.

    There are a couple of approaches you can take:

    • Use a metaclass to give each class its own counter variable (a more advanced topic than I can be bothered writing about at the moment, ask for more details if you want to).

    • Put in a line _id_counter = 0 in each class definition. (If you don't, you'll run into trouble with ID starting point - make a lot of them, e.g. User(), User(), SpecialUser(), User(), SpecialUser(), SpecialUser(), User(), and they'll have IDs of (respectively) 0, 1, 2, 2, 3, 4, 3 rather than 0, 1, 0, 2, 1, 2.

    An additional inefficiency which would arise from using the name mangling approach here would be that subclasses will have multiple counters in them - _User__id_counter, _SpecialUser__id_counter, etc.

Upvotes: 1

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136208

To access __user_id_counter an object or class reference is needed. In the argument list self or UserClass can not be accessed, hence:

class UserClass(object) :
    __user_id_counter = 0
    def __init__(self, UserID=None) :
        self.UserID = self.__user_id_counter if UserID is None else UserID
        UserClass.__user_id_counter += 1

Upvotes: 4

Constantinius
Constantinius

Reputation: 35039

You have to write self.__user_id_counter. This also looks up class variables, if the instance variable is not found.

EDIT: Sudhi pointed out, that in the argument list, the self declaration is not valid. To circumvent this problem try the following:

class UserClass(object) :
    __user_id_counter = 0
    def __init__(self, UserID=None) :
        if UserID is None:
            self.UserID = self.__user_id_counter
            self.__class__.__user_id_counter += 1
        else:
            self.UserID = UserID

myuser = UserClass()

Upvotes: 1

Related Questions