Python高级入门Day6
3. OOP基本特性
OOP的四大基本特性是封装、继承、多态和抽象。
3.1 封装
封装是指将对象的属性和方法包装在一起,对外隐藏实现细节,只提供必要的接口给外部访问。
在Python中,通过
__init__
方法初始化属性,并通过方法来操作这些属性。以
__
开头的属性或方法是私有的,在子类和类外部无法直接使用可使用“私有”属性和“公有”方法控制外部访问。
在 Python 中,封装主要通过以下几种方式实现:
1. 私有属性(Private Attributes)
在 Python 中,没有严格的私有属性,但可以通过命名约定来模拟私有属性。通常,使用单下划线 _
或双下划线 __
来表示私有属性。
单下划线 _
单下划线前缀表示属性是“受保护的”,建议外部代码不要直接访问。
单下划线前缀 _
是一种约定,而不是语言层面的强制规则。这种约定并没有强制性,外部代码仍然可以直接访问这些属性或方法。
class MyClass:def __init__(self):self._protected_attr = 42 obj = MyClass() print(obj._protected_attr) obj._protected_attr = 43 print(obj._protected_attr)
示例中虽然_protected_attr被标记为受保护的属性,但还是可以直接访问和修改。
双下划线 __
双下划线前缀表示属性是“私有的”,Python 会对其进行名称改写(name mangling),使其在类外部难以直接访问。
class MyClass:def __init__(self):self.__private_attr = 43 obj = MyClass() print(obj.__private_attr)
示例运行会报错:
AttributeError: 'MyClass' object has no attribute '__private_attr'
这是因为私有变量被python修改了名称,名称改写的规则是将属性名前面加上类名和一个下划线 _
。假设类 MyClass
,其中有一个私有属性 __private_attr
,那么 Python 会将这个属性名改写为 _MyClass__private_attr
。
class MyClass:def __init__(self):self.__private_attr = 43 obj = MyClass() print(obj._MyClass__private_attr)
obj._MyClass__private_attr:直接访问python修改后的私有变量名称可以成功,但不建议这么访问,主要是为了防止子类意外覆盖父类的私有属性。
2. 属性访问器(Getter 和 Setter)
单下划线和双下划线的变量虽然可以直接或间接被访问,但python不建议这么做。
如果需要访问或修改这些属性,应该通过类提供的公共方法getter
和 setter
,来控制对属性的访问和修改。
class MyClass:def __init__(self):self._value = 42 def get_value(self):return self._value def set_value(self, value):if value > 0:self._value = valueelse:raise ValueError("Value must be positive") obj = MyClass() print(obj.get_value()) # 输出: 42 obj.set_value(100) print(obj.get_value()) # 输出: 100
3. 使用 @property
装饰器
Python 提供了 @property
装饰器,可以更简洁地实现属性的访问和修改。
@property
是 Python 中的一个装饰器,用于将类的方法转换为属性。@property
装饰器用于定义一个只读属性。通过使用 @property
,可以将方法的调用方式转换为属性的访问方式,从而使代码更加简洁和易读。
如果需要设置属性的值,可以使用 @property.setter
装饰器定义一个可写属性。
@property
通常与 @<property_name>.setter
一起使用,以实现属性的读取和写入操作。
class MyClass:def __init__(self):self._value = 42 @propertydef value(self):return self._value @value.setterdef value(self, value):if value > 0:self._value = valueelse:raise ValueError("Value must be positive") obj = MyClass() print(obj.value) # 输出: 42 obj.value = 100 print(obj.value) # 输出: 100
在这个例子中,value方法被@property转换为属性,通过 @value.setter
,可以控制属性的写入操作,并在写入时进行验证。
3.2 继承/派生
儿子继承了父亲,父亲派生了儿子~
Python中所有的类最终都继承自内置的object类。
3.2.1 基础概念
继承/派生
继承是从已有的类中派生出新的类,新类具有原类的数据属性和行为,并能扩展新的能力。
派生类就是从一个已有类中衍生出新类,在新的类上可以添加新的属性和行为
继承/派生的作用
用继承派生机制,可以将一些共有功能加在基类中。实现代码的共享。
在不改变基类的代码的基础上改变原有类的功能
继承/派生名词:
基类(base class)/超类(super class)/父类(father class)
派生类(derived class)/子类(child class)
3.2.2 单继承
单继承的语法:
class 类名(基类名):语句块
单继承说明:单继承是指派生类由一个基类衍生出来的
示例1:
class Human: # 人类的共性def say(self, what): # 说话print("说:", what)def walk(self, distance): # 走路print("走了", distance, "公里") class Student(Human):def study(self, subject): # 学习print("学习:", subject) class Teacher(Human):def teach(self, language):print("教:", language) h1 = Human() h1.say("天气真好!") h1.walk(5) s1 = Student() s1.walk(4) s1.say("感觉有点累") s1.study("python") t1 = Teacher() t1.teach("面向对象") t1.walk(6) t1.say("一会吃点什么好呢")
3.2.3 多继承
Python支持多继承形式。多继承的类定义形如下例:
class DerivedClassName(Base1, Base2, Base3):<statement-1>...<statement-N>
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
# 类定义 class people:# 定义基本属性name = ''age = 0# 定义私有属性,私有属性在类外部无法直接进行访问__weight = 0 # 定义构造方法def __init__(self, n, a, w):self.name = nself.age = aself.__weight = w def speak(self):print("%s 说: 我 %d 岁。" % (self.name, self.age)) # 另一个类,多继承之前的准备 class speaker():topic = ''name = '' def __init__(self, n, t):self.name = nself.topic = t def speak(self):print("我叫 %s,我是一个演说家,我演讲的主题是 %s" % (self.name, self.topic)) # 多继承 class sample(people, speaker): def __init__(self, n, a, w, t):people.__init__(self, n, a, w)speaker.__init__(self, n, t) test = sample("Tim", 25, 80, "Python") test.speak() # 方法名同,默认调用的是在括号中参数位置排前父类的方法
3.2.4 覆盖 override
覆盖是指在有继承关系的类中,子类中实现了与基类同名的方法,在子类的实例调用该方法时,实际调用的是子类中的覆盖版本,这种现象叫覆盖
作用:
实现和父类同名,但功能不同的方法
覆盖示例
class A:def work(self):print("A.work 被调用!") class B(A):'''B类继承自A类'''def work(self):print("B.work 被调用!!!")pass b = B() b.work() # 请问调用谁? B a = A() a.work() # 请问调用谁? A
3.2.5 课堂练习
写一个类Bicycle类, 有run方法,调用时显示骑行里程kmclass 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 / 10if 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 多态
多态是指同一个方法在不同对象上具有不同的行为。
通过多态,可以使得不同类型的对象以相同的接口表现出不同的行为。
多态的实现通常通过继承和方法重写来实现。
多态可以分为两种主要类型:静态多态(Static Polymorphism)和动态多态(Dynamic Polymorphism)。
3.3.1 静态多态
静态多态也称为编译时多态(Compile-time Polymorphism),主要通过以下两种方式实现:
3.3.1.1 方法重载
方法重载是指在同一个类中定义多个同名但参数不同的方法。编译器根据调用时传递的参数类型和数量来决定调用哪个方法。
class Calculator:def add(self, a, b):return a + b def add(self, a, b, c):return a + b + c # 在 Python 中,方法重载并不直接支持,因为 Python 是动态类型语言 # 但可以通过默认参数或可变参数来模拟方法重载 class Calculator:def add(self, a, b, c=0):return a + b + c calc = Calculator() print(calc.add(1, 2)) # 输出: 3 print(calc.add(1, 2, 3)) # 输出: 6
在这个例子中,add
方法通过默认参数 c=0
来模拟方法重载。
3.3.1.2 运算符重载
运算符重载(Operator Overloading) 是指通过定义类的魔术方法(Magic Methods),使类的对象支持 Python 的内置运算符(如 +
、-
、*
、/
等)。通过运算符重载,我们可以让自定义类的对象像内置类型(如整数、字符串、列表等)一样使用运算符。
方法名 | 运算符 | 说明 |
---|---|---|
__add__(self, rhs) | + | 加法 |
__sub__(self, rhs) | - | 减法 |
__mul__(self, rhs) | * | 乘法 |
__truediv__(self, rhs) | / | 除法 |
__floordiv__(self, rhs) | // | 整除 |
__mod__(self, rhs) | % | 取模(求余) |
__pow__(self, rhs) | ** | 幂 |
class Point:def __init__(self, x, y):self.x = xself.y = y def __add__(self, other):return Point(self.x + other.x, self.y + other.y) def __str__(self):return f"({self.x}, {self.y})" p1 = Point(1, 2) p2 = Point(3, 4) p3 = p1 + p2 print(p3) # 输出: (4, 6)
在这个例子中,__add__
方法重载了 +
运算符,使得 Point
类的对象可以使用 +
运算符进行加法操作。
3.3.2 动态多态
动态多态也称为运行时多态(Runtime Polymorphism),主要通过方法重写(Method Overriding)实现。
3.3.2.1 方法重写
方法重写是指子类重新定义父类中已有的方法,从而在运行时根据对象的实际类型来调用相应的方法。
示例:假设基类为Animal,拥有一个speak方法,Dog和Cat分别是Animal的子类,继承Animal的方法
class Animal:def speak(self):return "Some generic sound" class Dog(Animal):pass class Cat(Animal):pass dog = Dog() cat = Cat() print(dog.speak()) print(cat.speak()) #输出: Some generic sound Some generic sound
Dog和Cat重写Animal的speak方法:
注意:子类重写的方法必须与父类方法具有相同的名称和参数列表。
class Animal:def speak(self):return "Some generic sound" class Dog(Animal):def speak(self):return "Woof!" class Cat(Animal):def speak(self):return "Meow!" dog = Dog() cat = Cat() print(dog.speak()) # 输出: Woof! print(cat.speak()) # 输出: Meow!
由于 Dog
和 Cat
类都重写了 speak
方法,因此调用时会执行各自类中的实现。
优化:提供一个公共方法,用来执行不同子类的方法
class Animal:def speak(self):return "Some generic sound" class Dog(Animal):def speak(self):return "Woof!" class Cat(Animal):def speak(self):return "Meow!" def animal_speak(animal):print(animal.speak()) dog = Dog() cat = Cat() animal_speak(dog) animal_speak(cat)
3.3.2.2 抽象基类
Python 提供了 abc
模块,用于定义抽象基类。抽象基类不能直接实例化,而是作为其他类的模板。子类必须实现抽象基类中定义的抽象方法,否则会引发错误。
使用 abc
模块的基本步骤
导入模块:导入
abc
模块中的ABC
和abstractmethod
。定义抽象基类:通过继承
ABC
类来定义抽象基类。定义抽象方法:使用
@abstractmethod
装饰器标记抽象方法。实现子类:子类必须实现抽象基类中定义的所有抽象方法,否则无法被实例化。
from abc import ABC, abstractmethod class Animal(ABC):@abstractmethoddef speak(self):pass class Dog(Animal):def speak(self):return "Woof!" class Cat(Animal):def speak(self):return "Meow!" # 多态的体现 def animal_sound(animal):print(animal.speak()) dog = Dog() cat = Cat() animal_sound(dog) # 输出: Woof! animal_sound(cat) # 输出: Meow!
在这个例子中,Animal
是一个抽象基类,Dog
和 Cat
类必须实现 speak
方法。animal_sound
函数可以接受 Dog
和 Cat
类的对象,并调用它们的 speak
方法。
3.3.3 内建函数重写(了解)
在自定义类内添加相应的方法,让自定义类创建的实例像内建对象一样进行内建函数操作。
然而,这样做通常是不推荐的,因为这可能会导致代码的可读性和可维护性降低,并且可能会引发意外的行为。
对象转字符串函数重写
对象转字符串函数重写方法
str() 函数的重载方法:
def __str__(self)
如果没有
__str__(self)
方法,则返回repr(obj)函数结果代替
str函数重写示例
class MyNumber:"此类用于定义一个自定义的类,用于演示str函数重写"def __init__(self, value):"构造函数,初始化MyNumber对象"self.data = valuedef __str__(self):"转换为普通字符串"return "%s" % self.data n1 = MyNumber("一只猫") n2 = MyNumber("一只狗") print("str(n2) ===>", str(n2))
内建函数重写
__abs__
abs(obj) 函数调用__len__
len(obj) 函数调用__reversed__
reversed(obj) 函数调用__round__
round(obj) 函数调用内建函数 重写示例
# file : len_overwrite.py class MyList:def __init__(self, iterable=()):self.data = [x for x in iterable]def __repr__(self):return "MyList(%s)" % self.datadef __len__(self):print("__len__(self) 被调用!")return len(self.data)def __abs__(self):print("__len__(self) 被调用!")return MyList((abs(x) for x in self.data)) myl = MyList([1, -2, 3, -4]) print(len(myl)) print(abs(myl))
4. super函数
super() 函数是用于调用父类(超类)的一个方法。
4.1 基本使用
在子类方法中使用 $super().add()$ 调用父类中已被覆盖的方法
使用 $super(Child, obj).myMethod()$ 用于子类对象调用父类已被覆盖的方法
class A:def add(self, x):y = x+1print(y) class B(A):def add(self, x):print("子类方法")super().add(x) b = B() b.add(2) # 3
class Parent: # 定义父类def myMethod(self):print('调用父类方法') class Child(Parent): # 定义子类def myMethod(self):print('调用子类方法') c = Child() # 子类实例 c.myMethod() # 子类调用重写方法 super(Child, c).myMethod() # 用子类对象调用父类已被覆盖的方法
4.2 super().__init__()
通过 $super().__init__()$ 调用父类构造函数,以确保父类的构造函数被正确调用和初始化。
class Parent:def __init__(self):print("Parent class constructor called")self.parent_attribute = "I am a parent attribute" class Child(Parent):def __init__(self):super().__init__()print("Child class constructor called")self.child_attribute = "I am a child attribute" # 创建一个 Child 类的实例 child_instance = Child() print(child_instance.parent_attribute) # 输出 # Parent class constructor called # Child class constructor called
为什么使用 super().__init__()
?
代码重用:避免在子类中重复父类的初始化代码。
正确初始化:确保父类的初始化逻辑(如设置属性、分配资源等)被执行。