python 面向对象编程--组合

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

==组合:== 就是将不同的类混合并加入到其他类中,来 增加类的功能 / 提高代码的重用性 / 易于维护(对类的修改会直接反应到整个应用中)

class School:
    def __init__(self,name,addr):
        self.name=name
        self.addr=addr

    def zhao_sheng(self):
        print('%s 正在招生' %self.name)

class Course:
    def __init__(self,name,price,period,school):  #school为另一个类
        self.name=name
        self.price=price
        self.period=period
        self.school=school



s1=School('oldboy','北京')
c1=Course('linux',10,'1h',s1)

print(c1.__dict__)
print(c1.school.name)
print(s1)
输出:
{'name': 'linux', 'price': 10, 'period': '1h', 'school': <__main__.School object at 0x03415D50>}
oldboy
<__main__.School object at 0x03415D50>

组合--选课案例

class School:
    def __init__(self,name,addr):
        self.name=name
        self.addr=addr


    def zhao_sheng(self):
        print('%s 正在招生' %self.name)

class Course:
    def __init__(self,name,price,period,school):
        self.name=name
        self.price=price
        self.period=period
        self.school=school



s1=School('oldboy','北京')
s2=School('oldboy','南京')
s3=School('oldboy','东京')

# c1=Course('linux',10,'1h','oldboy 北京')
# c1=Course('linux',10,'1h',s1)

msg='''
1 老男孩 北京校区
2 老男孩 南京校区
3 老男孩 东京校区
'''
while True:
    print(msg)
    menu={
        '1':s1,
        '2':s2,
        '3':s3
    }
    choice=input('选择学校>>: ')
    school_obj=menu[choice]
    name=input('课程名>>: ')
    price=input('课程费用>>: ')
    period=input('课程周期>>: ')
    new_course=Course(name,price,period,school_obj)
    print('课程【%s】属于【%s】学校' %(new_course.name,new_course.school.name))
运行:
1 老男孩 北京校区
2 老男孩 南京校区
3 老男孩 东京校区

选择学校>>: 1
课程名>>: lunix
课程费用>>: 100
课程周期>>: 10
课程【lunix】属于【oldboy】学校

派生

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

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

语法

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 生成的字典按照字母的顺序排序。

参考

  1. Python 进阶OOP 面向对象编程组合与继承
Update time: 2020-05-25

results matching ""

    No results matching ""