类和对象

你到目前为止使用过的每一种类型(字符串、列表、字典)实际上都是一个类。当你调用 "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("王五")
bob = Player("张三", score=50)
alice.add_points(30)
alice.display() # "王五: 30 points"
bob.display() # "张三: 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("王五")
bob = Player("张三")
Player.max_lives = 5 # 对所有当前和未来的实例改变为跨所有实例共享的值使用类属性:常量、计数器、默认值。为每个对象不同的数据使用实例属性。
self.attr = value 总是制作或更新实例自己的副本。所以当一个值对每个人都相同时到达类属性,当它每个对象不同时到达实例属性。 __str__ 和 __repr__
__str__ 控制 print() 和 f 字符串为你的对象显示什么。__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("王五", 87)
print(alice) # "王五 (87 pts)" (使用 __str__)
repr(alice) # "Player(name='王五', score=87)" (使用 __repr__)总是定义 __repr__。当你想要一个干净的用户面向的表示与调试视图分开时定义 __str__。如果只定义了 __repr__,Python 对两者都使用它。
__str__ 是 print() 和 f 字符串显示的,友好版本。__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("北京"), Cat("上海"), Dog("天津")]
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.learn("sit")
print(rex.tricks) # ["sit"]总是调用 super().__init__() 当你的子类有它自己的 __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("王五,87")class Player:
@staticmethod
def is_valid_name(name):
return name.isalpha() and len(name) >= 2
Player.is_valid_name("王五") # True
Player.is_valid_name("王5") # 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()。它适配从其他属性工作出的值,感觉自然地读作普通数据。在幕后它仍然每次运行你的方法当你访问它时。 实际
一个 Player 类,有实例属性、方法、一个 @property 和 __str__:
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.earn_points(50)
alice.take_hit()
print(alice) # "王五 | Score: 50 | Lives: 2"
print(alice.is_alive) # True
