python препраща към променлива на класа, докато предава аргументи на init

Какво искам да постигна:
1. Имам променлива на класа, която да отчита броя на създадените обекти
2. Тази променлива не трябва да е достъпна за обекти/други, т.е. частна за класа
3. Ако конкретният идентификатор не се предоставя по време на init, използвайте тази променлива на брояча, за да присвоите 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 като UserID - 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 : знам, че името mangling означава, че нищо не е частно в python, но бих предпочел да направя по-труден за подкласовете достъп до този брояч, отколкото да го направя публичен и да позволя да го обърквате. - person Sudhi; 27.07.2011

Трябва да напишете self.__user_id_counter. Това също търси променливи на класа, ако променливата на екземпляра не е намерена.

РЕДАКТИРАНЕ: Sudhi посочи, че в списъка с аргументи декларацията 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 броячи, започнете с _id_counter вместо __id_counter, тъй като манипулирането на името ще ви отведе в посока, в която не искате да вървите.

    Има няколко подхода, които можете да предприемете:

    • Използвайте метаклас, за да дадете на всеки клас собствена променлива на брояча (по-напреднала тема, отколкото мога да се занимавам да пиша в момента, поискайте повече подробности, ако искате).

    • Поставете ред _id_counter = 0 във всяка дефиниция на клас. (Ако не го направите, ще имате проблеми с началната точка на ID - направете много от тях, напр. 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 брояч и ще заменя моя __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