Python 进阶_OOP 面向对象编程_组合与继承

简介: #目录前言组合派生通过继承来覆盖重载方法最常用的重载场景实例方法的重载从标准类中派生类方法的重载前言我们定义一个类是希望能够把类当成模块来使用,并把类嵌入到我们的应用代码中,与其他的数据类型、逻辑执行流结合使用。
#目录

前言

我们定义一个类是希望能够把类当成模块来使用,并把类嵌入到我们的应用代码中,与其他的数据类型、逻辑执行流结合使用。一般来说我们可以使用两种方法在代码中利用类,那就是组合和派生。

组合

组合: 就是将不同的类混合并加入到其他类中,来 增加类的功能 / 提高代码的重用性 / 易于维护(对类的修改会直接反应到整个应用中) 。我们可以实例化一个更大的对象,同时还可以添加一些实例属性和实例方法的实现来丰富实例对象的功能。

In [1]: class NewAddrBookEntry(object):               # 定义一个类
   ...:     """New address book entry class."""
   ...:     def __init__(self, name, phone):          # 定义构造器
   ...:         self.name = Name(name)                # 创建 Name 对象
   ...:         self.phone = Phone(phone)             # 创建 Phone 对象
   ...:         print "Created the instance for ", self.name

上述的 class NewAddrBookEntry 有它自身和其他类 Name/Phone 组合而成, 可以实现一些更为复杂的功能。

派生

当我们希望较小的类是较大的类的组件时,组合是一个很好的处理方式。但当我们希望 相同的类却具有一些不同的功能时 派生就是最好的处理方式。这也是面向对象编程最强大的功能之一 —— 使用一个已经定义好的类,扩展它的功能或者对其进行修改生成一个新的类,但却不会对原来的类造成影响。

子类会从基类继承他们的任何属性(数据和方法),这种派生是可以继承多代的,且可以同时继承多个基类。

语法

class SubClassName(ParentClass1[, ParentClass2, ...]):
    class_suite

EXAMPLE

In [8]: class Parent(object):
   ...:    def __init__(self):
   ...:        print "created an instance of: ", self.__class__.__name__
   ...:
   ...:

In [9]: class Child(Parent):
   ...:     pass
   ...:

In [10]: c = Child()
created an instance of:  Child

In [14]: p = Parent()
created an instance of:  Parent

类 Child 的实例对象 c 并没有定义 __init__() 构造器,但仍然执行了 print 语句,这是因为 Child 从 Parent 继承了其构造器。

通过继承来覆盖(重载)方法

当我们派生一个子类,但同时希望相同的方法能在子类实现不同的功能,这时我们需要使用方法的 重载。使子类的方法能够覆盖父类的同名方法。

In [17]: class Parent(object):
    ...:     def func(self):
    ...:         print "This is Parent."
    ...:

In [18]: class Child(Parent):
    ...:     def func(self):
    ...:         print "This is Child."
    ...:
    ...:

In [19]: p = Parent()

In [20]: p.func()
This is Parent.

In [21]: c = Child()

In [22]: c.func()
This is Child.

这里子类 Child 重载了父类 Parent 的 func() 方法,实现了不同的功能。

但仍然有些场合需要我们即能使用子类的重载方法的同时,也要求我们重现父类方法的功能。那么我们可以调用那个被我们覆盖的父类方法吗?
答案是肯定的。

In [23]: Parent.func(c)
This is Parent.

我们可以通过 ParentClassName.functionName(object) 的方式来重现父类所被覆盖的方法。当然我们还有其他的方式可以实现这个效果,EG. 在子类的重载方法里显式的调用父类的同名方法:

In [24]: class Child(Parent):
    ...:     def func(self):
    ...:         print "This is Child."
    ...:         Parent.func(self)

In [25]: c = Child()

In [26]: c.func()
This is Child.
This is Parent.

两种方式本质上是相同的,都是通过 父类名结合句点表达式 来实现对父类方法的调用,而且需要注意的是,在调用的时候必须传递一个实例对象给 func(),否则会触发参数不匹配的语法错误。

还有一个更好的实现方式就是子类使用 super() 内置函数:

In [27]: class Child(Parent):
    ...:     def func(self):
    ...:         print "This is Child."
    ...:         super(Child, self).func()
    ...:

In [28]: c = Child()

In [29]: c.func()
This is Child.
This is Parent.

super() 内置函数不仅能自动的找到父类方法,并且还是自动的为父类方法传入 self 参数来实现实例方法的绑定。

最常用的重载场景(实例方法的重载)

最常用的重载场景莫过于 重载父类的构造器 了。
在上述的例子可以看出,当我们在子类中没有重载构造器的时候,会自动的调用父类的构造器。这很明显是不符合我们的需求的,因为我们常常需要在子类中定义一些新的成员属性。但是问题是:当我们为了初始化子类中新的成员属性时,不可避免的需要重复的编写初始化从父类中继承而来的属性的代码。 这也不符合代码重用的原则,所以我们一般会采用 重载构造器(init()) + 重现父类构造器(super()) 的方式来解决这个问题。

In [32]: class Parent(object):
    ...:     def __init__(self, name, age):
    ...:         self.name = name
    ...:         self.age = age
    ...:         print "This is Parent."
    ...:

In [33]: class Child(Parent):
    ...:     def __init__(self, name, age, sex):         # 初始化子类实例对象的属性
    ...:         super(Child, self).__init__(name, age)  # 初始化父类实例对象的属性
    ...:         self.sex = sex
    ...:         print "This is Child."
    ...:

In [35]: p = Parent("fanguiju", "24")
This is Parent.

In [36]: c = Child("fanguiju", "24", "man")
This is Parent.
This is Child.

In [37]: c.name
Out[37]: 'fanguiju'

In [38]: c.age
Out[38]: '24'

In [39]: c.sex
Out[39]: 'man'

一般而言,我们会在子类的构造器中首先调用父类的构造器,当然这并不是强制的。只是为了我们能够在执行子类构造器的代码之前首先完成对父类属性的初始化,防止在调用从父类继承而来的属性时仍未初始化的问题出现。

使用 super() 内置函数的漂亮之处在于,我们不需要明确的给出父类的名字,交由解析器去自动的找到该子类的父类,并自动的传入 self 参数来完成绑定。这样能够让代码具有更高的灵活性,我们只需要改变子类的定义语句,就可以改变类的继承关系。

从标准类中派生(类方法的重载)

不可变数据类型的派生:定义一个精度为 2 的浮点数据类型
派生不可变标准类,经常需要重载类方法,而类方法的重载一般是重载 __new__(),也就是所谓的 真·构造器

class RoundFloat(float):
    def __new__(cls, value):
        return float.__new__(cls, round(value, 2))  # 将类对象 float 传入参数 cls

In [44]: RoundFloat(1.1111111)
Out[44]: 1.11

真·构造器会自动的将类对象 RoundFloat 传入 cls 参数,类似于构造器__init__(self)

可变数据类型的派生:定义一个有序的字典数据类型
可变数据类型的派生可能不需要使用 构造器 或者 真·构造器 也能够实现。

class SortedDict(dict):
    def keys(self):
        return sorted(super(SortedDict, self).keys())

In [47]: for x in SortedDict((("name", "fanguiju"), ("age", 24), ("sex", "man"))):
    ...:     print x
    ...:
age
name
sex

通过 SortedDict 生成的字典按照字母的顺序排序。

需要注意的是: 在 Python 2.2 之后将类和类型合并了,所以所有的数据类型都是一个类,反之我们定义了一个类也相当于定义了一个新的类型。

相关文章
|
2月前
|
Java 程序员 C++
Python 面向对象详解!
本文详细介绍了Python中的面向对象编程(OOP),包括类、对象、继承、封装、多态和抽象等核心概念。通过具体示例,解释了如何使用类定义对象的属性和方法,以及如何通过继承实现代码重用。文章还探讨了封装和多态的重要性,并介绍了私有属性和抽象类的使用方法。最后,总结了OOP的四大支柱:封装、抽象、继承和多态,强调了这些概念在Python编程中的应用。适合Java程序员扩展Python编程知识。
79 2
|
5天前
|
算法 Python
Python多继承时子类如何调用指定父类
通过本文的介绍,希望您能够深入理解Python多继承时子类如何调用指定父类的方法,并在实际项目中灵活运用这些技巧,编写出高效且易维护的代码。
22 11
|
4天前
|
算法 Python
Python多继承时子类如何调用指定父类
通过本文的介绍,希望您能够深入理解Python多继承时子类如何调用指定父类的方法,并在实际项目中灵活运用这些技巧,编写出高效且易维护的代码。
23 1
|
14天前
|
关系型数据库 开发者 Python
Python编程中的面向对象设计原则####
在本文中,我们将探讨Python编程中的面向对象设计原则。面向对象编程(OOP)是一种通过使用“对象”和“类”的概念来组织代码的方法。我们将介绍SOLID原则,包括单一职责原则、开放/封闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则有助于提高代码的可读性、可维护性和可扩展性。 ####
|
2月前
|
Python
Python面向对象(2)
【10月更文挑战第14天】
Python面向对象(2)
|
2月前
|
设计模式 程序员 C语言
Python面向对象
【10月更文挑战第13天】
Python面向对象
|
2月前
|
Java C# Python
Python学习七:面向对象编程(中)
这篇文章是关于Python面向对象编程的中级教程,涵盖了析构函数、对象的三大特征(封装、继承、多态)、类属性与实例属性、以及类方法与静态方法的对比。
27 2
|
2月前
|
设计模式 安全 JavaScript
Python学习八:面向对象编程(下):异常、私有等
这篇文章详细介绍了Python面向对象编程中的私有属性、私有方法、异常处理及动态添加属性和方法等关键概念。
26 1
|
3月前
|
IDE Java 开发工具
Python类与面向对象
Python类与面向对象
|
3月前
|
存储 Java 程序员
30天拿下Python之面向对象编程
30天拿下Python之面向对象编程
21 3