【问题标题】:class initialization in PythonPython中的类初始化
【发布时间】:2015-02-19 09:29:14
【问题描述】:

我发现有些类包含 __init__ 函数,有些则没有。我对下面描述的事情感到困惑。

这两段代码有什么区别:

class Test1(object):
    i = 1

class Test2(object):
    def __init__(self):
        self.i = 1

我知道这两个类创建的结果或任何实例以及获取它们的实例变量的方式几乎相同。但是当我们没有为一个类定义 __init__ 函数时,是否有任何一种 Python 的“默认”或“隐藏”初始化机制?以及为什么我不能这样写第一个代码:

class Test1(object):
    self.i = 1

这是我的问题。非常感谢!


非常感谢安蒂·哈帕拉!你的回答让我对我的问题有了进一步的理解。现在,我明白它们的不同之处在于一个是“类变量”,另一个是“实例变量”。但是,当我进一步尝试时,我又遇到了另一个令人困惑的问题。

就是这样。我创建了 2 个新类来理解您所说的:

class Test3(object):
    class_variable = [1]
    def __init__(self):
        self.instance_variable = [2]

class Test4(object):
    class_variable = 1
    def __init__(self):
        self.instance_variable = 2

正如您在回答我的第一个问题时所说,我了解 class_variable 是类通用的“类变量”,应该通过引用内存中的相同位置。 instance_variable 会针对不同的实例单独创建。

但正如我所尝试的,您所说的对于 Test3 的实例是正确的,它们都共享相同的内存。如果我在一个实例中更改它,无论我调用它,它的值都会改变。

但对于 Test4 的实例,情况并非如此。 Test4类中的int不也应该通过引用来改变吗?

i1 = Test3()

i2 = Test3()
>>> i1.i.append(2)
>>> i2.i
[1, 2]

j1 = Test4()
j2 = Test4()
>>> j1.i = 3
>>> j2.i
1

这是为什么呢?默认情况下,“=”会创建一个名为“i”的“instance variable”而不更改原始“Test4.i”吗?然而“append”方法只处理“class variable”?

再次感谢您对 Python 新手最无聊的基本概念的详尽解释。我真的很感激!

【问题讨论】:

  • 在为每两个类创建两个实例之间尝试Test1.i += 100Test2.i += 100
  • 那行不通,@PadraicCunningham;后者会说AttributeError
  • @AnttiHaapala,这就是我的意思,你需要创建一个实例,即Test2().i += 100
  • 我的意思是它作为演示也不是很好。

标签: python class initialization


【解决方案1】:

在 python 中,实例属性(例如self.i)存储在实例字典(i.__dict__)中。类体中的所有变量声明都存储为类的属性。

这样

class Test(object):
    i = 1

等价于

class Test(object):
    pass
Test.i = 1

如果没有定义__init__ 方法,则新创建的实例通常以空实例字典开头,这意味着没有定义任何属性。

现在,当 Python 执行 get attribute 时(如在 print(instance.i) 操作中,它首先查找在 instance 上设置的名为 i 的属性) .如果失败,则在type(i) 上查找i 属性(即类属性i)。

所以你可以这样做:

class Test:
    i = 1

t = Test()
print(t.i)  # prints 1
t.i += 1
print(t.i)  # prints 2

但这实际上做的是:

>>> class Test(object):
...     i = 1
... 
>>> t = Test()
>>> t.__dict__
{}
>>> t.i += 1
>>> t.__dict__
{'i': 2}

新创建的t 上根本没有i 属性!因此,在t.i += 1 中,.iTest 类中被查找以供读取,但新值被设置到t 中。

如果你使用__init__

>>> class Test2(object):
...     def __init__(self):
...         self.i = 1
... 
>>> t2 = Test2()
>>> t2.__dict__
{'i': 1}

新创建的实例t2 已经设置了属性。


现在对于像int 这样的不可变值,没有太大区别。但是假设你使用了一个列表:

class ClassHavingAList():
    the_list = []

对比

class InstanceHavingAList()
    def __init__(self):
        self.the_list = []

现在,如果您同时创建 2 个实例:

>>> c1 = ClassHavingAList()
>>> c2 = ClassHavingAList()
>>> i1 = InstanceHavingAList()
>>> i2 = InstanceHavingAList()
>>> c1.the_list is c2.the_list
True
>>> i1.the_list is i2.the_list
False
>>> c1.the_list.append(42)
>>> c2.the_list
[42]

c1.the_listc2.the_list 指的是内存中完全相同的列表对象,而 i1.the_listi2.the_list 是不同的。修改c1.the_list 看起来好像c2.the_list 也发生了变化。

这是因为attribute 本身没有设置,它只是被读取。 c1.the_list.append(42) 在行为上与

相同
getattr(c1, 'the_list').append(42)

也就是说,它只尝试读取c1上的属性the_list的值,如果没有找到,则在超类中查找。 append 不会改变属性,它只是改变属性指向的值。

现在,如果您要编写一个表面看起来相同的示例:

c1.the_list += [ 42 ]

它的工作原理与

相同
original = getattr(c1, 'the_list')
new_value = original + [ 42 ]
setattr(c1, 'the_list', new_value)

然后做一件完全不同的事情:首先original + [ 42 ] 会创建一个新的列表对象。然后属性the_list 将在c1 中创建,并设置为指向这个新列表。即在instance.attribute的情况下,如果attribute是“读取自”,则可以在类(或超类)中查找if实例中没有设置,但如果它写入,如instance.attribute = something,它将始终在实例上设置。


至于这个:

class Test1(object):
    self.i = 1

这样的事情在 Python 中不起作用,因为在执行类主体(即类中的所有代码行)时没有定义 self - 实际上,类仅在 之后创建em> 类主体中的所有代码都已执行。类体就像任何其他代码一样,只有defs 和变量赋值会在类上创建方法和属性,而不是设置全局变量。

【讨论】:

  • 非常感谢 Antti Haapala 先生。作为演示,这真是太棒了。但是当我尝试进行一点测试时,我遇到了另一个令人困惑的问题。你能帮我解决这个问题吗?我将那部分添加到我原来的问题中。 @Antti Haapala
  • 我现在得到了我新添加问题的原因!谢谢!
【解决方案2】:

我理解了我新添加的问题。感谢安蒂·哈帕拉。

现在,当 Python 执行 get 属性时(如在 print(instance.i) 操作中,它首先查找在实例上设置的名为 i 的属性)。如果失败,则在 type(i) 上查找 i 属性(即类属性 i)。

我很清楚为什么会这样:

j1 = Test4()
j2 = Test4()
>>> j1.i = 3
>>> j2.i
1

经过几次测试。代码

j1.3 = 3

实际上为 j1 创建了一个新的实例变量,而不更改类变量。这就是“=”和“append”之类的方法之间的区别。

我是来自 c++ 的 Python 新手。所以,乍一看,这对我来说很奇怪,因为我从来没有想过创建一个新的实例变量,它不是在类中创建的,只是使用“=”。这确实是c++和Python之间的一个很大的区别。

现在我明白了,谢谢大家。

【讨论】:

  • 嗯,如果你有C++背景,你应该这样想:所有 Python中的变量、属性等就像C++指针。它们本身从不包含实际的对象。
  • 偶数,在Python中如果你写x = 3,在CPython中实现代码是PyObject *x = PyLong_FromLong(3);。无论这些命名指针指向什么,赋值语句都会改变。
  • 如果我错了,请纠正我。它非常接近 Swift 中的概念——“类是引用类型”,对吧?非常感谢!
  • 我对 Swift 一点也不熟悉,但是在 Objective-C 中是的,所有类都是通过引用 (NSObject *),在 Python 中是相同的,除了 everything 是总是参考。
  • 很高兴知道一切都是参考。再次,我真的很感谢你的帮助!这对我来说真的是一次美妙的经历,因为这是我在 stackoverflow 社区的第一个问题,并且得到的回答比我预期的要详尽得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-01
  • 2012-01-20
相关资源
最近更新 更多