Python编程 - 深入面向对象

目录

前言

一、隐藏数据

(一)使用私有属性

(二)使用属性和方法的封装

二、隐藏功能

三、对象关联

(一)关联

(二)聚合

(三)组合

(四)继承

(五)依赖

(六)总结

四、单继承

(一)基本概念

(二)语法

五、多继承

(一)基本实例

(二)方法解析顺序(MRO)

(三)使用场景

(四)注意事项

六、重写

(一)注意

七、super函数

(一)语法

(二)构造函数中的super

(三)多重继承中的super

(四)总结

八、总结


前言

上篇文章主要了解面向对象中的类和对象、实例属性和实例方法等,接下来继续深入了解python的面向对象,冲冲冲!


一、隐藏数据

隐藏数据是面向对象编程中的一个重要概念,旨在实现数据封装和信息隐藏。通过隐藏类的内部数据,开发者可以防止外部代码直接访问和修改对象的内部状态,从而保证对象的完整性和一致性。在Python中隐藏数据的方式有两种:使用私有属性和使用属性和方法的封装。

(一)使用私有属性

在类中使用双下划线前缀来定义私有属性。虽然Python没有真正的私有属性,但使用双下划线前缀会触发名称重整,使得属性变得不容易访问。

示例:

class MyClass:
    def __init__(self, value):
        self.__hidden_value = value

    def get_value(self):
        return self.__hidden_value

obj = MyClass(42)
print(obj.get_value())  # 输出: 42
print(obj.__hidden_value)  # 会报错: AttributeError
print(obj._MyClass__hidden_value)  # 可以通过重整的名称访问

(二)使用属性和方法的封装

通过封装属性,使得它们只能通过特定的方法访问。这种方式可以控制数据的访问和修改,并在访问或修改时执行额外的操作。

示例:

class MyClass:
    def __init__(self, value):
        self._value = value

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        # 可以添加额外的逻辑
        self._value = new_value

obj = MyClass(42)
print(obj.value)  # 输出: 42
obj.value = 100
print(obj.value)  # 输出: 100

二、隐藏功能

在Python中隐藏功能或方法的主要作用与隐藏数据类似,但更侧重于控制类或模块的行为和接口。隐藏功能的主要作用有以下几种:

1. 控制接口暴露

隐藏功能可以帮助开发者限制模块、类或对象对外暴露的接口。只将必要的功能暴露给外部用户,减少外部代码对内部实现的依赖,从而使代码更加清晰、简洁且易于维护。

2. 增强封装性

通过隐藏实现细节,开发者可以在不影响外部接口的情况下自由修改或重构内部逻辑。这种封装性使得代码更容易更新和维护,而不会破坏现有的功能。

3. 保护实现细节

隐藏功能可以保护复杂或不稳定的实现细节,防止外部代码依赖这些功能,从而避免潜在的错误或意外行为。这样可以降低错误发生的可能性,尤其是在实现细节需要频繁变化时。

4. 防止滥用

有些功能可能只在特定上下文中有效或安全使用。通过隐藏这些功能,开发者可以防止它们被滥用或误用。例如,某些内部工具函数或方法可能仅供内部使用,而不适合外部调用。

5. 提升安全性

通过隐藏可能引发安全问题的功能,开发者可以减少外部攻击者利用这些功能的机会。例如,某些调试或测试方法可能暴露敏感信息,隐藏这些方法可以提高代码的安全性。

6. 简化外部接口

隐藏不必要的功能可以使外部接口更简洁。用户只需关注公开的核心功能,而不必了解或理解所有的实现细节,这有助于降低学习和使用的难度。

7. 支持内部使用

某些功能可能是专门为类或模块内部使用而设计的,它们并不打算供外部调用。通过隐藏这些功能,代码更容易维护和理解,因为外部开发者不会尝试去调用这些内部函数或方法。

8. 提供更好的用户体验

隐藏内部功能可以使API更加直观和用户友好,用户不会被大量的内部方法或属性所困扰,只需使用公开的API进行交互。

9. 遵循设计原则

隐藏功能符合“单一职责原则”和“接口隔离原则”等设计原则。通过减少暴露的功能和接口,开发者可以更好地实现代码的模块化和职责分离。

在python中,通过通过单下划线前缀和双下划线前缀隐藏功能,示例:

  • 单下划线前缀:通过使用单下划线前缀来标记某些方法或属性为内部使用。虽然这并不能真正阻止访问,但它表示这些功能不应该被外部代码使用。
class MyClass:
    def __init__(self):
        self._internal_function()

    def _internal_function(self):
        print("这是一个内部方法")

    def public_function(self):
        print("这是一个公开的方法")

obj = MyClass()
obj.public_function()  # 正常调用
obj._internal_function()  # 可以调用,但不建议这样做
  • 双下划线前缀:通过使用双下划线前缀将方法或属性标记为类的私有成员,触发名称重整,进一步限制外部访问。
class MyClass:
    def __init__(self):
        self.__hidden_function()

    def __hidden_function(self):
        print("这是一个隐藏的方法")

    def public_function(self):
        print("这是一个公开的方法")

obj = MyClass()
obj.public_function()  # 正常调用
obj.__hidden_function()  # 访问会引发AttributeError
obj._MyClass__hidden_function()  # 可以通过名称重整来访问,但不建议这样做

三、对象关联

对象关联是面向对象编程中的一个关键概念,用于描述不同对象之间的关系。对象关联可以帮助开发者组织和管理代码的结构,使其更加清晰和可维护。Python中的对象关联通常分为以下几种类型:

(一)关联

关联是最基本的对象关系,表示一个对象与另一个对象之间有某种联系。关联关系通常是双向的,但可以是单向的。对象之间通过引用彼此来实现关联。

示例:

class Driver:
    def __init__(self, name):
        self.name = name

class Car:
    def __init__(self, model, driver):
        self.model = model
        self.driver = driver  # 关联Driver对象

driver = Driver("张三")
car = Car("大众", driver)

print(car.driver.name)  # 输出: 张三

(二)聚合

聚合是一种特殊的关联关系,表示一种“整体-部分”关系。聚合关系下,部分对象可以独立于整体对象存在。整体对象通常会引用部分对象,但不会管理它们的生命周期。

示例:

class Engine:
    def __init__(self, horsepower):
        self.horsepower = horsepower

class Car:
    def __init__(self, model, engine):
        self.model = model
        self.engine = engine  # 聚合Engine对象

engine = Engine(150)
car = Car("大众", engine)

print(car.engine.horsepower)  # 输出: 150

在这个例子中,Engine是一个独立存在的对象,Car只是引用了它。这意味着即使Car对象被销毁,Engine对象仍然可以独立存在。

(三)组合

组合是一种更强的“整体-部分”关系,表示部分对象的生命周期完全依赖于整体对象。整体对象负责创建和销毁部分对象,这意味着当整体对象被销毁时,部分对象也会被销毁。

示例:

class Engine:
    def __init__(self, horsepower):
        self.horsepower = horsepower

class Car:
    def __init__(self, model, horsepower):
        self.model = model
        self.engine = Engine(horsepower)  # 组合Engine对象

car = Car("大众", 200)
print(car.engine.horsepower)  # 输出: 200

在组合关系中,Engine对象是Car对象的一部分。当Car对象被销毁时,其内部的Engine对象也会被销毁。

(四)继承

继承用于表示子类继承父类的属性和方法。继承允许子类复用父类的代码,同时可以添加或修改功能。

示例:

class Vehicle:
    def __init__(self, brand):
        self.brand = brand

    def drive(self):
        print(f"{self.brand} ")

class Car(Vehicle):  # 继承Vehicle
    def __init__(self, brand, model):
        super().__init__(brand)
        self.model = model

car = Car("大众", "宝来")
car.drive()  # 输出: 大众

(五)依赖

依赖表示一个对象使用另一个对象作为参数或返回值,通常是临时的。依赖关系表示两个对象之间的一种“使用”关系。

示例:

class Fuel:
    def __init__(self, type):
        self.type = type

class Car:
    def __init__(self, model):
        self.model = model

    def refuel(self, fuel):
        print(f"这车加的油是 {fuel.type}")

# 示例
fuel = Fuel("汽油")
car = Car("大众")
car.refuel(fuel)  # 输出: 这车加的油是汽油

在这个例子中,Car对象依赖于Fuel对象,但这种依赖是临时的,仅在refuel方法调用期间存在。

(六)总结

Python中的对象关联通过以上几种基本关系来描述对象之间的交互方式:

  • 关联:描述一般的联系或依赖。
  • 聚合:整体-部分关系,但部分可以独立存在。
  • 组合:更强的整体-部分关系,部分依赖整体的生命周期。
  • 继承:子类继承父类,复用代码。
  • 依赖:一种临时的“使用”关系。

这些关联类型帮助开发者更好地组织和管理代码结构,使得软件设计更加模块化、可维护。


四、单继承

Python中的继承是面向对象编程中的一个核心概念,用于表示类之间的层次关系。继承允许一个类继承另一个类的属性和方法,从而实现代码的重用、扩展和多态性。

(一)基本概念

  • 父类(基类):被继承的类,提供基础属性和方法。
  • 子类(派生类):继承父类的类,可以复用父类的属性和方法,也可以添加新的属性和方法,或覆盖(重写)父类的方法。

(二)语法

继承通过定义一个类时在括号内指定父类来实现,语法如下:

class ParentClass:
    # 父类的属性和方法
    pass

class ChildClass(ParentClass):
    # 子类的属性和方法
    pass

示例:

class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name} 正在叫"

class Dog(Animal):  # Dog 类继承自 Animal 类
    def speak(self):
        return f"{self.name} 叫"

# 使用继承创建对象
dog = Dog("旺财")
print(dog.speak())  # 输出:旺财 叫

在这个例子中,Dog 类继承了 Animal 类的属性和方法。Dog 类重写了 speak() 方法,因此调用 Dog 类的 speak() 方法时,会返回 "叫" 而不是父类中的 "正在叫"。


五、多继承

多重继承是指一个子类可以继承多个父类。它允许子类从多个基类中获取属性和方法,从而实现更加灵活和强大的功能。然而,多重继承也会带来一些复杂性,尤其是在处理同名方法和属性时。Python 使用方法解析顺序(MRO)来决定调用的顺序。

(一)基本实例

class Base1:
    def method(self):
        print("父类1方法")

class Base2:
    def method(self):
        print("父类2方法")

class Derived(Base1, Base2):
    pass

d = Derived()
d.method()  # 输出:父类1方法

在这个例子中,Derived 类继承了 Base1Base2。当调用 method() 时,Derived 类首先使用 Base1method(),因为 Base1 在继承列表中排在 Base2 之前。

(二)方法解析顺序(MRO)

Python 使用方法解析顺序(MRO)来确定在多重继承情况下调用方法的顺序。可以使用 ClassName.mro()ClassName.__mro__ 查看 MRO。

示例:

class A:
    def process(self):
        print("A流程")

class B(A):
    def process(self):
        print("B流程")

class C(A):
    def process(self):
        print("C流程")

class D(B, C):
    pass

d = D()
d.process()  # 输出:B流程
print(D.mro())  # 输出:[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

该例子中,D 类的 MRO 是 D -> B -> C -> A -> object,因此调用 process() 方法时,会首先调用 B 类中的方法。

(三)使用场景

  • 类的扩展: 允许类从多个基础类中获取功能,并进行扩展。
  • 接口实现: 可以用多继承实现多个接口,尤其是在类似 Java 的接口设计中。

(四)注意事项

  • 设计复杂性: 多重继承可能导致复杂的继承图谱,容易使代码难以理解和维护。
  • 钻石继承问题: 如果两个基类有共同的父类,可能会导致方法重复调用的复杂情况。Python 的 MRO 处理了这个问题,通过 C3 线性化算法来确保一致性。

六、重写

        重写指的是子类重新定义从父类继承的方法。这使得子类可以提供特定的实现,而不是使用父类的默认实现。重写是面向对象编程中的一个核心概念,它允许你在继承的基础上扩展或修改行为。 当子类继承父类的方法时,它可以重写这些方法,以便提供不同的实现。子类中定义的方法将替代父类中的方法,而在子类实例上调用该方法时将使用新的实现。

示例:

假设我们有一个父类 Animal 和一个子类 Dog,其中 Dog 重写了 Animal 类的 speak 方法:

class Animal:
    def speak(self):
        return "动物叫"

class Dog(Animal):
    def speak(self):
        return "狗吠"

# 使用继承和重写创建对象
animal = Animal()
dog = Dog()

print(animal.speak())  # 输出:动物叫
print(dog.speak())     # 输出:狗吠

说明:Dog 类重写了 Animal 类的 speak 方法,因此当调用 dog.speak() 时,返回的是 "狗叫",而不是父类的实现。

(一)注意

  • 方法签名: 子类重写方法时,确保方法签名与父类一致,即方法名称和参数列表应相同,否则可能导致方法调用失败或行为不一致。
  • 调用父类方法: 在需要保留父类原有功能的情况下,使用 super() 调用父类的方法,确保子类扩展而不完全替代父类功能。
  • 保持一致性: 重写方法时,确保新实现与父类的接口一致,以避免引入错误或不一致的行为。

七、super函数

super() 函数用于调用父类的方法。它是面向对象编程中的一个重要工具,尤其在涉及继承和方法重写时,可以帮助你在子类中调用父类的实现。使用 super() 函数可以确保子类在扩展父类功能的同时,还能够利用父类已经实现的功能。

示例:

class Parent:
    def method(self):
        print("父类方法")

class Child(Parent):
    def method(self):
        super().method()  # 调用父类的 method 方法
        print("子类方法")

child = Child()
child.method()
# 输出:
# 父类方法
# 子类方法

该示例中,Child 类重写了 method 方法,并通过 super().method() 调用了 Parent 类中的 method 方法。

(一)语法

super() 函数通常用在方法内部,不需要传递任何参数。Python 会自动确定要调用的父类。

示例:

super().method()

在 Python 2.x 中,super() 的用法略有不同,通常需要传递当前类和实例作为参数:

super(Child, self).method()

(二)构造函数中的super

super在类的构造函数中非常有用,可以确保父类的构造函数被正确调用,从而初始化继承来的属性。

示例:

class Parent:
    def __init__(self, name):
        self.name = name

class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)  # 调用父类的构造函数
        self.age = age

child = Child("杰克", 30)
print(child.name)  # 输出:杰克
print(child.age)   # 输出:30

说明:Child 类的构造函数调用了 Parent 类的构造函数,以确保 name 属性被正确初始化。

(三)多重继承中的super

在多重继承中,super变得更复杂,但也更强大。它帮助确保方法调用按照正确的顺序执行,遵循方法解析顺序(MRO)。

示例:

class A:
    def method(self):
        print("A方法")

class B(A):
    def method(self):
        print("B方法")
        super().method()

class C(A):
    def method(self):
        print("C方法")
        super().method()

class D(B, C):
    def method(self):
        print("D方法")
        super().method()

d = D()
d.method()
# 输出:
# D方法
# B方法
# C方法
# A方法

在这个例子中,D 类的 method 方法通过 super() 调用 BC 类的 method 方法,最终调用 A 类的方法。super() 按照 MRO 确保了正确的调用顺序。

(四)总结

  • super() 函数用于调用父类的方法,确保在子类中扩展或重写方法时能够利用父类的实现。
  • 在构造函数中使用 super() 可以确保父类的构造函数被调用,从而初始化继承来的属性。
  • 在多重继承中,super() 遵循 MRO,确保方法调用按照正确的顺序进行。
  • 使用 super() 时需要注意方法的存在性以及继承结构的复杂性。

八、总结

该文章中更进一步了解了面向对象,内容有继承、重写和super等,从这些知识点可以了解到python的语法,同时也能体会到python的特殊之处,下篇文章会继续更新一步了解python面向对象,让我们拭目以待吧!

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值