目录
前言
上篇文章主要了解面向对象中的类和对象、实例属性和实例方法等,接下来继续深入了解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
类继承了 Base1
和 Base2
。当调用 method()
时,Derived
类首先使用 Base1
的 method()
,因为 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()
调用 B
和 C
类的 method
方法,最终调用 A
类的方法。super()
按照 MRO 确保了正确的调用顺序。
(四)总结
super()
函数用于调用父类的方法,确保在子类中扩展或重写方法时能够利用父类的实现。- 在构造函数中使用
super()
可以确保父类的构造函数被调用,从而初始化继承来的属性。 - 在多重继承中,
super()
遵循 MRO,确保方法调用按照正确的顺序进行。 - 使用
super()
时需要注意方法的存在性以及继承结构的复杂性。
八、总结
该文章中更进一步了解了面向对象,内容有继承、重写和super等,从这些知识点可以了解到python的语法,同时也能体会到python的特殊之处,下篇文章会继续更新一步了解python面向对象,让我们拭目以待吧!