python 在传递参数给 init 时引用类变量

1 投票
3 回答
3013 浏览
提问于 2025-04-16 22:20

我想实现的目标是:
1. 有一个类变量,用来统计创建了多少个对象
2. 这个变量不应该对对象或其他地方可见,也就是说它应该是类的私有变量
3. 如果在初始化时没有提供特定的ID,就用这个计数变量来给对象的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,所以请帮帮我 :)

3 个回答

1

你需要写 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()
1

首先,我把 UserClass 改成 User(我们知道这是一个类),把 UserID 改成 id(因为它在 User 类里,所以没必要重复“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_countertype(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(),它们的 ID 分别会是 0, 1, 2, 2, 3, 4, 3,而不是 0, 1, 0, 2, 1, 2。)

    使用名称改编的方法还会带来额外的低效,因为子类会有多个计数器,比如 _User__id_counter_SpecialUser__id_counter 等等。

4

要访问 __user_id_counter,你需要一个对象或类的引用。在参数列表中,selfUserClass 是无法直接访问的,所以:

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

撰写回答