Python类作用域&;列表

Python类作用域&;列表,python,Python,我对Python还是相当陌生的,我的OO经验来自Java。因此,我用Python编写了一些代码,考虑到以下代码,这些代码对我来说非常不寻常: class MyClass(): mylist = [] mynum = 0 def __init__(self): # populate list with some value. self.mylist.append("Hey!")

我对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中,在类主体中定义的变量会产生类变量(“MyClass.mylist”)和实例变量(“a.mylist”)。这些是独立的变量,而不仅仅是单个变量的不同名称

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

在本例中,列表和数字之间的区别在于,与Java中一样,当从一个变量传递到另一个变量时,会复制诸如数字之类的基本值。这会导致您看到的行为;即使变量初始化只计算一次,0在传递给每个实例的变量时也会被复制。但是,作为一个对象,列表没有这样的功能,因此您的append()调用都来自同一个列表。请尝试以下方法:

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

这将导致每次创建实例时分别计算该值。与Java非常不同,您不需要在这个代码段中附带类主体声明;_uuuinit_uuuu()中的赋值充当所需的所有声明。

我认为区别在于
+=
是赋值(与
=
+
相同),而
append
会在适当的位置更改对象

    mylist = []
    mynum = 0
这将在类定义时分配一些类变量一次

    self.mylist.append("Hey!")
这将通过追加字符串更改值
MyClass.mylist

    self.mynum += 1

这与
self.mynum=self.mynum+1
相同,即它分配
self.mynum
(实例成员)。从
self.mynum
中读取内容会影响到类成员,因为当时没有名为该名称的实例成员。

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

添加

print MyClass.mynum
变得更加困惑:)。它将打印“0”。为什么?因为那条线

self.mynum += 1
创建实例变量并随后增加它。它不会增加类变量

mylist的故事是不同的

self.mylist.append("Hey!")
不会创建列表。它希望存在一个带有“append”函数的变量。由于实例没有这样的变量,它最终引用了类中的一个变量,该变量确实存在,因为您初始化了它。就像在Java中一样,实例可以“隐式”引用类变量。类似“类字段应该由类引用,而不是由实例引用”(或者类似的东西;我在Java中看到它已经有一段时间了)这样的警告是正确的。加行

print MyClass.mylist
要验证此答案:)


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

“这些是独立的变量,而不仅仅是单个变量的不同名称。”它们是独立的变量,对变量和值进行了重要区分。这两个对id()的调用将返回相等的值,因为a.mylist和MyClass.mylist都引用同一个对象(在计算行“mylist=[]时创建),但它们仍然是分开的;以后为其中一个指定一个完全不同的列表不会改变另一个的值。@标记:这不是真的,您是对的。类中的前两行创建类变量,并且只创建类变量。有关正确的故事,请参阅我的答案。但是,您显示它不是真的方法不起作用:实例可以引用类变量,这两个命令实际上将返回相同的id。但是,当对“mynum”变量重复时,它们将返回不同的id。@conflusion:我明白你的意思。当前的问题是实例变量是与实例一起创建的,还是第一次分配的。我怀疑你在这一点上可能是对的(这在概念上更有意义),但我想不出任何测试来确定这种区别。有什么建议吗?经过进一步调查,我发现了一个合适的测试用例:在创建实例后更改类变量的值也会导致实例变量的更改。我承认混淆是正确的。从技术上讲,MyClass.mylist不是一个值。就像self一样.mylist,它是一个变量。调用self.mylist.append()不会更改MyClass.mylist的值;它会更改两个变量所引用的列表。+1.回答不错。@tlayton:你说“value”是什么意思?我当然会说调用
self.mylist.append(“嘿!”)
更改
MyClass.mylist
的值。这与我在此上下文中对“value”含义的理解不同。MyClass.mylist的值就是它所引用的列表。调用append()不会导致mylist引用其他列表,而是更改列表本身。我可能是在解释“value”但是技术上有点太过严格。这仍然没有抓住关键点;请参见@Ken的答案。
self.mynum+=1
扩展到
self.mynum=self.mynum+1
。执行此操作时,首先计算右侧。
self.mynum
查找实例上的
mynum
,并失败