Skip to content

继承与魔术方法

类可以继承另一个类——子类获得父类的属性和方法,还能加自己的东西或改写父类的行为。继承表达的是"是某种"关系PublishedArticleArticle 的一种,AdminUserUser 的一种。

魔术方法是 Python 预定义的特殊方法,名字前后有双下划线——__init__ __str__ __eq__ 等。它们让对象支持内置操作:print(obj) 调用 __str__obj1 == obj2 调用 __eq__

一、继承的基本写法

python
class Article:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.status = "草稿"

    def publish(self):
        self.status = "已发布"
        print(f"{self.title} 已发布")


class PublishedArticle(Article):
    """已发布文章——创建时就直接是已发布状态。"""

    def __init__(self, title, author, publish_date):
        super().__init__(title, author)  # 调用父类的 __init__
        self.status = "已发布"            # 覆盖默认值
        self.publish_date = publish_date  # 子类自己的属性

    def show_info(self):
        print(f"《{self.title}》by {self.author}")
        print(f"状态:{self.status},发布日期:{self.publish_date}")

PublishedArticle(Article) 表示 PublishedArticle 继承 Articlesuper().__init__(title, author) 调用父类的初始化方法,子类再加自己的逻辑。

python
article = PublishedArticle("Python 入门", "张三", "2024-06-01")
article.show_info()
# 《Python 入门》by 张三
# 状态:已发布,发布日期:2024-06-01

article.publish()  # 继承自父类的方法仍然可用
# Python 入门 已发布

子类能用父类的方法(publish),也能加自己的方法(show_info)。

二、重写父类方法

子类可以重新定义父类的方法,覆盖原有行为:

python
class Article:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.status = "草稿"

    def get_summary(self):
        return f"{self.title}{self.author})"


class FeaturedArticle(Article):
    """精选文章——摘要里要带"推荐"标记。"""

    def get_summary(self):
        # 重写父类方法
        return f"[推荐] {self.title}{self.author})"


a1 = Article("Python 入门", "张三")
a2 = FeaturedArticle("Go 并发", "李四")

print(a1.get_summary())  # Python 入门(张三)
print(a2.get_summary())  # [推荐] Go 并发(李四)

FeaturedArticle 重写了 get_summary,调用时走子类的版本。

什么时候用继承? 当子类确实是父类的一种特殊情况时。FeaturedArticleArticle 的一种,用继承合理;ArticleAuthor 是"文章有作者"的关系,用组合更合适。继承容易被滥用——能用组合就优先用组合

三、魔术方法

3.1 __str__——print 时显示什么

python
class Contact:
    def __init__(self, name, phone):
        self.name = name
        self.phone = phone

    def __str__(self):
        return f"{self.name}{self.phone})"


c = Contact("张三", "13800138000")
print(c)  # 张三(13800138000)

不写 __str__ 时,print(c) 输出的是类似 <__main__.Contact object at 0x...> 这种内存地址——对排查没帮助。写了 __str__print 时输出人类可读的内容。

3.2 __repr__——调试时显示什么

__repr__ 是给开发者看的,通常写成"能还原对象的代码":

python
class Contact:
    def __init__(self, name, phone):
        self.name = name
        self.phone = phone

    def __repr__(self):
        return f"Contact({self.name!r}, {self.phone!r})"


c = Contact("张三", "13800138000")
print(c)  # Contact('张三', '13800138000')

在交互式环境或调试器里直接敲变量名,显示的是 __repr__ 的输出。如果只定义一个,优先定义 __repr__——没有 __str__ 时,print 也会用 __repr__

3.3 __eq__——两个对象相等怎么判断

python
class Contact:
    def __init__(self, name, phone):
        self.name = name
        self.phone = phone

    def __eq__(self, other):
        # 两个对象的 name 和 phone 都相等才认为相等
        return self.name == other.name and self.phone == other.phone


c1 = Contact("张三", "13800138000")
c2 = Contact("张三", "13800138000")
c3 = Contact("张三", "13900139000")

print(c1 == c2)  # True
print(c1 == c3)  # False

不写 __eq__ 时,c1 == c2 比较的是内存地址(两个不同对象,False)。写了 __eq__,可以按业务逻辑定义"相等"。

3.4 __len__——len() 返回什么

python
class Article:
    def __init__(self, title, content):
        self.title = title
        self.content = content

    def __len__(self):
        return len(self.content)


article = Article("Python 入门", "Python 是一门很好学的编程语言。")
print(len(article))  # 14

len(article) 调用 article.__len__()

3.5 __bool__——bool() 返回什么

python
class Article:
    def __init__(self, title, content=""):
        self.title = title
        self.content = content

    def __bool__(self):
        # 内容不为空才认为是"真"
        return len(self.content) > 0


a1 = Article("空文章")
a2 = Article("正常文章", "有一些内容")

print(bool(a1))  # False
print(bool(a2))  # True

if a1:
    print("有内容")
else:
    print("内容为空")  # 走这里

不写 __bool__ 时,对象默认是 True。写了可以定义"什么情况下这个对象算真"。

四、常用魔术方法一览

方法触发场景返回
__init__创建对象时 Article(...)
__str__print(obj) str(obj)字符串
__repr__调试时直接显示字符串
__eq__obj1 == obj2布尔值
__lt__obj1 < obj2布尔值
__len__len(obj)整数
__bool__bool(obj) if obj布尔值
__getitem__obj[key]取到的值
__setitem__obj[key] = value
__contains__key in obj布尔值
__iter__for item in obj迭代器

这些方法让自定义对象表现得像内置类型——能 print、能比大小、能迭代、能判断包含关系。不用全记,用到再查。最常用的是 __init__ __str__ __repr__ __eq__ 这四个,其他用到场景再补。

五、继承与组合

继承表达"是某种",组合表达"有某个"。继承容易被过度使用——新手很容易把"文章有作者"写成"文章继承作者",这是错的。

判断标准:问自己"子类是父类的一种吗?"。FeaturedArticleArticle 的一种,用继承对;Article 不是 Author 的一种,用组合对。