在Python中,在__init__中定义成员与在类体中定义成员的区别是什么?

7 投票
3 回答
921 浏览
提问于 2025-04-15 14:59

这两段代码有什么区别呢?

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

class a:
   val=1
   def __init__(self):
       pass

3 个回答

5

正如其他人提到的,有时候属性是在类上,有时候是在实例上。这有什么区别吗?有的,特别是当值是可变的时候。最好的解释就是代码,所以我会加一些代码来展示(这其实就是这个回答的全部内容):

首先定义一个类,里面有两个实例属性。

>>> class A(object):
...     def __init__(self):
...         self.number = 45
...         self.letters = ['a', 'b', 'c']
... 

然后定义一个类,里面有两个类属性。

>>> class B(object):
...     number = 45
...     letters = ['a', 'b', 'c']
... 

现在我们来使用它们:

>>> a1 = A()
>>> a2 = A()
>>> a2.number = 15
>>> a2.letters.append('z')

一切都很好:

>>> a1.number
45
>>> a1.letters
['a', 'b', 'c']

现在使用类属性的变体:

>>> b1 = B()
>>> b2 = B()
>>> b2.number = 15
>>> b2.letters.append('z')

结果是...嗯...

>>> b1.number
45
>>> b1.letters
['a', 'b', 'c', 'z']

是的,注意到当你改变了可变的类属性时,它对所有类都发生了变化。这通常不是你想要的结果。

如果你在使用ZODB,你会使用很多类属性,因为这是一个方便的方法,可以用新属性来升级现有对象,或者在类级别添加一些信息,而这些信息不会被保存。否则,你基本上可以忽略它们。

6

其他人已经解释了技术上的区别。我来试着说明一下为什么你可能想用类变量。

如果你只创建一次这个类的实例,那么类变量实际上就和实例变量一样了。不过,如果你要创建很多个实例,或者想让几个实例之间共享一些状态,类变量就非常有用了。举个例子:

class Foo(object):
    def __init__(self):
        self.bar = expensivefunction()

myobjs = [Foo() for _ in range(1000000)]

这段代码会让expensivefunction()被调用一百万次。如果每次返回的值都是一样的,比如从数据库中获取一个配置参数,那么你应该考虑把它放到类定义里,这样就只会调用一次,然后在所有实例之间共享这个值。

我在缓存结果的时候也经常使用类变量。比如:

class Foo(object):
    bazcache = {}

    @classmethod
    def baz(cls, key):
        try:
            result = cls.bazcache[key]
        except KeyError:
            result = expensivefunction(key)
            cls.bazcache[key] = result
        return result

在这个例子中,baz是一个类方法;它的结果不依赖于任何实例变量。这意味着我们可以在类变量中保存一份结果缓存,这样1) 你就不会重复存储相同的结果,2) 每个实例都可以利用其他实例缓存的结果。

举个例子,假设你有一百万个实例,每个实例都在处理一次谷歌搜索的结果。你肯定更希望这些对象共享搜索结果,而不是每个实例都去执行搜索并等待答案。

所以我不同意Lennart的看法。在某些情况下,类变量是非常方便的。当它们是解决问题的合适工具时,别犹豫,尽管使用它们。

10
class a:
   def __init__(self):
       self.val=1

这段代码创建了一个类(在Python 2中,这是个很糟糕的老式类,不要这样做!;在Python 3中,这种老旧的类终于消失了,所以这就是唯一的好类,创建时需要用 class a(object): 这样的方式)。每个实例一开始都有自己的整数对象 1 的引用。

class a:
   val=1
   def __init__(self):
       pass

这段代码创建了一个同样类型的类,它自己有一个整数对象 1 的引用(它的实例一开始没有每个实例自己的引用)。

对于像 int 这样的不可变对象,实际差别不太明显。例如,无论哪种情况,如果你后来在某个 a 的实例上执行 self.val = 2,这将创建一个实例引用(之前的回答在这方面是错误的)。

这个区别对可变对象很重要,因为可变对象有修改方法,所以知道某个列表是每个实例独有的还是所有实例共享的非常关键。但对于不可变对象,由于你永远不能改变对象本身,只能重新赋值(比如给 self.val 赋值,这总是会创建每个实例的引用),所以这个区别就不那么重要了。

对于不可变对象,唯一相关的区别是:如果你后来将 a.val = 3,在第一种情况下,这会影响每个实例看到的 self.val(除了那些已经给自己的 self.val 赋值的实例);而在第二种情况下,这不会影响任何实例看到的 self.val(除了那些你执行过 del self.val 或类似操作的实例)。

撰写回答