【从0-1学习Python | 第七篇】Python 面向对象基础

前言

这篇整理 Python 面向对象的基础用法,分三部分展开:类与对象、三种方法、类的内置方法。全部内容围绕一个 Car 类展开,串起来正好是定义类、创建对象、定义行为、定制内置语法行为的完整过程。

一、类与对象

1. 类的定义

类用 class 关键字定义,类名遵循大驼峰写法,每个单词首字母大写:

python 复制代码
class Car:
    pass

类体暂时没有内容时,用 pass 占位保证语法完整。类定义完成之后,它就是一个可以用来创建对象的模板,后面的属性和方法都写在它里面。

2. 类的使用

类名后面加括号就是实例化,得到一个对象:

python 复制代码
c1 = Car()
print(c1)   # <__main__.Car object at 0x...>

每执行一次 Car() 就生成一个新对象,对象之间互不影响。直接打印对象只会看到类名和一串内存地址,看不出对象里装了什么,这个问题由后面的内置方法解决。

3. self 的作用

self 表示当前这个对象本身。通过 self.xxx 挂上的属性,只属于调用方法的那一个对象:

python 复制代码
# 定义类的时候指定实例属性
class Car:
    def __init__(self, b_name, c_color):
        self.b = b_name
        self.c = c_color

c1 = Car("hello", "red")
print(c1.b)          # hello
print(c1.__dict__)   # {'b': 'hello', 'c': 'red'}

调用 Car("hello", "red") 时,Python 把新创建的对象传给 self,把括号里的参数传给 b_name、c_color,self.b = b_name 就是把参数保存到这个对象的属性上。__dict__ 以字典形式列出对象当前的全部属性,是查看对象内部数据最直接的方式。

4. 构造方法初始化

构造方法 __init__ 在实例化的那一刻自动执行,专门用来完成初始化,不需要手动调用:

python 复制代码
class Car:
    def __init__(self, brand, name, price):
        self.brand = brand
        self.name = name
        self.price = price

c1 = Car("BMW", "X5", 500000)
print(c1.brand, c1.name, c1.price)   # BMW X5 500000

创建对象时传入的三个参数,按位置对应 __init__ 的三个形参。属性名和形参写成同名,self.brand = brand 读起来最直观。对象需要哪些属性,就在这里初始化哪些。

5. 定义一个空类

空类同样可以实例化,对象还能在类外面随时补充属性:

python 复制代码
class Car:
    pass

c1 = Car()
c1.color = "red"     # 给对象动态添加属性
print(c1.color)      # red

c1.color 把属性直接挂到了 c1 这一个对象上,别的 Car 对象不会有它。这种写法适合临时验证,属性一多还是应该回到构造方法里统一初始化。

6. 类变量

类变量定义在方法外面,挂在类本身上,所有对象共享同一份;实例属性则每个对象各一份:

python 复制代码
class Car:
    wheels = 4              # 类变量,所有实例共享

    def __init__(self, brand):
        self.brand = brand  # 实例属性,每个对象一份

c1 = Car("BMW")
c2 = Car("Audi")
print(c1.wheels, c2.wheels)   # 4 4
print(Car.wheels)             # 类名也能直接访问

对象取属性时先找实例属性,找不到再往类变量上找,所以 c1、c2 都能读到 wheels。像轮子数这种所有对象一致的值,放类变量比写在构造方法里更合适。

二、三种方法

类里的方法分成实例方法、类方法、静态方法三种,区别在于第一个参数接收什么、由谁调用。

1. 实例方法

实例方法的第一个参数固定是 self,调用时由 Python 自动把对象传进去,方法内部通过 self 读写这个对象的属性:

python 复制代码
class Car:
    def __init__(self, brand, name, price):
        self.brand = brand
        self.name = name
        self.price = price

    def running(self):
        print(f"{self.brand} {self.name} 正在高速行驶...")

    def total_cost(self, discount, rate):
        return self.price * discount + self.price * rate

c1 = Car("BMW", "X5", 500000)
total_cost = c1.total_cost(0.9, 0.1)
print(f"提车总价为:{total_cost:.0f}")   # 提车总价为:500000

c1.running()   # BMW X5 正在高速行驶...

running 只打印,不返回值;total_cost 用对象的 price 配合传入的 discount(折扣)和 rate(费率)算出提车总价,通过 return 交给调用方。c1.total_cost(0.9, 0.1) 执行时,c1 填进 self,0.9 填进 discount,0.1 填进 rate。

2. 类方法

类方法用 @classmethod 标记,第一个参数是 cls,指向类本身,一般用来读写类变量:

python 复制代码
class Car:
    wheels = 4

    @classmethod
    def show_wheels(cls):
        print(f"每辆车 {cls.wheels} 个轮子")

Car.show_wheels()   # 每辆车 4 个轮子

cls 和 self 的区别在于指向:cls 是类,self 是对象。类方法通过类名直接调用,不依赖任何具体对象。

3. 静态方法与选用

静态方法用 @staticmethod 标记,不接收 self 也不接收 cls,本质是放在类作用域里的普通函数:

python 复制代码
class Car:
    @staticmethod
    def check_price(price):
        return price > 0

print(Car.check_price(500000))   # True

check_price 只依赖传入的 price,和任何对象、类变量都无关,写成静态方法语义最清楚。三种方法的选用标准:要读写实例属性就用实例方法,要读写类变量就用类方法,两者都不涉及就写成静态方法。

三、类的内置方法

双下划线开头结尾的方法叫内置方法,也叫魔法方法,由 Python 在特定语法点自动调用。print、比较运算这些语法作用在对象上时,真正干活的就是它们。

1. 定制打印格式:__str__ 和 __repr__

__str__ 决定 print(对象) 时显示什么,必须返回字符串:

python 复制代码
class Car:
    def __init__(self, brand, name, price):
        self.brand = brand
        self.name = name
        self.price = price

    def running(self):
        print(f"{self.brand} {self.name} 正在高速行驶...")

    # 定义print(对象)时的输出格式
    def __str__(self):
        return f"{self.brand} {self.name} {self.price}"

c1 = Car("BMW", "X5", 500000)
print(c1)          # BMW X5 500000

没有 __str__ 时打印出来是内存地址,定义之后 print 直接输出这三个属性,对象里装了什么一眼可见。

__repr__ 的作用和 __str__ 类似,区别在于 print 一个列表时,逐个显示元素用的是 __repr__:

python 复制代码
    # 定义对象放进列表后的显示格式
    def __repr__(self):
        return f"Car({self.brand}, {self.name}, {self.price})"

cars = [Car("BMW", "X5", 500000), Car("Audi", "A6L", 450000)]
print(cars)   # [Car(BMW, X5, 500000), Car(Audi, A6L, 450000)]

补上 __repr__ 之后,列表里装的对象也能直接看出内容,而不是一串内存地址。

2. 定制比较规则:__lt__、__le__、__eq__

比较运算默认比较内存地址,两个属性完全一样的对象用 == 比较,结果也是 False。比较类的内置方法可以改掉这个默认行为,它们同样定义在 Car 类里,每个方法对应一种比较运算:

python 复制代码
    # 定义<  比较规则(按价格比较)
    def __lt__(self, other):
        return self.price < other.price

    def __le__(self, other):
        return self.price <= other.price

    # 定义 ==  比较规则(属性全部相同时视为相等)
    def __eq__(self, other):
        return self.price == other.price and self.brand == other.brand and self.name == other.name

c1 = Car("BMW", "X5", 500000)
c2 = Car("BMW", "X5", 500000)
c3 = Car("Audi", "A6L", 450000)

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

__lt__ 对应 <,__le__ 对应 <=,这里都按 price 比较;__eq__ 对应 ==,brand、name、price 三个属性全部相同才返回 True。c1 < c3 是 False,因为 500000 并不小于 450000,把方向反过来结果就是 True。

定义了 __lt__ 之后,sorted 这类排序函数可以直接对 Car 列表按价格排序。易错点:__eq__ 里直接访问了 other.price,如果 other 不是 Car 类型会报 AttributeError,严谨的写法是先用 isinstance(other, Car) 判断类型,再做属性比较。

3. 实例方法和魔法方法的区别

实例方法和魔法方法都定义在类里,第一个参数都是 self,但解决的问题不同。把它们放进同一个类里对比,差别一目了然:

python 复制代码
class Car:
    def __init__(self, brand, name, price):
        self.brand = brand
        self.name = name
        self.price = price

    def running(self):                 # 实例方法
        print(f"{self.brand} {self.name} 正在高速行驶...")

    # 定义print(对象)时的输出格式
    def __str__(self):                 # 魔法方法
        return f"{self.brand} {self.name} {self.price}"

    # 定义 ==  比较规则(属性全部相同时视为相等)
    def __eq__(self, other):           # 魔法方法
        return self.price == other.price and self.brand == other.brand and self.name == other.name

c1 = Car("BMW", "X5", 500000)
c2 = Car("BMW", "X5", 500000)

c1.running()       # 实例方法:写清方法名和括号,手动调用
print(c1)          # 魔法方法:代码里看不到 __str__,print 触发它执行
print(c1 == c2)    # 魔法方法:代码里看不到 __eq__,== 触发它执行

三行调用里,只有第一行写出了要执行的方法名。c1.running() 是把 running 写在代码里手动调用;后两行的代码里完全看不到 __str__ 和 __eq__,print 和 == 一碰到对象就自动执行它们,输出 BMW X5 500000 和 True。这就是两者最直观的差别:实例方法要显式调用,魔法方法藏在内置语法背后自动执行。

主要区别整理成三点:

对比点 实例方法 魔法方法
命名 自己取名,如 running、total_cost 双下划线开头结尾,名字固定
触发方式 对象手动调用,如 c1.running() 语法自动触发,print(c1) 触发 __str__,== 触发 __eq__
解决的问题 给对象扩展业务行为 定制内置语法作用在对象上的规则

结合前面的例子看,实例方法是你主动找对象办事,魔法方法是内置语法碰到对象时按你定的规矩办事。

总结

这篇把面向对象基础串成一条线:class 定义模板,构造方法在实例化时把数据挂到 self 上,实例方法、类方法、静态方法分别负责实例属性、类变量和独立逻辑,内置方法决定打印和比较这些内置语法作用在对象时的行为。可迁移的判断是:想让某个内置语法对自定义对象按自己的规则生效,先去找对应的内置方法;没定义就走默认行为,比如默认的 == 比较的就是内存地址。

相关推荐
XIU YUAN1 小时前
ESP32-C6 专业游戏合集系统 - 打造你的掌上游戏机
1024程序员节
Wang's Blog1 小时前
Java 项目实战: 交友项目 - 查询圈子个人动态:查询我的动态需求分析
java·开发语言·交友
lzqrzpt1 小时前
太阳能控制器选型要点:电路工艺与工程适配技术解析
python·1024程序员节
前端 贾公子1 小时前
LangGraph == 静态分支
1024程序员节
北极星电力网1 小时前
南方区域电力市场正式运行 全国统一电力市场体系建设取得重大突破
1024程序员节
Alsn861 小时前
JAVA通过 http api与ollama本地大模进行流式多轮对话
学习
动词ing1 小时前
【学习笔记】51单片机 LED灯闪烁,LED流水灯
1024程序员节
fish_zzzz1 小时前
笔记--浅谈内存,存储空间,BIOS芯片,显卡GPU、VRAM(1)
笔记·1024程序员节
海宇AI1 小时前
零信任架构实战:基于海宇行驶OCR证识别构建自动化租车车队行驶证电子档案网关
运维·人工智能·架构·自动化·1024程序员节