在线咨询 400-826-1668
回到顶部
ARTICLE DETAIL

资讯详情

深耕国风建站与运营引流的一线实战洞察。

Python面向对象编程:从类与对象基础到封装继承多态实战

Python面向对象编程:从类与对象基础到封装继承多态实战 1. 从“蓝图”到“实体”理解Python类与对象的本质如果你刚开始学Python可能觉得“类”和“对象”这两个词有点抽象像是编程里的黑话。但说穿了它们就是用来组织代码、模拟现实世界的一种超级工具。你可以把类Class想象成一张设计蓝图而对象Object就是根据这张蓝图建造出来的、实实在在的房子。蓝图类定义了房子要有几间卧室、几个卫生间、门窗怎么开。但蓝图本身不能住人它只是一套规则。只有根据蓝图建好的那栋具体的房子对象你才能进去装修、居住。在Python里我们通过定义类来创建一种新的数据类型然后用这个类去“实例化”也就是建造出一个个独立的对象。每个对象都拥有类里定义的属性和功能但各自的数据可以完全不同。比如我们定义一个“学生”类那么“张三”和“李四”就是两个不同的学生对象他们都有姓名、学号这些属性但具体的值各不相同。为什么这如此重要因为一旦你掌握了类和对象你的代码就能从一堆零散的函数和变量升级为结构清晰、易于管理和扩展的“乐高积木”。无论是开发一个Web应用、进行数据分析还是写一个小工具面向对象编程OOP的思想都能让你事半功倍。接下来我们就抛开那些枯燥的定义直接动手看看怎么把这张“蓝图”画出来并用它盖起“高楼大厦”。2. 类的定义与对象的创建从零开始搭建万事开头难但定义第一个类其实非常简单。在Python中我们使用class关键字来声明一个类。2.1 定义一个最简单的类我们从一个最基础的例子开始创建一个表示“狗”的类。class Dog: pass是的你没看错两行代码就定义了一个类。pass是一个占位符表示“这里暂时什么都不做”。现在Dog就是一个空壳类但它已经具备了被实例化的能力。2.2 创建你的第一个对象有了蓝图就可以盖房子了。创建对象的过程叫做“实例化”语法是使用类名后跟一对圆括号看起来就像调用一个函数。my_dog Dog() # 创建了一个Dog类的对象并赋值给变量my_dog your_dog Dog() # 创建了另一个独立的Dog对象这里my_dog和your_dog就是两个不同的对象。虽然它们现在内部都是空的因为类定义里只有pass但它们是两个独立的存在。你可以用type()函数来验证它们的类型print(type(my_dog)) # 输出class __main__.Dog print(my_dog is your_dog) # 输出False这是两个不同的对象2.3 初始化对象__init__方法一个空的狗对象没什么用。我们通常希望在创建对象的同时就给它赋予一些初始特征比如名字和年龄。这就需要用到类中一个非常特殊的方法__init__()。你可以把它理解为对象的“构造函数”或“初始化方法”。class Dog: def __init__(self, name, age): self.name name # 为这个对象创建一个属性叫name赋值为传入的name参数 self.age age # 为这个对象创建一个属性叫age赋值为传入的age参数 print(f一只名叫{name}的小狗诞生了)这里有几个关键点self参数这是Python类方法的第一个参数而且必须要有。它代表对象实例本身。当我们调用my_dog Dog(“Buddy”, 2)时self指的就是my_dog这个即将被创建出来的对象。属性赋值self.name name这行代码的作用是给当前这个对象self绑定一个名为name的属性并把外部传入的name参数值赋给它。这样每个狗对象就拥有了自己独立的name和age。现在我们可以创建有血有肉的狗对象了dog1 Dog(Buddy, 2) # 输出一只名叫Buddy的小狗诞生了 dog2 Dog(Lucy, 5) # 输出一只名叫Lucy的小狗诞生了 print(dog1.name, dog1.age) # 输出Buddy 2 print(dog2.name, dog2.age) # 输出Lucy 5注意__init__方法并不是必须的。如果你不需要在创建对象时进行任何初始化操作可以省略它Python会调用一个默认的。但实践中几乎所有的类都会定义__init__来设置初始状态。2.4 实例属性与类属性分清“个人的”和“公共的”上面例子中的name和age我们是通过self.属性名来定义的这叫做实例属性。它属于单个对象不同对象的实例属性值互不影响。与之相对的是类属性它属于类本身所有由这个类创建的对象都共享同一个类属性。类属性直接在类内部、方法外部定义。class Dog: species Canis familiaris # 类属性所有狗都属于这个物种 def __init__(self, name, age): self.name name # 实例属性 self.age age # 实例属性 dog1 Dog(Buddy, 2) dog2 Dog(Lucy, 5) print(dog1.species) # 输出Canis familiaris print(dog2.species) # 输出Canis familiaris print(Dog.species) # 输出Canis familiaris通过类名也能访问 # 修改类属性会影响所有实例和类本身 Dog.species Canis lupus familiaris print(dog1.species) # 输出Canis lupus familiaris print(dog2.species) # 输出Canis lupus familiaris实操心得类属性通常用于定义该类所有对象共有的常量或默认值。一个常见的坑是如果类属性是可变对象如列表、字典通过一个实例修改它会影响所有其他实例。例如Dog.toys []如果dog1.toys.append(“ball”)那么dog2.toys也会看到这个球。为了避免这种混淆除非确有必要否则建议将可变状态定义为实例属性。3. 让对象“动”起来实例方法详解属性让对象有了状态是什么而方法让对象有了行为能做什么。在类中定义的函数我们称之为方法。最常见的就是实例方法它的第一个参数永远是self。3.1 定义与调用实例方法让我们给Dog类增加一些行为class Dog: def __init__(self, name, age): self.name name self.age age def bark(self): 狗叫的方法 print(f{self.name} says: Woof! Woof!) def have_birthday(self): 过生日年龄加一 self.age 1 print(fHappy Birthday, {self.name}! Now you are {self.age} years old.) # 创建对象并调用方法 my_dog Dog(Buddy, 2) my_dog.bark() # 输出Buddy says: Woof! Woof! my_dog.have_birthday() # 输出Happy Birthday, Buddy! Now you are 3 years old. print(my_dog.age) # 输出3调用实例方法时Python会自动将调用该方法的对象my_dog作为第一个参数self传入。所以my_dog.bark()等价于Dog.bark(my_dog)。这种语法糖让代码读起来更自然“我的狗叫一下”。3.2 特殊方法魔术方法赋予对象“超能力”__init__就是一种特殊方法它以双下划线开头和结尾。Python提供了很多这样的魔术方法用于实现或改变对象的某些内置行为。理解它们能极大提升你对Python对象的掌控力。__str__和__repr__对象的字符串表示__str__当你使用print(obj)或str(obj)时调用返回一个对用户友好的、可读的字符串描述。__repr__当你直接在交互式环境输入对象名或使用repr(obj)时调用目标是返回一个明确的、无歧义的字符串理想情况下能用来重新创建这个对象。class Dog: def __init__(self, name, age): self.name name self.age age def __str__(self): return fA dog named {self.name}, {self.age} years old. def __repr__(self): return fDog({self.name}, {self.age}) my_dog Dog(Buddy, 3) print(my_dog) # 输出A dog named Buddy, 3 years old. (调用 __str__) print(repr(my_dog)) # 输出Dog(Buddy, 3) (调用 __repr__) # 在交互环境中直接输入 my_dog会显示Dog(Buddy, 3)__len__定义对象的长度如果你的对象有“长度”的概念比如一个自定义的集合可以定义__len__方法。class Pack: def __init__(self): self.dogs [] def add_dog(self, dog): self.dogs.append(dog) def __len__(self): return len(self.dogs) pack Pack() pack.add_dog(Dog(Buddy, 2)) pack.add_dog(Dog(Lucy, 5)) print(len(pack)) # 输出2这里调用了 pack.__len__()__add__实现对象的加法让你自定义的对象支持运算符。class Dog: def __init__(self, name, age, friendliness0): self.name name self.age age self.friendliness friendliness # 友好度0-10 def __add__(self, other_dog): 两只狗在一起产生一个‘玩耍能量’值 return self.friendliness other_dog.friendliness dog1 Dog(Buddy, 2, 8) dog2 Dog(Lucy, 5, 6) play_energy dog1 dog2 # 等价于 dog1.__add__(dog2) print(fPlay energy between {dog1.name} and {dog2.name}: {play_energy}) # 输出14注意事项魔术方法非常强大但不要滥用。只在确实需要改变对象的内置行为时才使用它们。例如为一个“银行账户”类定义__add__来合并余额可能是合理的但为一个“用户”类定义__add__可能就令人困惑了。4. 面向对象三大支柱封装、继承与多态这是面向对象编程的核心思想理解了它们你才算真正入门。4.1 封装隐藏内部细节提供公共接口封装的核心思想是将数据属性和操作数据的方法捆绑在一起并对外部隐藏对象内部状态的实现细节。在Python中我们主要通过“命名约定”来实现一种非强制的封装。公开属性像self.name这样可以直接从外部访问和修改。受保护属性以一个下划线开头如self._internal_data。这是一种约定告诉其他程序员“这是一个内部属性请不要在类外部直接访问它”但Python并不会阻止你访问。私有属性以两个下划线开头如self.__secret。Python会对其进行“名称改写”使得在类外部难以直接访问主要用于避免子类意外重写父类属性。class BankAccount: def __init__(self, owner, balance): self.owner owner # 公开属性 self._account_number “123456” # 受保护属性约定 self.__balance balance # 私有属性 def deposit(self, amount): if amount 0: self.__balance amount print(fDeposited {amount}. New balance: {self.__balance}) else: print(“Invalid deposit amount.”) def get_balance(self): # 提供公共方法来访问私有属性 return self.__balance account BankAccount(“Alice”, 1000) print(account.owner) # 可以访问Alice print(account._account_number) # 可以访问但不建议123456 # print(account.__balance) # 直接访问会报错AttributeError print(account.get_balance()) # 正确方式通过公共接口输出1000 account.deposit(500) # 通过方法修改状态输出Deposited 500. New balance: 1500封装的好处是提高了代码的安全性和可维护性。你可以自由修改内部实现比如将__balance的存储方式从变量改为数据库查询只要不改变公共方法如deposit,get_balance的对外行为调用这些方法的代码就完全不需要修改。4.2 继承代码复用与层次化设计继承允许我们定义一个类子类来继承另一个类父类或超类的属性和方法。子类可以扩展或修改父类的行为。这实现了代码的复用和逻辑的层次化。# 父类基类 class Animal: def __init__(self, name): self.name name def speak(self): raise NotImplementedError(“Subclass must implement abstract method”) # 抽象方法 # 子类派生类 class Dog(Animal): # Dog类继承自Animal类 def __init__(self, name, breed): super().__init__(name) # 调用父类的__init__方法初始化name属性 self.breed breed # 子类特有的属性 def speak(self): # 重写父类的抽象方法 return f“{self.name} says: Woof!” class Cat(Animal): def __init__(self, name, is_indoor): super().__init__(name) self.is_indoor is_indoor def speak(self): # 重写父类的抽象方法 return f“{self.name} says: Meow!” # 使用 animals [Dog(“Buddy”, “Golden Retriever”), Cat(“Whiskers”, True)] for animal in animals: print(animal.speak()) # 输出 # Buddy says: Woof! # Whiskers says: Meow!关键点解析super()这是一个内置函数用于调用父类超类的方法。在子类的__init__中调用super().__init__(name)是非常标准的做法确保父类的初始化逻辑得以执行。方法重写子类可以定义与父类同名的方法这样就会覆盖父类的方法。这是实现多态的基础。抽象基类像上面Animal中的speak方法它只是抛出一个错误并不实现具体功能。这种设计的目的是定义一个接口强制要求所有子类必须实现speak方法。Python中更正式的做法是使用abc模块。4.3 多态同一接口不同实现多态指的是不同类型的对象可以对同一方法调用做出不同的响应。在上面的例子中animal.speak()这行代码就是多态的体现。animal变量在运行时可能是Dog对象也可能是Cat对象但调用speak()方法时Python会自动找到该对象实际类型Dog或Cat中定义的方法来执行。多态的好处是提高了代码的通用性和灵活性。我们写一个处理Animal列表的函数不需要关心列表里具体是狗还是猫只要它们都有speak()方法就行。未来即使新增一个Bird类这个函数也完全不需要修改。def animal_concert(animals_list): for animal in animals_list: print(animal.speak()) # 这里就是多态调用 # 这个函数可以处理任何实现了 speak 方法的“动物”对象5. 类方法与静态方法不依赖于实例的操作我们之前讲的都是实例方法它们操作的是具体的对象实例self。但有时我们需要一些与类相关但与任何特定实例无关的操作。这时就需要类方法和静态方法。5.1 类方法classmethod类方法的第一个参数是cls代表类本身而不是实例。它通常用于作为替代构造函数提供不同的方式来创建对象。操作类属性。定义类方法需要使用classmethod装饰器。class Dog: species “Canis familiaris” all_dogs [] # 类属性记录所有创建过的狗 def __init__(self, name, age): self.name name self.age age Dog.all_dogs.append(self) # 每创建一个实例就添加到列表中 classmethod def get_population(cls): 类方法获取创建的狗的总数 return len(cls.all_dogs) classmethod def create_from_birth_year(cls, name, birth_year): 替代构造函数通过出生年份创建狗 from datetime import datetime current_year datetime.now().year age current_year - birth_year return cls(name, age) # 注意这里调用的是 cls(name, age)而不是 Dog(...) # 使用类方法 dog1 Dog(“Buddy”, 2) dog2 Dog.create_from_birth_year(“Lucy”, 2019) # 使用类方法创建对象 print(Dog.get_population()) # 输出2 print(dog2.age) # 输出根据当前年份计算的年龄create_from_birth_year方法接收cls参数并最终调用cls(name, age)来创建新实例。这意味着即使Dog类被继承子类调用这个类方法时cls会是子类创建出来的也是子类的实例这非常灵活。5.2 静态方法staticmethod静态方法既不需要self参数也不需要cls参数。它就像一个定义在类命名空间里的普通函数与类和实例都没有强制性的绑定关系。它用于组织代码将逻辑上属于这个类的功能放在一起。定义静态方法需要使用staticmethod装饰器。class StringUtils: staticmethod def is_palindrome(text): 静态方法判断字符串是否是回文 cleaned_text ”.join(char.lower() for char in text if char.isalnum()) return cleaned_text cleaned_text[::-1] staticmethod def count_vowels(text): 静态方法统计元音字母数量 vowels “aeiouAEIOU” return sum(1 for char in text if char in vowels) # 使用静态方法无需创建实例 print(StringUtils.is_palindrome(“A man, a plan, a canal: Panama”)) # 输出True print(StringUtils.count_vowels(“Hello World”)) # 输出3 # 也可以通过实例调用但不常见 util StringUtils() print(util.is_palindrome(“racecar”)) # 输出True实操心得如何选择如果你的方法需要访问或修改实例的状态用实例方法。如果你的方法需要访问或修改类的状态或者作为替代构造函数用类方法。如果你的方法只是一个工具函数逻辑上属于这个类但既不操作实例也不操作类用静态方法。静态方法让代码结构更清晰也便于测试。6. 属性装饰器精细化控制属性访问有时候我们希望对属性的访问和修改进行更精细的控制比如在赋值时进行验证或者将某个方法“伪装”成属性来访问。Python的property装饰器家族就是干这个的。6.1 property将方法变为“只读属性”假设我们有一个Circle类有半径radius属性。我们想方便地获取它的面积但面积不应该被直接设置它应由半径计算得出。这时可以用property。class Circle: def __init__(self, radius): self.radius radius property def area(self): 将area方法作为属性访问计算圆的面积 return 3.14159 * self.radius ** 2 property def diameter(self): 直径也是只读的由半径决定 return self.radius * 2 circle Circle(5) print(circle.radius) # 输出5 print(circle.area) # 输出78.53975 注意是属性访问不是方法调用 circle.area() print(circle.diameter) # 输出10 # circle.area 100 # 这行会报错AttributeError: can‘t set attribute现在area看起来就像一个普通的属性但实际上是调用了一个方法。这提供了更好的封装性和接口一致性。6.2 属性名.setter控制属性赋值如果我们想在给属性赋值时进行验证或触发其他操作可以定义 setter 方法。class Circle: def __init__(self, radius): self._radius radius # 使用“受保护”的属性名存储实际值 property def radius(self): 获取半径 return self._radius radius.setter def radius(self, value): 设置半径并确保其为正数 if value 0: raise ValueError(“Radius must be positive.”) self._radius value # 可以在这里触发其他操作比如更新UI、记录日志等 print(f“Radius updated to {value}”) property def area(self): return 3.14159 * self._radius ** 2 circle Circle(5) print(circle.area) # 输出78.53975 circle.radius 10 # 输出Radius updated to 10 触发了setter print(circle.area) # 输出314.159 # circle.radius -1 # 会抛出 ValueError: Radius must be positive.通过这种方式我们对外暴露了一个简洁的radius属性但内部却实现了复杂的控制和验证逻辑。外部代码像使用普通属性一样使用它完全感知不到背后的细节。6.3 属性名.deleter控制属性删除类似地可以定义 deleter 方法来控制删除属性时的行为比如清理资源。class ManagedFile: def __init__(self, filename): self.filename filename self._file open(filename, ‘r’) # 假设打开一个文件 property def content(self): self._file.seek(0) return self._file.read() content.deleter def content(self): print(“Closing the file...“) self._file.close() self._file None file_obj ManagedFile(“example.txt”) print(file_obj.content[:100]) # 读取文件前100个字符 del file_obj.content # 输出Closing the file... 触发了deleter关闭文件 # 之后再次访问 file_obj.content 可能会出错因为文件已关闭注意事项属性装饰器非常强大但不要过度使用。对于简单的、不需要额外逻辑的属性直接使用公开的实例属性是最清晰、最Pythonic的做法。只有当你需要在获取、设置或删除属性时加入特定逻辑时才考虑使用property及其 setter/deleter。7. 高级主题与实战技巧掌握了基础之后我们来看一些更深入的概念和实际编码中会遇到的技巧。7.1 组合与继承的选择“有一个” vs “是一个”继承代表“是一个”的关系Dog是一个Animal。但面向对象设计中组合“有一个”的关系往往比继承更灵活、更推荐。组合是指一个类将其他类的对象作为自己的属性。何时用继承当子类确实是父类的一种特殊类型并且需要重用或扩展父类的大部分功能时。何时用组合当你想利用其他类的功能但并不是其子类型时。# 使用继承是一个 class Engine: def start(self): return “Engine started.” class Car(Engine): # 不自然Car 是一个 Engine? pass # 使用组合有一个 class Engine: def start(self): return “Engine started.” class Car: def __init__(self): self.engine Engine() # Car 有一个 Engine def start(self): return self.engine.start() ” Car is ready to go.” my_car Car() print(my_car.start()) # 输出Engine started. Car is ready to go.组合降低了类之间的耦合度。你可以轻易地更换Car的引擎比如换成ElectricEngine而不需要修改Car类的继承结构。遵循“组合优于继承”的原则能让你的代码更模块化、更易维护。7.2 多重继承与方法解析顺序MROPython支持一个类继承自多个父类这就是多重继承。这带来了强大的灵活性但也引入了“菱形继承”等复杂性。Python使用C3线性化算法来确定方法解析顺序MRO即当调用一个方法时Python按照什么顺序在继承链中查找。class A: def do_something(self): print(“Method defined in A”) class B(A): def do_something(self): print(“Method defined in B”) super().do_something() # 调用父类A的方法 class C(A): def do_something(self): print(“Method defined in C”) super().do_something() # 调用父类A的方法 class D(B, C): def do_something(self): print(“Method defined in D”) super().do_something() # 根据MRO调用下一个类的方法 d D() d.do_something() print(D.__mro__) # 查看D类的MRO输出可能是Method defined in D Method defined in B Method defined in C Method defined in A (class ‘__main__.D’, class ‘__main__.B’, class ‘__main__.C’, class ‘__main__.A’, class ‘object’)MRO顺序是 D - B - C - A。super()并不是简单地调用“父类”而是按照 MRO 调用“下一个类”。理解 MRO 对于设计和调试复杂的多重继承结构至关重要。你可以使用类名.__mro__或类名.mro()来查看MRO。7.3 使用__slots__优化内存默认情况下Python对象使用一个字典__dict__来存储其属性。这非常灵活允许动态添加新属性但也会消耗较多内存。如果你要创建大量成千上万的某个类的实例并且它们的属性是固定的可以使用__slots__来显著减少内存占用。__slots__告诉Python为实例使用一个更紧凑的数组结构来存储属性而不是字典。class PointWithSlots: __slots__ (‘x’, ‘y’) # 明确声明只有 x 和 y 两个属性 def __init__(self, x, y): self.x x self.y y class PointWithoutSlots: def __init__(self, x, y): self.x x self.y y # 测试内存占用 import sys p1 PointWithSlots(1, 2) p2 PointWithoutSlots(1, 2) print(sys.getsizeof(p1) sys.getsizeof(p1.__slots__)) # 通常更小 print(sys.getsizeof(p2) sys.getsizeof(p2.__dict__)) # 通常更大 # p1.z 3 # 这行会报错AttributeError: ‘PointWithSlots’ object has no attribute ‘z’ # p2.z 3 # 这行是允许的注意事项使用__slots__的代价是失去了动态添加属性的能力。同时如果类需要被继承且子类也需要__slots__则子类的__slots__只包含自己新增的属性。除非你确实面临内存瓶颈并且实例属性结构稳定否则一般不需要使用__slots__。7.4 枚举类Enum定义一组命名的常量当你需要定义一组相关的、有限的常量时比如星期几、状态码、颜色等使用enum模块的Enum类比直接使用数字或字符串常量更清晰、更安全。from enum import Enum, auto class Color(Enum): RED 1 GREEN 2 BLUE 3 # 或者使用 auto() 自动赋值 # RED auto() # GREEN auto() # BLUE auto() class Status(Enum): PENDING “pending” PROCESSING “processing” COMPLETED “completed” # 使用 fav_color Color.RED print(fav_color) # 输出Color.RED print(fav_color.value) # 输出1 print(fav_color.name) # 输出RED if Status(request.status) Status.PENDING: # 确保状态值有效 # do something pass # 遍历所有枚举成员 for color in Color: print(color.name, color.value)枚举成员是单例的不能重复创建。它们保证了值的唯一性和可读性避免了魔法数字和字符串在代码中散落的问题。8. 常见问题与避坑指南在实际使用类和对象时总会遇到一些坑。这里记录了几个最常见的问题和我的解决思路。8.1 可变对象作为类属性的陷阱这是一个经典陷阱。如果你将一个可变对象如列表、字典作为类属性那么所有实例共享的是同一个对象。class ProblematicClass: shared_list [] # 类属性可变对象 def add_item(self, item): self.shared_list.append(item) obj1 ProblematicClass() obj2 ProblematicClass() obj1.add_item(“A”) obj2.add_item(“B”) print(obj1.shared_list) # 输出[‘A’, ‘B’] print(obj2.shared_list) # 输出[‘A’, ‘B’] print(ProblematicClass.shared_list) # 输出[‘A’, ‘B’]解决方案永远不要在类属性中初始化可变对象。应该在__init__方法中将可变对象初始化为实例属性。class CorrectClass: def __init__(self): self.instance_list [] # 实例属性每个对象独立 def add_item(self, item): self.instance_list.append(item) obj1 CorrectClass() obj2 CorrectClass() obj1.add_item(“A”) obj2.add_item(“B”) print(obj1.instance_list) # 输出[‘A’] print(obj2.instance_list) # 输出[‘B’]8.2 在方法中忘记使用self新手常犯的错误是在实例方法内部引用其他方法或属性时忘记了self。class MyClass: def method_a(self): print(“Method A”) def method_b(self): method_a() # 错误这会被当作一个局部函数或全局函数而不是实例方法 # 正确写法 class MyClass: def method_a(self): print(“Method A”) def method_b(self): self.method_a() # 正确通过 self 调用实例方法8.3 混淆和is运算符比较的是两个对象的值是否相等通过调用__eq__方法。is比较的是两个对象的身份标识内存地址是否相同即是否是同一个对象。list1 [1, 2, 3] list2 [1, 2, 3] list3 list1 print(list1 list2) # True值相同 print(list1 is list2) # False不是同一个对象 print(list1 is list3) # Truelist3是list1的引用是同一个对象 # 对于小整数和短字符串Python会缓存可能导致 is 判断为True但不要依赖这种行为。 a 256 b 256 print(a is b) # 在大多数Python实现中为 True (小整数缓存) c 257 d 257 print(c is d) # 通常为 False在自定义类中默认的比较行为和is一样比较的是内存地址。如果你需要根据内容判断相等需要重写__eq__方法。8.4 如何优雅地实现对象比较除了__eq__等于你还可以重写其他比较魔术方法如__lt__小于、__le__小于等于、__gt__大于、__ge__大于等于。Python 的functools.total_ordering装饰器可以帮你省事只要你定义了__eq__和其中一个比较方法如__lt__它就能自动帮你生成其他比较方法。from functools import total_ordering total_ordering class Student: def __init__(self, name, grade): self.name name self.grade grade def __eq__(self, other): return self.grade other.grade def __lt__(self, other): return self.grade other.grade def __repr__(self): return f“Student({self.name}, {self.grade})” alice Student(“Alice”, 85) bob Student(“Bob”, 90) print(alice bob) # False print(alice bob) # True print(alice bob) # True (total_ordering 自动生成) print(alice bob) # False (total_ordering 自动生成)8.5 使用dataclasses简化数据类创建如果你有一个类主要用来存储数据比如配置、DTO、数据库记录Python 3.7 的dataclasses模块可以极大减少样板代码。它会自动为你生成__init__、__repr__、__eq__等方法。from dataclasses import dataclass, field from typing import List dataclass(orderTrue) # orderTrue 会自动生成比较方法 class InventoryItem: 代表库存物品的类 name: str unit_price: float quantity_on_hand: int 0 # 可以设置默认值 tags: List[str] field(default_factorylist) # 对于可变默认值使用field def total_cost(self) - float: return self.unit_price * self.quantity_on_hand # 自动生成了 __init__, __repr__, __eq__ 等方法 item1 InventoryItem(“Screwdriver”, 9.99, 10) item2 InventoryItem(“Hammer”, 19.99, 5) print(item1) # 输出InventoryItem(name‘Screwdriver’, unit_price9.99, quantity_on_hand10, tags[]) print(item1 item2) # False print(item1.total_cost()) # 99.9 # 因为 orderTrue还可以比较 print(item1 item2) # 比较基于字段定义的顺序这里是 name (‘Screwdriver’ ‘Hammer’? 按字母序)dataclasses让创建清晰、简洁的数据容器变得异常简单是现代Python代码中的常用工具。理解并熟练运用Python的类和对象是你从脚本编写者迈向软件工程师的关键一步。它不仅仅是语法更是一种组织代码、抽象现实的思维方式。从今天起尝试在你下一个项目里有意识地将相关数据和功能封装成类你会立刻感受到代码结构清晰带来的好处。
返回列表