类变量和实例变量之间的区别是什么?

2024-04-24 16:02:20 发布

您现在位置:Python中文网/ 问答频道 /正文

Python中的类变量和实例变量有什么区别?

class Complex:
    a = 1

以及

class Complex:
    def __init__(self):
        self.a = 1

在这两种情况下都使用调用:x = Complex().a将x赋给1。

关于__init__()self的更深入的答案将不胜感激。


Tags: 实例答案selfinitdef情况classcomplex
2条回答

您所称的“实例”变量实际上不是实例变量;它是类变量。请参阅language reference about classes

在您的示例中,a似乎是一个实例变量,因为它是不可变的。这是一个变量的性质,在分配可变对象时可以看到:

>>> class Complex:
>>>    a = []
>>>
>>> b = Complex()
>>> c = Complex()
>>> 
>>> # What do they look like?
>>> b.a
[]
>>> c.a
[]
>>> 
>>> # Change b...
>>> b.a.append('Hello')
>>> b.a
['Hello']
>>> # What does c look like?
>>> c.a
['Hello']

如果使用self,那么它将是一个真正的实例变量,因此每个实例都有自己的惟一a。对象的__init__函数是called when a new instance is created,而self是对该实例的引用。

编写类块时,创建类属性(或类变量)。在类块中分配的所有名称(包括用def定义的方法)都成为类属性。

创建类实例后,任何引用该实例的对象都可以在其上创建实例属性。在方法内部,“current”实例几乎总是绑定到名称self,这就是为什么您将它们看作“自变量”的原因。通常在面向对象的设计中,附加到类的代码应该控制该类实例的属性,因此几乎所有的实例属性分配都是在方法内部完成的,使用方法的self参数中接收到的实例的引用。

类属性通常与在java、C++、C++等语言中发现的静态EME>变量(或方法)进行比较。但是,如果您希望深入了解,我将避免将类属性视为“相同”的静态变量。虽然它们通常用于相同的目的,但其基本概念却大不相同。在下面的“高级”部分有更多关于这个的内容。

一个例子!

class SomeClass:
    def __init__(self):
        self.foo = 'I am an instance attribute called foo'
        self.foo_list = []

    bar = 'I am a class attribute called bar'
    bar_list = []

执行此块后,有一个类SomeClass,具有3个类属性:__init__barbar_list

然后我们将创建一个实例:

instance = SomeClass()

当这种情况发生时,将执行SomeClass__init__方法,并在其self参数中接收新实例。此方法创建两个实例属性:foofoo_list。然后这个实例被分配到instance变量中,因此它被绑定到具有这两个实例属性的对象:foofoo_list

但是:

print instance.bar

给出:

I am a class attribute called bar

这是怎么发生的?当我们试图通过dot语法检索属性时,如果该属性不存在,Python将通过一系列步骤来尝试满足您的请求。接下来,它将尝试查看实例类的类属性。在本例中,它在SomeClass中找到了一个属性bar,因此返回了该属性。

顺便说一下,这也是方法调用的工作方式。例如,当调用mylist.append(5)时,mylist没有名为append的属性。但是mylist是这样的,并且它绑定到一个方法对象。该方法对象由mylist.append位返回,然后(5)位使用参数5调用该方法。

这是有用的,因为SomeClass的所有实例都可以访问相同的bar属性。我们可以创建一百万个实例,但是我们只需要将一个字符串存储在内存中,因为它们都可以找到它。

但你得小心一点。请查看以下操作:

sc1 = SomeClass()
sc1.foo_list.append(1)
sc1.bar_list.append(2)

sc2 = SomeClass()
sc2.foo_list.append(10)
sc2.bar_list.append(20)

print sc1.foo_list
print sc1.bar_list

print sc2.foo_list
print sc2.bar_list

你觉得这是什么指纹?

[1]
[2, 20]
[10]
[2, 20]

这是因为每个实例都有自己的foo_list副本,所以它们被分别附加到。但是所有实例共享对同一bar_list的访问。所以当我们做了sc1.bar_list.append(2)它影响了sc2,即使sc2还不存在!同样地,sc2.bar_list.append(20)影响了通过sc1检索到的bar_list。这通常不是你想要的。


接下来是高级研究。:)

要真正搞糟Python,来自Java和C#等传统的静态类型OO语言,您必须学会稍微重新思考类。

在Java中,类本身并不是真正的对象。当你写一个类的时候,你更多的是声明一堆类的所有实例都有共同点的东西。在运行时,只有实例(以及静态方法/变量,但它们实际上只是与类关联的命名空间中的全局变量和函数,与OO无关)。类是在源代码中写下实例在运行时的样子的方式;它们只在so中“存在”urce代码,不在正在运行的程序中。

在Python中,类没有什么特别的。它是一个物体,就像其他任何东西一样。所以“类属性”实际上和“实例属性”完全相同;实际上只有“属性”。唯一的区别是我们倾向于使用与非类对象不同的类对象。基本的机器都是一样的。这就是为什么我说把类属性看作其他语言的静态变量是错误的。

但真正使Python类不同于Java风格类的是,与任何其他对象一样,每个类都是某个类的实例!

在Python中,大多数类都是名为type的内置类的实例。正是这个类控制了类的常见行为,并使所有的OO都像它那样。在Python中,默认的OO方式是让类的实例具有自己的属性,并且具有由类定义的公共方法/属性,这只是一种协议。如果你愿意,你可以改变它的大部分方面。如果您曾经听说过使用元类,那么所要做的就是定义一个类,该类是与type不同的类的实例。

类唯一真正“特别”的地方是类块语法(除了默认情况下让它们按自己的方式工作的所有内置机制之外),它使您更容易创建type的实例。这:

class Foo(BaseFoo):
    def __init__(self, foo):
        self.foo = foo

    z = 28

大致相当于:

def __init__(self, foo):
    self.foo = foo

classdict = {'__init__': __init__, 'z': 28 }

Foo = type('Foo', (BaseFoo,) classdict)

它将安排classdict的所有内容成为所创建对象的属性。

因此,通过Class.attribute可以像i = Class(); i.attribute一样轻松地访问类属性,这一点变得非常简单。iClass都是对象,并且对象具有属性。这也使您很容易理解如何在创建类之后修改它;只需像对任何其他对象一样分配它的属性!

事实上,实例与用来创建它们的类没有特殊的关系。Python知道要搜索实例中未找到的属性的类的方式是通过隐藏的__class__属性。您可以读取它来找出这是哪个类的实例,就像任何其他属性一样:c = some_instance.__class__。现在有一个变量c绑定到一个类,尽管它可能与该类的名称不同。您可以使用它来访问类属性,甚至调用它来创建更多的类实例(即使您不知道它是什么类!)。

你甚至可以分配给i.__class__来改变它是哪个类的实例!如果你这样做,什么都不会马上发生。这不是惊天动地。这意味着,当您查找实例中不存在的属性时,Python将查看__class__的新内容。因为这包括了大多数方法,而且方法通常希望它们操作的实例处于特定的状态,所以如果您随机执行,这通常会导致错误,而且非常混乱,但这是可以完成的。如果您非常小心,那么存储在__class__中的东西甚至不必是类对象;Python所要做的就是在某些情况下查找属性,因此,您所需要的只是一个具有正确类型属性的对象(除了Python对类或特定类的实例有挑剔的地方,还有一些警告)。

现在可能已经够了。希望(如果你读了这么多的话)我没有把你弄糊涂。当你学习Python是如何工作的时候,它是很整洁的。:)

相关问题 更多 >