Python __init__与类属性的区别

10

我刚开始学习编程,对类的概念还不是很理解。我花了好几个小时去研究,但仍然感到困惑。我有一个具体的问题:

我不明白何时使用类属性,何时使用初始化方法(__init__)。

我知道当使用__init__时,我不会立即分配任何值,只需要在创建使用该类的对象时分配值。而类属性则自动继承到在该类下创建的对象中。

但在实际应用中,它们是否完成了相同的任务?它们只是做相同事情的两种不同方式吗?或者__init__可以做一些类属性无法做到的事情吗?

我进行了一些测试,结果是相同的。我不确定何时使用哪个。对我来说,类属性看起来更方便使用。

#use class attributes for class Numbers_1
class Numbers_1:

  one = 1
  two = 2
  three = 3
  six = two * three

def multiply(self):
   return self.six * self.two * self.three

 #use initializer for class Numbers_2    
 class Numbers_2:

 def __init__(self, num10, num20, num30, num600):
   self.num10 = num10
   self.num20 = num20
   self.num30 = num30
   self.num600 = num600

 def multiply(self):
   return self.num600 * self.num20 * self.num30

 #Now I run some test to compare the two classes...
 x = Numbers_1()
 y = Numbers_2(10, 20, 30, 20*30)

 print(x.one)     #print 1
 print(y.num10)   #print 10

 print(x.six)     #print 6
 print(y.num600)  #print 600

 #assign attributes to each objects
 x.eighteen = x.six * x.three 
 y.num18000 = y.num600 * y.num30

 print(x.eighteen)   #print 18
 print(y.num18000)   #print 18000

 #try printing methods in each object
 print(x.multiply()) #print 36
 print(y.multiply()) #print 360000

 #try reassign values to attributes in each object
 x.one = 100
 y.num10 = 1000

 print(x.one)     #prints 100
 print(y.num10)   #prints 1000

需要注意的一件事是,在您的第一个类的实例上,self.one = 2 会将 'one' 重新绑定到该实例的命名空间,因此这个共享变量的说法 似乎 是不正确的。尝试使用可变变量进行操作以更好地了解情况,将其声明为类级别并在实例中覆盖是具有不可变变量默认值的好方法。 - JL Peyret
我想说的是在类级别列表变量中添加。 - JL Peyret
5个回答

8
你的所有翻译都是正确的,除了在Python中,class属性也像静态变量一样起作用。
但请注意,在Python解释器解析时,类范围内的所有内容都会立即运行。
# file1.py
def foo():
    print("hello world")

class Person:
     first_name = foo()
     last_name  = None

     def __init__(self):
         last_name = "augustus"
         print("good night")

# file2.py
import file1
>>> "hello world"
x = Person()
>>> "good night"

6
为了理解差异,您需要考虑类和这些类的实例之间的区别。
类属性适用于该类的每个对象。修改它们会修改该类的所有实例(除了在更改Class本身之前显式修改此属性的实例)。修改实例属性仅修改正在操作的特定对象。
例如:
class Foo:
    class_var = 'bar'
    def __init__(self):
         self.instance_var = 'baz'
foo1 = Foo()
foo2 = Foo()

print(foo1.class_var, foo2.class_var)
print(foo1.instance_var, foo2.instance_var)

Foo.class_var = 'quux'
Foo.instance_var = "this doesn't work"
foo1.instance_var = 'this does'


print(foo1.class_var, foo2.class_var)
print(foo1.instance_var, foo2.instance_var)

打印
bar bar
baz baz
quux quux
this does baz

如果我们这样做:

foo1.class_var = 'spam'
Foo.class_var = 'eggs'

print(foo1.class_var, foo2.class_var)

它打印出来了

spam eggs

由于在类之前对其进行了修改,因此foo1保持原样。

因此,修改Foo.class_var会替换Foo的所有现有实例中的class_var(除了先前已经修改的实例),而修改Foo.instance_var则不会产生作用。然而,在类型为Foo对象上修改instance_var确实可以工作,但仅适用于该特定实例 - 其他实例保持不变。


3

如果您创建多个对象,您可以看到它们之间的区别。

class Numbers_1:

  one = 1
  two = 2
  six = one * two

  def __init__(self, o, t):
     self.o = o
     self.t = t

  def mul(self):
     return self.o * self.t

o1 = Numbers_1(1, 2)
o2 = Numbers_1(10, 20)
o3 = Numbers_1(20, 30)

print(o1.six) # 2
print(o2.six) # 2
print(o3.six) # 2

print(o1.mul())  # 2
print(o2.mul())  # 200
print(o3.mul())  # 600

像one、to、six这样的变量被称为类变量。

类变量由使用同一类创建的对象共享。


1
关于类属性,有一些微妙的问题需要我们注意。
根据 https://docs.python.org/3/tutorial/classes.html

9.4. 随机备注

如果同一个属性名称在实例和类中都出现了,那么属性查找会优先选择实例。

此外,我观察到,改变类属性的值,这个值的改变会传播回那些没有设置(覆盖)其属性值的实例。这符合上面的文档说明。
让我们来看一些例子。首先,考虑这个类:
class Engine:
    started = False;

    def start(self):
        self.started = True;

让我们实例化它并看看`started`属性的行为:
engine1 = Engine()

print( f"1. engine1.started: {engine1.started}" );
print( f"1. Engine.started: {Engine.started}" );

“如预期一样,两个都是 False:"
1. engine1.started: False
1. Engine.started: False

继续:
engine1.start()

print( f"2. engine1.started: {engine1.started}" );
print( f"2. Engine.started: {Engine.started}" );

输出为:
2. engine1.started: True
2. Engine.started: False

实际上,我看到:
然后属性查找优先考虑实例。
考虑以下实例化:
Engine.started = True
engine2 = Engine()

print( f"3. engine2.started: {engine2.started}" );
print( f"3. Engine.started: {Engine.started}" );

两者都是“True”,这对我们来说应该是有意义的:
3. engine2.started: True
3. Engine.started: True

个人而言,这引出了一个问题:当类属性的值被改变时,实例属性的值会发生什么变化?也就是说:

# Note: Engine.started is True from (3, engine2) above.

engine3 = Engine()

# Expected True.
print( f"4. engine3.started: {engine3.started}" );

Engine.started = False

# Expected False.
print( f"5. engine3.started: {engine3.started}" );

输出:

4. engine3.started: True
5. engine3.started: False

这是我上面提到的观察结果:
更改类属性的值,该值的更改将传播回那些未设置属性值(被覆盖)的实例。
其次,这是引用上述文档页面的示例,我稍微调整了一下。
class Warehouse:
    purpose = 'Storage'
    region = 'west'

w1 = Warehouse()

print( "1: ", w1.purpose, w1.region )

输出:

1:  Storage west

然后:

w2 = Warehouse()
w2.region = 'east'

print( "2: ", w2.purpose, w2.region )

2:  Storage east

Warehouse.region = 'north'
w3 = Warehouse()

print( "3: ", w3.purpose, w3.region )

3:  Storage north

这句话的意思是“关于w1和w2呢?”
print( f"4: w1.region: {w1.region}, w2.region: {w2.region}")

输出,我们应该能够弄清原因:
4: w1.region: north, w2.region: east

最终,通过父类更改一个类属性值会将该值的更改向下传播到子类,但反过来不行。
考虑以下例子:
class Engine:
    started = False;

    def start(self):
        self.started = True;

class TwoStrokeEngine(Engine):
    pass
    
class FourStrokeEngine(Engine):
    pass
    
# Expected: all False.
print( f"1. Engine.started: {Engine.started}" );
print( f"1. TwoStrokeEngine.started: {TwoStrokeEngine.started}" );
print( f"1. FourStrokeEngine.started: {FourStrokeEngine.started}\n" );

Engine.started = True

# Expected: all True.
print( f"2. Engine.started: {Engine.started}" );
print( f"2. TwoStrokeEngine.started: {TwoStrokeEngine.started}" );
print( f"2. FourStrokeEngine.started: {FourStrokeEngine.started}\n" );

Engine.started = False

# Expected: all False.
print( f"3. Engine.started: {Engine.started}" );
print( f"3. TwoStrokeEngine.started: {TwoStrokeEngine.started}" );
print( f"3. FourStrokeEngine.started: {FourStrokeEngine.started}\n" );

FourStrokeEngine.started = True

# Expected: False, False, True
print( f"4. Engine.started: {Engine.started}" );
print( f"4. TwoStrokeEngine.started: {TwoStrokeEngine.started}" );
print( f"4. FourStrokeEngine.started: {FourStrokeEngine.started}" );

输出:

1. Engine.started: False
1. TwoStrokeEngine.started: False
1. FourStrokeEngine.started: False

2. Engine.started: True
2. TwoStrokeEngine.started: True
2. FourStrokeEngine.started: True

3. Engine.started: False
3. TwoStrokeEngine.started: False
3. FourStrokeEngine.started: False

4. Engine.started: False
4. TwoStrokeEngine.started: False
4. FourStrokeEngine.started: True

1

类属性不是特定于对象的。例如:

x = Numbers_1()
y = Numbers_1()

在上面的代码中,x和y将具有相同的类属性。
相反,init函数定义了对象属性。例如:
s = Numbers_2(10, 20, 30, 20*30)
t = Numbers_2(11, 21, 31, 21*31)

s和t现在具有不同的对象属性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接