类与对象
你目前接触过的每一种类型(字符串、列表、字典)其实都是类。当你调用 "hello".upper() 时,你就是在调用一个字符串对象上的方法。类让你能定义自己的类型,拥有自己的数据和行为。一个 Player 类可以保存名字、分数和等级,还能知道如何展示自己。
蓝图与实例
类就是一份蓝图。实例是根据这份蓝图制造出来的具体东西。你可以按需要制造任意多个实例,每个实例都有自己的数据,但共享类中定义的同一套方法。
class Dog:
def bark(self):
print("Woof!")
rex = Dog()
luna = Dog()
rex.bark() # "Woof!"
luna.bark() # "Woof!"Dog 是类。rex 和 luna 是实例:两只不同的狗,各自共享类中定义的同一套行为。
Dog(),你就会得到一个全新的实例。每个实例都共享类的方法,但保留自己的数据,所以 rex 和 luna 可以表现出相同的行为,同时又是两只独立的狗。 __init__ 与 self
__init__ 是你创建新实例时 Python 自动调用的方法。你就是在这里为对象设置初始数据。self 是方法用来指代它正在操作的那个具体实例的方式,它始终是方法的第一个参数。
class Player:
def __init__(self, name, score=0):
self.name = name
self.score = score
def add_points(self, points):
self.score += points
def display(self):
print(f"{self.name}: {self.score} points")
alice = Player("Alice")
bob = Player("Bob", score=50)
alice.add_points(30)
alice.display() # "Alice: 30 points"
bob.display() # "Bob: 50 points"self.name 和 self.score 是实例属性:它们属于具体的对象,而不是类本身。每个 Player 实例都有自己的 name 和 score。
__init__ 在你创建实例的那一刻就会运行,所以这正是你用 self.name = value 设置初始数据的地方。self 就是 Python 正在处理的那个实例,它始终是方法的第一个参数,并且是自动交给你的。当你调用 alice.display() 时,你自己从不需要传 self。 方法
在类内部定义的任何函数都是方法。实例方法的第一个参数始终是 self,Python 会自动把它传进去。方法可以通过 self 读取和修改实例的数据。
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def scale(self, factor):
self.radius *= factor
return self # 返回 self 可以实现链式调用:c.scale(2).scale(0.5)
c = Circle(5)
print(c.area()) # 78.53975
c.scale(2)
print(c.area()) # 314.159self,也就是它正在操作的那个实例。Python 会替你把 self 传进去,所以你调用 c.area() 时不需要额外传任何东西。方法通过 self 读取和修改那个对象自己的数据。 类变量与实例变量
直接定义在类上(不在 __init__ 内部)的变量是类变量。所有实例共享同一个类变量。在 __init__ 内部设置在 self 上的变量是实例变量,每个对象独有。
class Player:
max_lives = 3 # 类变量,所有 Player 都一样
def __init__(self, name):
self.name = name # 实例变量,每个 Player 独有
self.lives = Player.max_lives
def die(self):
self.lives -= 1
alice = Player("Alice")
bob = Player("Bob")
Player.max_lives = 5 # 对所有当前及未来的实例都生效对所有实例共享的值使用类变量:常量、计数器、默认值。对每个对象各不相同的数据使用实例变量。
__init__ 里设在 self 上的变量属于那一个对象。写 self.attr = value 总会创建或更新实例自己的那一份。所以当一个值对所有对象都一样时用类变量,当它因对象而异时用实例变量。 __str__ 与 __repr__
__str__ 控制 print() 和 f-string 展示你对象时的内容。__repr__ 控制在控制台和调试时显示的开发者视图。始终定义 __repr__。 当你想要一个和调试视图不同、面向用户的清爽展示时再定义 __str__。
class Player:
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
return f"{self.name} ({self.score} pts)"
def __repr__(self):
return f"Player(name={self.name!r}, score={self.score})"
alice = Player("Alice", 87)
print(alice) # "Alice (87 pts)" (使用 __str__)
repr(alice) # "Player(name='Alice', score=87)" (使用 __repr__)始终定义 __repr__。当你想要一个和调试视图不同、面向用户的清爽表示时再定义 __str__。如果只定义了 __repr__,Python 两者都会使用它。
__str__ 是 print() 和 f-string 展示的内容,友好的那个版本。__repr__ 是你在控制台里看到的开发者视图。始终写 __repr__;就算你忘了写 __str__,它也照样能干活。只有当面向用户的文本需要读起来不一样时才加上 __str__。 私有约定
Python 没有真正意义上的私有变量,但名字开头的单下划线(_balance)是一种约定,表示"这是内部实现,不要从类外部直接使用它"。语言本身不会强制这一点;它只是向其他开发者传递的一个信号。
class BankAccount:
def __init__(self, balance):
self._balance = balance # _ 表示"别碰"
def deposit(self, amount):
if amount > 0:
self._balance += amount
def balance(self):
return self._balance双下划线(__name)会触发名称改写;Python 会把这个属性重命名为 _ClassName__name 以避免子类中的冲突。这种情况很少需要用到。大多数代码中用的是单下划线的约定。
_balance)是大家默认认可的信号,意思是"内部实现,从外部别碰"。没有什么能阻止你去碰它,这只是给其他开发者(包括未来的你)传递的一个信息。双下划线是更少见的工具,用来避免子类中的命名冲突;单下划线才是你日常会用到的。 继承
一个类可以从另一个类继承,自动获得它的所有属性和方法。然后你可以在子类中重写某些方法来改变它们的行为。这让你能复用一份通用的基础,同时在需要的地方做特化。
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
pets = [Dog("Rex"), Cat("Luna"), Dog("Max")]
for pet in pets:
print(pet.speak())Dog 和 Cat 从 Animal 继承了 __init__,所以它们不需要自己的版本。它们重写了 speak(),给出各自的具体行为。
Dog 和 Cat 复用了 Animal 的 __init__,只重新定义了 speak()。 super()
super() 用来调用父类的方法。当你想要扩展父类的行为而不是完全替换掉它时就用它:调用父类的 __init__ 来运行它的初始化逻辑,然后再加上你子类需要的其他内容。
class Animal:
def __init__(self, name, sound):
self.name = name
self.sound = sound
class Dog(Animal):
def __init__(self, name):
super().__init__(name, "Woof") # 调用 Animal.__init__
self.tricks = [] # 再加点额外的东西
def learn(self, trick):
self.tricks.append(trick)
rex = Dog("Rex")
rex.learn("sit")
print(rex.tricks) # ["sit"]只要你的子类有自己的 __init__ 且父类也有,就一定要调用 super().__init__()。
super() 会接到父类,所以 super().__init__() 会先运行父类的初始化逻辑,然后你再加上自己的部分。当你的子类自己写了 __init__,而父类也有一个时就用它。省略它的话父类的初始化就永远不会运行,导致对象处于半成品状态。 类方法与静态方法
@classmethod 创建的方法接收到的是类本身,而不是一个实例。它常用在替代构造方法上:从字符串、文件或其他格式创建一个实例。@staticmethod 是出于组织上的原因放在类内部的一个普通函数;它既不接收实例,也不接收类。
class Player:
def __init__(self, name, score):
self.name = name
self.score = score
@classmethod
def from_string(cls, data):
name, score = data.split(",")
return cls(name, int(score))
alice = Player.from_string("Alice,87")class Player:
@staticmethod
def is_valid_name(name):
return name.isalpha() and len(name) >= 2
Player.is_valid_name("Alice") # True
Player.is_valid_name("A1") # False替代构造方法用 @classmethod。逻辑上属于这个类、但不需要实例或类数据的工具函数用 @staticmethod。
@classmethod 把类交给你,而不是一个实例,这让它成为替代构造方法的首选:从字符串、文件,或者你手上现有的任何格式构建一个 Player。@staticmethod 是塞在类里图个整洁的普通函数;它既拿不到类也拿不到实例。普通方法会碰某个对象的数据,这两者都不会。 @property
@property 让你能像访问属性一样访问一个方法,不需要加括号。用它来处理那些由其他属性计算得出、读起来更适合当作普通属性访问的值。
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14159 * self.radius ** 2
@property
def diameter(self):
return self.radius * 2
c = Circle(5)
print(c.area) # 78.53975(看起来像属性,运行起来像方法)
print(c.diameter) # 10属性对计算值很有用:那些从其他属性推算出来、读起来自然不需要加 () 的东西。
@property 让你能像读取属性一样读取一个方法,不用加括号:c.area 而不是 c.area()。它适合那些从其他属性算出来、读起来自然像普通数据的值。在背后它每次被访问时依然会运行你的方法。 实战演练
一个带有实例属性、方法、@property 和 __str__ 的 Player 类:
class Player:
max_lives = 3
def __init__(self, name: str):
self.name = name
self.score = 0
self.lives = Player.max_lives
def earn_points(self, amount: int) -> None:
self.score += amount
def take_hit(self) -> bool:
self.lives -= 1
return self.lives > 0
@property
def is_alive(self) -> bool:
return self.lives > 0
def __str__(self) -> str:
return f"{self.name} | Score: {self.score} | Lives: {self.lives}"
alice = Player("Alice")
alice.earn_points(50)
alice.take_hit()
print(alice) # "Alice | Score: 50 | Lives: 2"
print(alice.is_alive) # True
