self 是参数名,不是关键字。__init__ 并不创建对象。类属性由所有实例共享,直到你通过某个实例给它赋值的那一刻。
__init__ 和 self 有两点常让从其他语言转过来的人意外。看清 Python 实际在做什么之后,两点都好理解。
self 是参数,不是关键字
调用 d.speak() 时,Python 实际调用的是 Dog.speak(d)。实例作为第一个参数传进去,self 只是我们给这个参数起的名字。
# self is a parameter name, not a keyword
class Dog:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} says woof"
d = Dog('Rex')
print(d.speak())
print(Dog.speak(d)) # the same call, written out
输出:
Rex says woof
Rex says woof
两行是同一个调用。d.speak() 只是简写。
可以证明 self 只是约定:
# proof that self is only a convention
class Cat:
def __init__(whatever, name):
whatever.name = name
def speak(this):
return f"{this.name} says meow"
print(Cat('Mia').speak())
输出:
Mia says meow
能跑。但千万别这么写——每个 Python 读者都默认看到 self,打破这个约定的代价远大于任何好处。不过知道它是参数,就能解释为什么每个方法签名里都得写上它,否则这看起来就像多余的噪音。
__init__ 并不创建对象
名字让人以为它是构造函数,其实不是。__init__ 运行时,对象已经存在了——__init__ 只负责往里填东西。
# __init__ does not create the object, it fills one in
class Tracked:
def __new__(cls, *args):
print(' __new__ ran, object does not exist yet')
return super().__new__(cls)
def __init__(self, value):
print(' __init__ ran, self already exists:', type(self).__name__)
self.value = value
t = Tracked(5)
print('value =', t.value)
输出:
__new__ ran, object does not exist yet
__init__ ran, self already exists: Tracked
value = 5
真正的构造函数是 __new__。你几乎不会去写它——只有不可变类型和元类才用得上,别处都用不到。但它解释了这个名字:__init__ 是初始化,不是创建。
类属性是共享的
在类体里赋值的属性属于类本身。不管创建多少个实例,它都只有一份。
# instance attributes vs class attributes
class Counter:
total = 0 # shared by every instance
def __init__(self):
self.count = 0 # one per instance
def tick(self):
self.count += 1
Counter.total += 1
a, b = Counter(), Counter()
a.tick(); a.tick(); b.tick()
print('a.count =', a.count, '| b.count =', b.count, '| Counter.total =', Counter.total)
输出:
a.count = 2 | b.count = 1 | Counter.total = 3
count 每个实例各有一份。total 是整个类共用的一个数。
容易踩坑的地方
通过实例读取类属性没问题。但通过实例赋值不会更新类属性——它会新建一个实例属性,把类属性遮住。
# assigning through the instance shadows the class attribute
a.total = 99
print('a.total =', a.total, '| b.total =', b.total, '| Counter.total =', Counter.total)
print('a has its own:', 'total' in a.__dict__, '| b does not:', 'total' in b.__dict__)
输出:
a.total = 99 | b.total = 3 | Counter.total = 3
a has its own: True | b does not: False
a 现在有了自己的 total,b 和类仍然共用原来那个。这就是为什么 tick 里写的是针对类的 Counter.total += 1,而不是 self.total += 1——后者会读出类上的值,加一,然后悄悄把结果存到实例上。
同样的规则也说明了为什么可变的类属性是个陷阱。类体里的列表由所有实例共享,而 self.items.append(x) 修改的是这个共享列表,并不会遮住它。可变属性要在 __init__ 里赋值。
要点
d.speak()就是Dog.speak(d)。self是第一个参数,名字只是约定。__init__初始化一个已经存在的对象。创建对象的是__new__,你很少需要写它。- 类属性是共享的。通过实例读取能找到它;通过实例赋值会遮住它。
- 可变的类属性既共享又可变——把它们放进
__init__。