python ссылается на переменную класса при передаче аргументов в init

Чего я хочу добиться:
1. Иметь переменную класса, в которой будет подсчитываться количество созданных объектов
2. Эта переменная не должна быть доступна для объектов/других, т.е. закрыта для класса
3. Если конкретный идентификатор не предоставляется во время инициализации, используйте эту переменную счетчика для назначения object.ID
У меня есть следующий код Python

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

myuser = UserClass()

но я получаю UnboundLocalError: local variable '_UserClass__user_id_counter' referenced before assignment
Я новичок в python, поэтому помогите мне здесь :)


person Sudhi    schedule 27.07.2011    source источник
comment
Не редактируйте вопрос, чтобы указать ответ. Отметьте ответ, который вы хотите сделать ответом, нажав на галочку.   -  person Chris Morgan    schedule 27.07.2011
comment
@chris-morgan: спасибо, буду иметь в виду.   -  person Sudhi    schedule 27.07.2011


Ответы (3)


Для доступа к __user_id_counter требуется ссылка на объект или класс. В списке аргументов self или UserClass нет доступа, следовательно:

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
person Maxim Egorushkin    schedule 27.07.2011
comment
спасибо, это решает проблему передачи аргументов, но не увеличивает счетчик при создании следующего объекта. Используя ваш код, я создал 3 объекта, но все они имеют 0 в качестве идентификатора пользователя. - person Sudhi; 27.07.2011
comment
Вы должны увеличить переменную класса. Вы делаете это с self.__class__.__user_id_counter, как в моем ответе. - person Constantinius; 27.07.2011
comment
так что вместо self.__user_id_counter += 1 я должен сделать self.__class__.__user_id_counter += 1 ?? - person Sudhi; 27.07.2011
comment
Ответ - да. Большое спасибо Максиму-Егорушкину и @Constantinius. - person Sudhi; 27.07.2011
comment
Проголосовали против, так как этот ответ не решает проблему из-за изменения его в self, а не UserClass. - person Chris Morgan; 27.07.2011
comment
Исправлено. `UserClass.__user_id_counter += 1` - person Maxim Egorushkin; 27.07.2011
comment
@chris-morgan, вы говорите, что я должен ссылаться на имя класса? Конечно, этот класс будет унаследован другими классами, так что в этом случае это не сработает? Что ты предлагаешь? - person Sudhi; 27.07.2011
comment
@Chris: ты слишком добр, спасибо) - person Maxim Egorushkin; 27.07.2011
comment
@Sudhi: если вы используете ведущее двойное подчеркивание, вы все равно можете столкнуться с проблемами при работе с ним в подклассах, в зависимости от того, как вы это делаете, из-за происходящего искажения имени. - person Chris Morgan; 27.07.2011
comment
@Sudhi: если вы хотите использовать разные счетчики для каждого подтипа, вам нужно быть очень осторожным в разных местах в отношении того, как вы это делаете и где вы с ним работаете. Очень высока вероятность того, что при первой попытке вы ошибетесь. Если вы действительно этого хотите, я напишу ответ, объясняющий, в чем будут заключаться проблемы и как их избежать (в конце концов, сделать это должным образом довольно сложно). - person Chris Morgan; 27.07.2011
comment
@chris-morgan, пожалуйста, сделайте это, это очень поможет! Вы предлагаете мне сделать user_id_counter общедоступной переменной? Я всегда думал, что внутренние переменные должны быть только приватными. - person Sudhi; 27.07.2011
comment
@Sudhi: это двойное ведущее подчеркивание, которое делает его особенным. См. docs.python.org/tutorial/classes.html#private-variables - person Chris Morgan; 27.07.2011
comment
@chris-morgan: я знаю, изменение имени означает, что в python нет ничего частного, но я бы предпочел затруднить доступ подклассов к этому счетчику, чем сделать его общедоступным и позволить его испортить. - person Sudhi; 27.07.2011

Вы должны написать self.__user_id_counter. Это также ищет переменные класса, если переменная экземпляра не найдена.

EDIT: Судхи указал, что в списке аргументов объявление self недействительно. Чтобы обойти эту проблему, попробуйте следующее:

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()
person Constantinius    schedule 27.07.2011
comment
если я делаю __init__(self, UserID=self.__user_id_counter) :, он говорит NameError name self not definied - person Sudhi; 27.07.2011
comment
Я понимаю. Кажется, что это невозможно в списке аргументов. Я думаю, вам нужно сделать небольшой трюк. Я обновлю свой ответ. - person Constantinius; 27.07.2011

Для начала я переименовываю UserClass в User (мы знаем, что это класс) и UserID в id (это класс User, поэтому нет необходимости повторять бит «Пользователь», а стиль Python рекомендует строчные буквы) и __user_id_counter в __id_counter (то же самое). Прочтите PEP 8, чтобы ознакомиться с рекомендациями по стилю Python.

Итак, наша отправная точка такова:

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()

Теперь двойное подчеркивание в начале (без двойного подчеркивания в конце, как в таких вещах, как __init__ и __metaclass__) является особенным и используется для реализации частных переменных класса — читайте о частные переменные в руководстве по Python для получения дополнительной информации. Это вызывает так называемое «искажение имени». Что это заканчивается, так это то, что __id_counter переименовывается в _User__id_counter по всему классу. Это и полезно, и опасно. В данном случае, я думаю, это, вероятно, просто не нужно.

Это может привести к проблемам с подклассами.

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

Теперь SpecialUser() вызовет AttributeError: 'SpecialUser' object has no attribute '_SpecialUser__id_counter'.

Итак, вопрос в том, хотите ли вы, чтобы подклассы имели один и тот же счетчик ID или свои собственные счетчики ID?

  • Если вы хотите, чтобы у них был одинаковый счетчик ID, используйте User.__id_counter. Не используйте self.__class__.__id_counter или type(self).__id_counter с += 1, так как это для экземпляра SpecialUser установит SpecialUser._User__id_counter на User._User__id_counter + 1, а затем SpecialUser будет использовать собственный _User__id_counter с этой точки вперед, что далеко от того, что вы хотите.

  • Если вы хотите, чтобы у них были собственные счетчики идентификаторов, начните с использования _id_counter, а не __id_counter, так как искажение имени приведет вас в нежелательном направлении.

    Есть несколько подходов, которые вы можете предпринять:

    • Используйте метакласс, чтобы дать каждому классу свою собственную переменную-счетчик (более сложная тема, чем я могу писать о ней в данный момент, запросите более подробную информацию, если хотите).

    • Вставьте строку _id_counter = 0 в каждое определение класса. (Если вы этого не сделаете, у вас возникнут проблемы с отправной точкой идентификатора — сделайте их много, например, User(), User(), SpecialUser(), User(), SpecialUser(), SpecialUser(), User(), и они будут иметь идентификаторы (соответственно) 0, 1, 2, 2, 3, 4, 3, а не 0, 1, 0, 2, 1, 2.

    Дополнительная неэффективность, которая может возникнуть из-за использования здесь подхода с изменением имени, заключается в том, что подклассы будут иметь несколько счетчиков в них - _User__id_counter, _SpecialUser__id_counter и т. д.

person Chris Morgan    schedule 27.07.2011
comment
это здорово, теперь я понимаю, что происходит внутри python, когда я объявляю __id_counter , я думаю, что буду придерживаться прежнего случая счетчика идентификаторов тот же и заменю свой __id_counter на _id_counter. Большое спасибо, Крис, я уверен, что я буду много беспокоить вас с большим количеством таких запросов python: D Жаль, что я не могу отметить несколько ответов на свой вопрос :( - person Sudhi; 28.07.2011
comment
@Sudhi: вы можете проголосовать за него, если хотите (нажмите стрелку вверх). - person Chris Morgan; 28.07.2011
comment
не могу, недостаточно репутации :( Я сделаю это, когда получу больше репутации в stackoverflow - person Sudhi; 28.07.2011