【问题标题】:Python Class scope & listsPython 类范围和列表
【发布时间】:2010-06-30 20:11:48
【问题描述】:

我对 Python 还是很陌生,我的 OO 经验来自 Java。因此,鉴于以下代码,我有一些我用 Python 编写的代码对我来说非常不寻常:

class MyClass():
        mylist = []
        mynum = 0

        def __init__(self):
                # populate list with some value.
                self.mylist.append("Hey!")
                # increment mynum.

                self.mynum += 1

a = MyClass()

print a.mylist
print a.mynum

b = MyClass()

print b.mylist
print b.mynum

运行它会产生以下输出:

['Hey!']
1
['Hey!', 'Hey!']
1

显然,我希望类变量产生相同的确切数据和相同的确切输出...我似乎在任何地方都找不到是什么使列表与字符串或数字不同,为什么该列表是否引用了后续实例中第一个实例化的相同列表?显然我可能误解了某种范围机制或列表创建机制..

【问题讨论】:

    标签: python


    【解决方案1】:

    tlayton 的回答是故事的一部分,但它并不能解释一切。

    添加一个

    print MyClass.mynum
    

    变得更加困惑:)。它将打印“0”。为什么?因为线

    self.mynum += 1
    

    创建一个实例变量并随后增加它。它不会增加 class 变量。

    mylist 的故事不同。

    self.mylist.append("Hey!")
    

    不会创建列表。它期望存在具有“附加”功能的变量。由于实例没有这样的变量,它最终会引用类中的变量,因为您已对其进行了初始化,所以 确实存在。就像在 Java 中一样,实例可以“隐式”引用类变量。像“类字段应该由类引用,而不是由实例引用”(或类似的东西;自从我在 Java 中看到它已经有一段时间了)这样的警告将是有序的。添加一行

    print MyClass.mylist
    

    验证这个答案:)。

    简而言之:您正在初始化类变量并更新实例变量。实例可以引用类变量,但一些“更新”语句会自动为您创建实例变量。

    【讨论】:

    • 这还是漏掉了重点;请参阅@Ken 的回答。 self.mynum += 1 扩展为 self.mynum = self.mynum + 1。执行此操作时,首先评估右侧。 self.mynum 在实例上查找 mynum 并找不到它(至少在第一次调用 __init__ 时),然后在类上查找(并找到)mynum。所以右侧的self.mynum 的计算结果是MyClass.mynum 的值。该值现在分配给self.mynum,这里指的是实例变量。
    【解决方案2】:

    我认为区别在于+= 是一个赋值(与=+ 相同),而append 就地更改对象。

        mylist = []
        mynum = 0
    

    这会在类定义时一次性分配一些类变量。

        self.mylist.append("Hey!")
    

    这会通过附加一个字符串来更改值MyClass.mylist

        self.mynum += 1
    

    这与self.mynum = self.mynum + 1 相同,即它分配self.mynum(实例成员)。从 self.mynum 读取数据会传递给类成员,因为当时没有该名称的实例成员。

    【讨论】:

    • 从技术上讲,MyClass.mylist 不是一个值。像 self.mylist 一样,它是一个变量。调用 self.mylist.append() 不会改变 MyClass.mylist 的值;它会更改两个变量所引用的列表。
    • +1。不错的答案。 @tlayton:当您说“价值”时,您是什么意思?我肯定会说调用self.mylist.append("Hey!") 会改变MyClass.mylist 的值。
    • 不是根据我对上下文中“价值”含义的理解。 MyClass.mylist 的值是它所引用的列表。调用 append() 不会导致 mylist 引用不同的列表,而是更改列表本身。不过,我可能对“价值”的解释有点过于技术性了。
    【解决方案3】:

    您在这里所做的不仅仅是创建一个类变量。在 Python 中,类主体中定义的变量会产生一个类变量(“MyClass.mylist”)和一个实例变量(“a.mylist”)。这些是单独的变量,而不仅仅是单个变量的不同名称。

    但是,当以这种方式初始化变量时,初始值只计算一次并传递给每个实例的变量。这意味着,在您的代码中,MyClass 的每个实例的 mylist 变量都指向一个列表对象。

    在这种情况下,列表和数字之间的区别在于,就像在 Java 中一样,原始值(例如数字)在从一个变量传递到另一个变量时会被复制。这会导致您看到的行为;即使变量初始化只计算一次,0 也会在传递给每个实例的变量时被复制。但是,作为一个对象,列表没有这样的事情,所以您的 append() 调用都来自同一个列表。试试这个:

    class MyClass():
        def __init__(self):
            self.mylist = ["Hey"]
            self.mynum = 1
    

    这将导致在每次创建实例时单独评估该值。与 Java 非常不同的是,您不需要伴随这个 sn-p 的类主体声明; __init__() 中的赋值作为所有需要的声明。

    【讨论】:

    • "这些是单独的变量,而不仅仅是单个变量的不同名称。" id(a.mylist) 和id(MyClass.mylist) 看看你会得到什么。
    • 它们是独立的变量,是变量和值之间的重要区别。这两个对 id() 的调用将返回相等,因为 a.mylist 和 MyClass.mylist 都引用同一个对象(在评估“mylist = []”行时创建,但它们仍然是分开的;分配一个完全不同的列表以后对其中之一不会改变另一个的值。
    • @Mark:你说得对,这不是真的。类中的前两行创建类变量并且仅创建类变量。请参阅我的答案以获取正确的故事。但是,表明它不正确的方法不起作用:实例可以引用类变量,而这两个命令实际上将返回相同的 id。但是,当对“mynum”变量重复时,它们将返回不同的 id。
    • @Confusion:我明白你在说什么。手头的问题是实例变量是与实例一起创建的,还是第一次被分配。我怀疑您实际上可能是对的(从概念上讲,这更有意义),但我想不出任何测试来确定这种区别。有什么建议吗?
    • 经过进一步调查,我找到了一个合适的测试用例:创建实例后更改类变量的值会导致实例变量也发生变化。我承认混淆是正确的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-30
    • 2011-08-24
    • 2020-04-04
    • 2015-03-03
    • 1970-01-01
    • 2010-11-09
    • 1970-01-01
    相关资源
    最近更新 更多