当前位置: 首页 > news >正文

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不建议这么做。

如果需要访问或修改这些属性,应该通过类提供的公共方法gettersetter ,来控制对属性的访问和修改。

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!

由于 DogCat 类都重写了 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 模块的基本步骤

  1. 导入模块:导入 abc 模块中的 ABCabstractmethod

  2. 定义抽象基类:通过继承 ABC 类来定义抽象基类。

  3. 定义抽象方法:使用 @abstractmethod 装饰器标记抽象方法。

  4. 实现子类:子类必须实现抽象基类中定义的所有抽象方法,否则无法被实例化。

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 是一个抽象基类,DogCat 类必须实现 speak 方法。animal_sound 函数可以接受 DogCat 类的对象,并调用它们的 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__()

  • 代码重用:避免在子类中重复父类的初始化代码。

  • 正确初始化:确保父类的初始化逻辑(如设置属性、分配资源等)被执行。

http://www.dtcms.com/a/299025.html

相关文章:

  • (React入门上手——指北指南学习(第一节)
  • Earth靶机攻略
  • 公域流量向私域流量转化策略研究——基于开源AI智能客服、AI智能名片与S2B2C商城小程序的融合应用
  • 分治算法 (Divide and Conquer)原理、及示例-JS版
  • 告别配置混乱!Spring Boot 中 Properties 与 YAML 的深度解析与最佳实践
  • C++查询mysql数据
  • linux下变更mysql的数据文件目录
  • CentOS 7 安装 MySQL 8.4.6(二进制包)指南
  • 基于MySQL实现基础图数据库
  • Day04–链表–24. 两两交换链表中的节点,19. 删除链表的倒数第 N 个结点,面试题 02.07. 链表相交,142. 环形链表 II
  • GMP模型
  • 背包问题及 LIS 优化
  • 口腔助手|口腔挂号预约小程序|基于微信小程序的口腔门诊预约系统的设计与实现(源码+数据库+文档)
  • vue子组件关闭自己的方式(事件触发)
  • 上证50指数分红和股指期货有什么关系?
  • MybatisPlus-18.插件功能-分页插件基本用法
  • Vue3 学习教程,从入门到精通,Vue3 样式绑定语法详解与案例(17)
  • 学习随想录-- web3学习入门计划
  • 【自动化运维神器Ansible】Ansible常用模块之File模块详解
  • 收银系统优惠功能架构:可扩展设计指南(含可扩展性思路与落地细节)
  • selenium自动化鼠标和键盘操作
  • 06-ES6
  • 【LLM】Kimi-K2模型架构(MuonClip 优化器等)
  • 详解力扣高频SQL50题之550. 游戏玩法分析 IV【中等】
  • qt c++ msvc2017编译器解决界面中中文乱码问题
  • 数据赋能(336)——技术平台——智能化运营
  • 动态SQL标签
  • AI-调查研究-39-多模态大模型量化 微调与量化如何协同最大化性能与效率?
  • opencv学习(图像梯度)
  • 像素、视野、光源,都有哪些因素影响测量精度?