Python 的面向对象编程(OOP)是一种基于类和对象的编程范式,支持封装、继承、多态等特性。
1. 初识类和对象
示意图:
/-------> 陶吉吉 小镇姑娘,二十二 歌手(类) \-------> 周杰伦 花海,游园会 /-------> 国漫 狐妖小红娘 牧神记 动漫(类) \-------> 日漫 咒术回站 鬼灭之刃 /-------> 100 (对象) int(类) \-------> 200 (对象)
1.1 类→Class
物以类聚
1.1.1 概念
类是面向对象编程(OOP)的核心概念之一。它是一种用户自定义的数据类型,封装了数据和操作数据的方法。类可以看作是具有相同属性和方法的对象的集合。
1.1.2 创建
数据成员:表明事物的特征。 相当于变量
方法成员:表明事物的功能。 相当于函数
通过class
关键字定义类。
类的创建语句语法:
class 类名 (继承列表): 实例属性(类内的变量) 定义 实例方法(类内的函数method) 定义 类变量(class variable) 定义 类方法(@classmethod) 定义 静态方法(@staticmethod) 定义
参考代码:
类名就是标识符,建议(有点强烈)首字母大写
类名实质上就是变量,它绑定一个类
self代表类实例化的对象本身,不一定是self,这是一个形参,可以自己命名
1.2 对象→Object
1.2.1 概念
对象是类的实例化,是类的实际数据存储,具有类所定义的属性和方法。
1.2.2 创建
构造函数调用表达式
变量 = 类名([参数])
说明
变量存储的是实例化后的对象地址
类参数按照初始化方法的形参传递
对象是类的实例,具有类定义的属性和方法。
通过调用类的构造函数来创建对象。
每个对象有自己的状态,但共享方法。
示例代码
创建了两个对象,dog1和dog2,它们的id不同,所以同一个类申明的变量,不是同一个,一个类可以申明多个对象,包含了这个对象的变量和函数,互不干扰。
self.name = name 操作是,对Person这个对象创建name这个变量(左边),把name对象赋值给左边的name,age同理。
2. 属性和方法
类的属性和方法是类的核心组成部分,它们用于定义类的状态和行为。
2.1 实例属性
每个实例有自己的变量,称为实例变量(也叫属性)。实例属性是每个对象独有的属性,定义时需要使用self
关键字。实例属性在__init__
方法中初始化。
属性的使用语法
实例.属性名
属性使用
2.2 实例方法
class 类名(继承列表): def 实例方法名(self, 参数1, 参数2, ...): "文档字符串" 语句块
实例方法就是函数,至少有一个指向实例对象的形参self
调用
实例.实例方法名(调用传参) # 或 类名.实例方法名(实例, 调用传参)
带有实例方法的简单的Dog类
实列方法,需要有一个指针指向实列对象的形参self,这个self可以自己命名。在实列对象创建的变量在调用的时候,不需要用self来调用变量,如果是这个类自带的变量,在类下面的定义的函数中调用的时候,就需要用self。
2.3 类属性
类属性是类本身具有的属性,属于所有实例对象。定义时直接在类中定义,不依赖于self
。
作用:
通常来存储共有的属性
类属性说明
类属性,可以通过该类直接访问
类属性,可以通过类的实例直接访问
类属性示例
2.4 类方法
类方法是用于描述类的行为的方法,类方法属于类,不属于该类创建的对象
说明
类方法需要使用@classmethod装饰器定义
类方法至少有一个形参用于绑定类,约定为 cls
类和该类的实例都可以调用类方法
类方法不能访问此类创建的对象的实例属性
类方法示例1
调用A.get_v的函数,来得到v的值v是A类里面的变量,所以要用一个形参变量cls(规范命名,也可以不是这个),输出0;调用A.set_v函数,将传入的参数100,传递给cls.v这个A类的v,再调用get_v函数,输出的就是100;因为A类里面的v已经改变了,所以申明的对象a里面的v也是100.
类方法示例2
类可以调用类方法,实例可以调用实列方法,实例可以调用类方法,类方法不能直接调用实例方法,如果需要调用就得添加实例对象
可以通过类函数访问类里面的变量,可以通过实例化的对象来访问类里的变量,可以通过类里面的方法来调用其他的类方法
2.5 静态方法
静态方法是Python中的一种特殊方法,它不需要访问类的实例或类本身的状态,因此不需要self
或cls
参数。静态方法通常用于将一些与类相关的辅助功能组织在类中,使代码结构更清晰。
使用@staticmethod装饰器定义
通过类或类实例调用
可以访问类属性,不能访问实例属性
静态方法示例
因为在静态函数里面,没有传入self或者slc这样的形参,所以就没办法去访问实例或者类,只能是去访问变量参数
静态方法的特点
静态方法不需要实例化类就可以直接调用。
静态方法不依赖于类或实例的状态,因此无法访问
self
或cls
。静态方法可以被子类继承,但不会根据子类的类属性或实例属性进行修改。
静态方法的使用场景
当一个方法与类相关,但不需要访问类或实例的属性时,可以将其定义为静态方法。
将一些工具函数或辅助函数组织在类中,便于代码管理。
静态方法与类方法、实例方法的区别
实例方法:需要实例化类后调用,第一个参数是
self
,表示类的实例。类方法:使用
@classmethod
装饰器定义,第一个参数是cls
,表示类本身。静态方法:不需要实例化类即可调用,没有
self
或cls
参数。
2.6 构造方法和初始化方法
__new__
方法
作用:负责对象的创建和内存分配。它在对象实例化时被调用,返回一个新的对象实例。
参数:第一个参数是类本身(cls
), followed by the arguments passed to the class instantiation.
调用:通常不需要显式定义__new__
,因为Python会自动调用基类object
的__new__
方法。如果你重写了__new__
,必须确保返回一个实例(通常是通过调用super().__new__(cls)
)。
注意:如果你重写了__new__
,它必须返回一个实例,否则__init__
不会被调用。
__init__
方法
作用:负责初始化已经创建的对象。它在__new__
之后被调用,用于设置对象的初始状态。
参数:第一个参数是实例本身(self
), followed by other parameters used to initialize the object.
调用:在对象创建后自动调用,用于初始化对象的属性。
实际开发中的使用
通常不需要显式定义
__new__
:在大多数情况下,你只需要定义__init__
来初始化对象。单例模式:
__new__
在实现单例模式时非常有用,因为它可以控制对象的创建过程。自定义对象创建行为:如果你需要在对象创建时添加一些自定义逻辑(如缓存、对象池等),可以重写
__new__
。
2.8 魔术方法
魔术方法是 Python 中一种特殊的方法,它们允许咱们自定义类的行为,以便与内置 Python 功能(如运算符、迭代、字符串表示等)交互。魔术方法以双下划线(__
)开头和结尾,例如 __init__
、__str__
、__add__
等。
2.8.1 常用方法
__init__(self, ...): 初始化对象,通常用于设置对象的属性。
__str__(self): 定义对象的字符串表示形式,可通过str(object)或print(object)调用。例如,您可以返回一个字符串,描述对象的属性。
__repr__(self): 定义对象的“官方”字符串表示形式,通常用于调试。可通过repr(object)调用。
__len__(self): 定义对象的长度,可通过len(object)调用。通常在自定义容器类中使用。
__getitem__(self, key): 定义对象的索引操作,使对象可被像列表或字典一样索引。例如,object[key]。
__setitem__(self, key, value): 定义对象的赋值操作,使对象可像列表或字典一样赋值。例如,object[key] = value。
__delitem__(self, key): 定义对象的删除操作,使对象可像列表或字典一样删除元素。例如,del object[key]。
__iter__(self): 定义迭代器,使对象可迭代,可用于for循环。
__next__(self): 定义迭代器的下一个元素,通常与__iter__一起使用。
__add__(self, other): 定义对象相加的行为,使对象可以使用+运算符相加。例如,object1 + object2。
__sub__(self, other): 定义对象相减的行为,使对象可以使用-运算符相减。
__eq__(self, other): 定义对象相等性的行为,使对象可以使用==运算符比较。
__lt__(self, other): 定义对象小于其他对象的行为,使对象可以使用<运算符比较。
__gt__(self, other): 定义对象大于其他对象的行为,使对象可以使用>运算符比较。
__call__(self, other) 是一个特殊的方法(也称为“魔法方法”),它允许一个对象像函数一样被调用。
2.8.2 案例参考
__init__(self, ...)
:初始化对象
用于设置对象的属性,当对象被创建时自动调用。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
__str__(self)
:定义对象的字符串表示形式
通过 str(object)
或 print(object)
调用,返回一个易读的字符串。class Person:
def __str__(self):
return f"Person(name={self.name}, age={self.age})"
__repr__(self)
:定义对象的“官方”字符串表示形式
通常用于调试,返回一个更详细的字符串。
class Person:
def __repr__(self):
return f"Person(name={self.name}, age={self.age})"
__len__(self)
:定义对象的长度
通过 len(object)
调用,通常在自定义容器类中使用。
class MyList:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
__getitem__(self, key)
:定义对象的索引操作
使对象可被像列表或字典一样索引。
class MyList:
def __getitem__(self, key):
return self.items[key]
__setitem__(self, key, value)
:定义对象的赋值操作
使对象可像列表或字典一样赋值。
class MyList:
def __setitem__(self, key, value):
self.items[key] = value
__delitem__(self, key)
:定义对象的删除操作
使对象可像列表或字典一样删除元素。
class MyList:
def __delitem__(self, key):
del self.items[key]
__add__(self, other)
:定义对象相加的行为
使对象可以使用 +
运算符相加。
class Vector:
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
__sub__(self, other)
:定义对象相减的行为
使对象可以使用 -
运算符相减。
class Vector:
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
__eq__(self, other)
:定义对象相等性的行为
使对象可以使用 ==
运算符比较。
class Person:
def __eq__(self, other):
return self.name == other.name and self.age == other.age
__lt__(self, other)
:定义对象小于其他对象的行为
使对象可以使用 <
运算符比较。
class Person:
def __lt__(self, other):
return self.age < other.age
__gt__(self, other)
:定义对象大于其他对象的行为
使对象可以使用 >
运算符比较。
class Person:
def __gt__(self, other):
return self.age > other.age
__call__(self, ...)
:定义对象被调用的行为
使对象可以像函数一样被调用。
class CallableClass:
def __call__(self, value):
return value * 2
3. OOP基本特性
OOP的四大基本特性是封装、继承、多态和抽象。
3.1 封装
封装是面向对象编程的核心概念之一,它将数据(属性)和操作数据的方法封装到一个类中,限制对内部实现细节的访问,只暴露必要的接口给外部使用。封装的主要目的是提高代码的安全性和可维护性。
实现封装的方式
在 Python 中,封装通过以下方式实现:
-
私有属性:在类中,通过在属性名前添加双下划线(
__
)将其定义为私有属性,外部无法直接访问。 -
公有方法:提供公有方法(不以下划线开头)来操作私有属性,实现对内部数据的封装。
添加了(__),在类里面就是私有属性,如果直接通过实例来调用,会显示不存在,如果要来调用这个私有的变量,需要用函数来调用
3.2 继承/派生
儿子继承了父亲,父亲派生了儿子~
Python中所有的类最终都继承自内置的object类。
3.2.1 基础概念
继承/派生
继承是从已有的类中派生出新的类,新类具有原类的数据属性和行为,并能扩展新的能力。
派生类就是从一个已有类中衍生出新类,在新的类上可以添加新的属性和行为
继承/派生的作用
用继承派生机制,可以将一些共有功能加在基类中。实现代码的共享。
在不改变基类的代码的基础上改变原有类的功能
继承/派生名词:
基类(base class)/超类(super class)/父类(father class)
派生类(derived class)/子类(child class)
3.2.2 继承的实现
继承语法:
Python 支持多继承,即一个子类可以继承多个父类。
class 子类名(父类名1,父类名2,父类名3): # 子类的内容
3.2.3 覆盖
在子类中实现与基类同名的方法,我们叫覆盖
作用:
实现和父类同名,但功能不同的方法
覆盖示例
3.2.4 课堂练习
写一个类Bicycle类, 有run方法,调用时显示骑行里程km class Bicycle: def run(self, km): print("自行车骑行了", km, "公里") 再写一个类EBicycle,在Bicycle类的基础上,添加电池电量volume属性,有两个方法: 1. fill_charge(vol) 用来充电, vol 为电量 2. run(km)方法每骑行10km消耗电量1度,同时显示当前电量,当电量耗尽则,则调用Bicycle的run方法 class EBicyle(Bicycle): ...
参考:
class Bicycle: def run(self, km): print("自行车骑行了", km, "公里") class EBicycle(Bicycle): def __init__(self, vol): self.cur_volume = vol # 当前电量 def run(self, km): e_km = min(km, self.cur_volume * 10) # 求km和 乘余电量能行走的最小里程 self.cur_volume -= e_km / 10 if e_km > 0: print("电动车骑行了 %d km" % e_km, "剩余电量:", self.cur_volume) if km > e_km: super().run(km - e_km) def fill_charge(self, vol): print("电动自行车充电", vol, "度") self.cur_volume += vol b = EBicycle(5) # 新买的电动车内有5度电 b.run(10) # 电动骑行了10km 还剩 4度电 b.run(100) # 电动骑行了 40 km ,还剩 0 度电, 用脚登骑行了60km b.fill_charge(10) # 电动自行车充电 10 度 b.run(50) # 骑行了50公里剩余 5度电
3.3 多态
多态是面向对象编程的核心概念之一,它允许不同类的对象对同一消息作出不同的响应。在 Python 中,多态是通过鸭子类型(Duck Typing)来实现的,即只要一个对象具有某种方法或属性,就可以被当作是具有该方法或属性的对象来使用,而不需要关心它具体属于哪个类。
多态的实现方式
在 Python 中,多态可以通过以下几种方式实现:
3.3.1 方法重写
在继承关系中,子类可以重写父类的方法,以实现自己的特定功能。当通过父类类型的引用调用方法时,会根据实际对象的类型调用相应的方法。
在这个例子中,
Animal
类定义了一个speak
方法,但没有实现具体的功能。Dog
和Cat
类分别继承了Animal
类,并重写了speak
方法,实现了自己的功能。当通过dog
和cat
对象调用speak
方法时,会根据实际对象的类型调用相应的方法。
3.3.2 接口一致性
只要不同的类具有相同的方法或属性,就可以通过统一的接口来调用它们,而不需要关心它们的具体类型。
在这个例子中,
Shape
类定义了一个area
方法,但没有实现具体的功能。Circle
和Rectangle
类分别继承了Shape
类,并实现了area
方法。通过统一的area
接口,可以调用不同形状的面积计算方法。
3.4 重写
通过方法重写,子类可以实现与父类不同的功能,从而实现多态。
3.4.1 对象转字符串重写
在 Python 中,可以通过重写 __str__
和 __repr__
方法来自定义对象转换为字符串时的行为。
__str__
方法
__str__
方法用于返回对象的字符串表示,当使用 str()
函数或 print()
函数时会被调用。如果没有定义 __str__
方法,则会使用 __repr__
方法的结果。
__repr__
方法
__repr__
方法返回对象的官方字符串表示,通常用于调试和开发。它应该返回一个可以用来重新创建该对象的字符串。
class MyNumber: """此类用于定义一个自定义的类,用于演示str/repr函数重写""" def __init__(self, value): """构造函数,初始化MyNumber对象""" self.data = value def __str__(self): """转换为普通字符串""" return "%s" % self.data n1 = MyNumber("一只猫") n2 = MyNumber("一只狗") print("str(n2) ===>", str(n2))
3.4.2 内建函数重写
__abs__
abs(obj) 函数调用
__len__
len(obj) 函数调用
__reversed__
reversed(obj) 函数调用
__round__
round(obj) 函数调用
内建函数 重写示例
3.4.2 运算符重载
运算符重载是指让自定义的类生成的对象(实例)能够使用运算符进行操作
运算符重载的作用
让自定义类的实例像内建对象一样进行运算符操作
让程序简洁易读
对自定义对象将运算符赋予新的运算规则
运算符重载说明:
运算符重载方法的参数已经有固定的含义,不建议改变原有的意义
方法名 | 运算符和表达式 | 说明 |
---|---|---|
__add__(self, rhs) | self + rhs | 加法 |
__sub__(self, rhs) | self - rhs | 减法 |
__mul__(self, rhs) | self * rhs | 乘法 |
__truediv__(self, rhs) | self / rhs | 除法 |
__floordiv__(self, rhs) | self // rhs | 地板除 |
__mod__(self, rhs) | self % rhs | 取模(求余) |
__pow__(self, rhs) | self ** rhs | 幂 |
rhs (right hand side) 右手边
二元运算符重载方法格式:
def __xxx__(self, other): ....
算术运算符重载示例
4. super函数
在 Python 中,super()
是一个内置函数,用于调用父类的方法。它在多继承和方法重写的情况下非常有用,可以帮助我们实现对父类方法的扩展和重用。
4.1 基本使用
在子类方法中使用 super().add() 调用父类中已被覆盖的方法
使用 super(Child, obj).myMethod() 用于子类对象调用父类已被覆盖的方法
Box2声明一个对象b2,对b2.x变量赋值400,调用super(Box2,b2).fn()函数,意思是我要调用Box2声明的b2对象,用b2对象来调用基类的fn函数,所以把b2传给了fn函数,所以self.x=400。
4.2 super().__init__()
通过 super().\_\_init\_\_() 调用父类构造函数,以确保父类的构造函数被正确调用和初始化。
当我用Box2声明了一个对象b2,需要对b2对象进行传参,在__init__的时候,不能忘了对父类也进行__init__,super(Box2,self).__init__(width,height),意思是我要将用self,也就是b2来调用父类的init函数,传递给的形参也是b2的
为什么使用 super().__init__()
?
代码重用:避免在子类中重复父类的初始化代码。
正确初始化:确保父类的初始化逻辑(如设置属性、分配资源等)被执行。