5.1 继承机制及其使用

继承是面向对象的三大特征之一,也是实现软件复用的重要手段。Python 的继承是多继承机制,即一个子类可以同时有多个直接父类。

Python 子类继承父类的语法是在定义子类时,将多个父类放在子类之后的圆括号里。语法格式如下:

In [2]:
# class Subclass (SuperClass1, SuperClass2, ...):
#     #类定义部分

从上面的语法格式来看,定义子类的语法非常简单,只需在原来的类定义后增加圆括号,井在圆括号中添加多个父类,即可表明该子类继承了这些父类。

如果在定义一个 Python 类时并未显式指定这个类的直接父类,则这个类默认继承 object 类。因此,object 类是所有类的父类,要么是其直接父类,要么是其间接父类。

实现继承的类被称为子类,被继承的类被称为父类(也被称为基类、超类)。父类和子类的关系,是一般和特殊的关系。例如水果和苹果的关系,苹果继承了水果,苹果是水果的子类,则苹果是一种特殊的水果。

由于子类是一种特殊的父类,因此父类包含的范围总比子类包含的范围要大,所以可以认为父类是大类,而子类是小类。

从实际意义上看,子类是对父类的扩展,子类是一种特殊的父类。从这个意义上看,使用继承来描述子类和父类的关系是错误的,用扩展更恰当。因此,这样的说法更加准确:Apple 类扩展了 Fruit 类。

下面程序示范了子类继承父类的特点。下面是 Fruit 类的代码:

In [3]:
class Fruit:def info(self):print("我是一个水果!重%g克" % self.weight)
class Food:def taste(self):print("不同食物的口感不同")
# 定义Apple类,继承了Fruit和Food类
class Apple(Fruit, Food):pass

In [4]:
# 创建Apple对象
a = Apple()
a.weight = 5.6

In [5]:
# 调用Apple对象的info()方法
a.info()

我是一个水果!重5.6克

In [6]:
# 调用Apple对象的taste()方法
a.taste()

不同食物的口感不同

5.2 关于Python的多继承

大部分面向对象的编程语言(除了 C++)都只支持单继承,而不支持多继承,这是由于多继承不仅增加了编程的复杂度,而且很容易导致一些莫名的错误。

Python 虽然在语法上明确支持多继承,但通常推荐如果不是很有必要,则尽量不要使用多继承,而是使用单继承,这样可以保证编程思路更清晰,而且可以避免很多麻烦。

当一个子类有多个直接父类时,该子类会继承得到所有父类的方法,这一点在前面示例中己经做了示范。现在的问题是,如果多个父类中包含了同名的方法,此时会发生什么呢?此时排在前面的父类中的方法会“遮蔽”排在后面的父类中的同名方法。例如如下代码:

In [13]:
class Item:def info (self):print("Item中方法:", '这是一个商品')
class Product:def info (self):print("Product中方法:", '这是一个工业产品')

In [14]:
class Mouse(Item, Product): # ①pass
m = Mouse()
m.info()

Item中方法: 这是一个商品

让 Mouse 继承了 Item 类和 Product 类,由于 Item 排在前面,因此 Item 中定义的方法优先级更好,Python 会优先到 Item 父类中搜寻方法,一旦在 Item 父类中搜寻到目标方法,Python 就不会继续向下搜寻了。

上面程序中 Item 和 Product 两个父类中都包含了 info() 方法,当 Mouse 子类对象调用 info() 方法时(子类中没有定义 info() 方法,因此 Python 会从父类中寻找 info() 方法),此时优先使用第一个父类 item 中的 info() 方法。

In [16]:
class Mouse(Product, Item):  # ①passm = Mouse()
m.info()

Product中方法: 这是一个工业产品

5.3 父类方法重写

子类扩展了父类,子类是一种特殊的父类。大部分时候,子类总是以父类为基础,额外增加新的方法。但有一种情况例外,子类需要重写父类的方法。

例如,鸟类都包含了飞翔方法,其中驼鸟是一种特殊的鸟类,因此驼鸟应该是鸟的子类,它也将从鸟类获得飞翔方法,但这个飞翔方法明显不适合驼鸟,为此,驼鸟需要重写鸟类的方法。

如下程序示范了子类重写父类的方法:

In [17]:
class Bird:# Bird类的fly()方法def fly(self):print("我在天空里自由自在地飞翔...")class Ostrich(Bird):# 重写Bird类的fly()方法def fly(self):print("我只能在地上奔跑...")

In [18]:
# 创建Ostrich对象
os = Ostrich()
# 执行Ostrich对象的fly()方法,将输出"我只能在地上奔跑..."
os.fly()

我只能在地上奔跑...

这种子类包含与父类同名的方法的现象被称为方法重写(Override),也被称为方法覆盖。可以说子类重写了父类的方法,也可以说子类覆盖了父类的方法。

5.4 使用未绑定方法调用被重写的方法

如果在子类中调用重写之后的方法,Python 总是会执行子类重写的方法,不会执行父类中被重写的方法。如果需要在子类中调用父类中被重写的实例方法,那该怎么办呢?

别忘了,Python 类相当于类空间,因此 Python 类中的方法本质上相当于类空间内的函数。所以,即使是实例方法,Python 也允许通过类名调用。区别在于:在通过类名调用实例方法时,Python 不会为实例方法的第一个参数 self 自动绑定参数值,而是需要程序显式绑定第一个参数 self。这种机制被称为未绑定方法。

通过使用未绑定方法即可在子类中再次调用父类中被重写的方法。例如如下程序:

In [19]:
class BaseClass:def foo (self):print('父类中定义的foo方法')class SubClass(BaseClass):# 重写父类的foo方法def foo (self):print('子类重写父类中的foo方法')def bar (self):print('执行bar方法')# 直接执行foo方法,将会调用子类重写之后的foo()方法self.foo()# 使用类名调用实例方法(未绑定方法)调用父类被重写的方法BaseClass.foo(self)

In [20]:
sc = SubClass()
sc.bar()

执行bar方法
子类重写父类中的foo方法
父类中定义的foo方法

上面程序中 SubClass 继承了 BaseClass 类,并重写了父类的 foo() 方法。接下来程序在 SubClass 类中定义了 bar() 方法,该方法的第 11 行代码直接通过 self 调用 foo() 方法,Python 将会执行子类重写之后的 foo() 方法;第 13 行代码通过未绑定方法显式调用 BaseClass 中的 foo 实例方法,井显式为第一个参数 self 绑定参数值,这就实现了调用父类中被重写的方法。

5.5 super函数:调用父类的构造方法

Python 的子类也会继承得到父类的构造方法,如果子类有多个直接父类,那么排在前面的父类的构造方法会被优先使用。例如如下代码:

In [48]:
class Employee :def __init__ (self, salary):self.salary = salarydef work (self):print('普通员工正在写代码,工资是:', self.salary)class Customer:def __init__ (self, favorite, address):self.favorite = favoriteself.address = addressdef info (self):print('我是一个顾客,我的爱好是: %s,地址是%s' % (self.favorite, self.address))

In [49]:
# Manager继承了Employee、Customer
class Manager (Employee, Customer):passm = Manager(25000)
m.work()  #①

普通员工正在写代码,工资是: 25000

In [50]:
m.info()

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-50-00c123459052> in <module>
----> 1m.info()<ipython-input-48-4616f2c22e21> in info(self)
     10         self.address = address
     11     def info (self):
---> 12print('我是一个顾客,我的爱好是: %s,地址是%s' % (self.favorite, self.address))AttributeError: 'Manager' object has no attribute 'favorite'

In [51]:
class Manager (Customer, Employee):pass

In [52]:
m = Manager('IT产品', '广州')
m.info()  #②

我是一个顾客,我的爱好是: IT产品,地址是广州

In [53]:
class Manager (Employee, Customer):passm = Manager('IT产品', '广州')
m.info()

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-53-8db72262183d> in <module>
      2     pass
      3
----> 4m = Manager('IT产品', '广州')
      5 m.info()TypeError: __init__() takes 2 positional arguments but 3 were given

从上面的实验可以看出多继承的子类在构造方法方面的问题,为了让 Manager 能同时初始化两个父类中的实例变量,Manager 应该定义自己的构造方法,即重写父类的构造方法。Python 要求:如果子类重写了父类的构造方法,那么子类的构造方法必须调用父类的构造方法。

子类的构造方法调用父类的构造方法有两种方式:
1)使用未绑定方法,这种方式很容易理解。因为构造方法也是实例方法,当然可以通过这种方式来调用。
2)使用 super() 函数调用父类的构造方法。

super 其实是一个类,因此调用 super() 的本质就是调用 super 类的构造方法来创建 super 对象。

使用 super() 构造方法最常用的做法就是不传入任何参数(这种做法与 super(type, obj) 的效果相同),然后通过 super 对象的方法既可调用父类的实例方法,也可调用父类的类方法。在调用父类的实例方法时,程序会完成第一个参数 self 的自动绑定,如上帮助信息中 ① 号信息所示。在调用类方法时,程序会完成第一个参数 cls 的自动绑定,如上面帮助信息中 ② 号信息所示。

掌握了 super() 函数的用法之后,接下来可以将上面程序改为如下形式:

In [84]:
# Manager继承了Employee、Customer
class Manager(Employee, Customer):# 重写父类的构造方法def __init__(self, salary, favorite, address):print('--Manager的构造方法--')# # 通过super()函数调用父类的构造方法,好像会报错# super.__init__(salary)# 与上一行代码的效果相同Employee.__init__(self,salary)# 使用未绑定方法调用父类的构造方法Customer.__init__(self,favorite, address)

In [85]:
# 创建Manager对象
m = Manager(25000,'IT产品', '广州')

--Manager的构造方法--

In [86]:
m.work()  #①

普通员工正在写代码,工资是: 25000

In [87]:
m.info()  #②

我是一个顾客,我的爱好是: IT产品,地址是广州

6.1 slots:限制类实例动态添加属性和方法

前面介绍了为对象动态添加方法,但是所添加的方法只是对当前对象有效,如果希望为所有实例都添加方法,则可通过为类添加方法来实现。例如如下代码:

In [89]:
class Cat:def __init__(self, name):self.name = namedef walk_func(self):print('%s慢慢地走过一片草地' % self.name)

In [90]:
d1 = Cat('Garfield')
d2 = Cat('Kitty')

In [91]:
# 为Cat动态添加walk()方法,该方法的第一个参数会自动绑定
Cat.walk = walk_func  # ①

In [92]:
# d1、d2调用walk()方法
d1.walk()

Garfield慢慢地走过一片草地

In [93]:
d2.walk()

Kitty慢慢地走过一片草地

程序中 ① 号代码为 Cat 动态添加了 walk() 方法,为类动态添加方法时不需要使用 MethodType 进行包装,该函数的第一个参数会自动绑定。为 Cat 动态添加 walk() 方法之后,Cat 类的两个实例 d1、d2 都具有了 walk() 方法,因此上面程序中最后两行 d1、d2 都可调用 walk() 方法。

Python 的这种动态性固然有其优势,但也给程序带来了一定的隐患,即程序定义好的类,完全有可能在后面被其他程序修改,这就带来了一些不确定性。如果程序要限制为某个类动态添加属性和方法,则可通过 slots 属性来指定。

slots 属性的值是一个元组,该元组的所有元素列出了该类的实例允许动态添加的所有属性名和方法名(对于 Python 而言,方法相当于属性值为函数的属性)。例如如下程序:

In [95]:
class Dog:__slots__ = ('walk', 'age', 'name')def __init__(self, name):self.name = namedef test(self):print('预先定义的test方法')

In [96]:
from types import MethodTyped = Dog('Snoopy')
# 只允许动态为实例添加walk、age、name这3个属性或方法
d.walk = MethodType(lambda self: print('%s正在慢慢地走' % self.name), d)

In [98]:
d.age = 5
d.walk()

Snoopy正在慢慢地走

In [99]:
d.foo = 30 # AttributeError

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-99-b08ea83fbb60> in <module>
----> 1d.foo = 30 # AttributeErrorAttributeError: 'Dog' object has no attribute 'foo'

上面程序中第 2 行代码定义了 slots=('walk','age', 'name'),这意味着程序只允许为 Dog 实例动态添加 walk、age、name 这三个属性或方法。因此上面程序中第 10 行、第 11 行代码为 Dog 对象动态添加 walk() 方法和 age 属性都是允许的。

但如果程序尝试为 Dog 对象添加其他额外属性,程序就会引发 AttributeError 错误,如上面最后一行代码所示。

需要说明的是,slots 属性并不限制通过类来动态添加属性或方法,因此下面代码是合法的:

In [100]:
# __slots__属性并不限制通过类来动态添加方法
Dog.bar = lambda self: print('abc') # AttributeError
d.bar()

abc

In [101]:
Dog.foo = 30
d.foo

Out[101]:
30

下面代码通过 Dog 类动态添加了 bar() 方法,这样 Dog 对象就可以调用该 bar() 方法了。

此外,slots 属性指定的限制只对当前类的实例起作用,对该类派生出来的子类是不起作用的。例如如下代码:

In [105]:
class GunDog(Dog):def __init__(self, name):Dog.__init__(self,name)pass

In [106]:
gd = GunDog('Puppy')

In [107]:
# 完全可以为Gundog实例动态添加属性
gd.speed = 99
print(gd.speed)

99

正如从上面代码所看到的,Dog 的子类 GunDog 的实例完全可以动态添加 speed 属性,这说明 slots 属性指定的限制只对当前类起作用。

如果要限制子类的实例动态添加属性和方法,则需要在子类中也定义 slots 属性,这样,子类的实例允许动态添加属性和方法就是子类的 slots 元组加上父类的 slots 元组的和。

6.2 type函数:动态创建类

前面己经提到使用 type() 函数可以查看变量的类型,但如果想使用 type() 直接查看某个类的类型呢?看如下程序:

In [108]:
class Role:pass
r = Role()
# 查看变量r的类型
print(type(r)) # <class '__main__.Role'>
# 查看Role类本身的类型
print(type(Role)) # <class 'type'>

<class '__main__.Role'>
<class 'type'>

从上面的输出结果可以看到,Role 类本身的类型是 type。这句话有点拗口,怎样理解 Role 类的类型是 type?

从 Python 解释器的角度来看,当程序使用 class 定义 Role 类时,也可理解为定义了一个特殊的对象(type 类的对象),并将该对象赋值给 Role 变量。因此,程序使用 class 定义的所有类都是 type 类的实例。

实际上 Python 完全允许使用 type() 函数(相当于 type 类的构造器函数)来创建 type 对象,又由于 type 类的实例就是类,因此 Python 可以使用 type() 函数来动态创建类。例如如下程序:

In [109]:
def fn(self):print('fn函数')
# 使用type()定义Dog类
Dog = type('Dog', (object,), dict(walk=fn, age=6))

In [110]:
# 创建Dog对象
d = Dog()
# 分别查看d、Dog的类型
print(type(d))
print(type(Dog))

<class '__main__.Dog'>
<class 'type'>

In [111]:
d.walk()
print(Dog.age)

fn函数
6

上面第 4 行代码使用 type() 定义了一个 Dog 类。在使用 type() 定义类时可指定三个参数:
参数一:创建的类名。
参数二:该类继承的父类集合。由于 Python 支持多继承,因此此处使用元组指定它的多个父类。即使实际只有一个父类,也需要使用元组语法(必须要多一个逗号)。
参数三:该字典对象为该类绑定的类变量和方法。其中字典的 key 就是类变量或方法名,如果字典的 value 是普通值,那就代表类变量;如果字典的 value 是函数,则代表方法。

由此可见,第 4 行代码定义了一个 Dog 类,该类继承了 object 类,还为该类定义了一个 walk() 方法和一个 age 类变量。

从上面的输出结果可以看出,使用 type() 函数定义的类与直接使用 class 定义的类井没有任何区别。事实上,Python 解释器在执行使用 class 定义的类时,其实依然是使用 type() 函数来创建类的。因此,无论通过哪种方式定义类,程序最终都是创建一个 type 的实例。

6.3 metaclass详解

如果希望创建某一批类全部具有某种特征,则可通过 metaclass 来实现。使用 metaclass 可以在创建类时动态修改类定义。

为了使用 metaclass 动态修改类定义,程序需要先定义 metaclass, metaclass 应该继承 type 类,并重写 new() 方法。

下面程序定义了一个 metaclass 类:

In [112]:
# 定义ItemMetaClass,继承type
class ItemMetaClass(type):# cls代表动态修改的类# name代表动态修改的类名# bases代表被动态修改的类的所有父类# attr代表被动态修改的类的所有属性、方法组成的字典def __new__(cls, name, bases, attrs):# 动态为该类添加一个cal_price方法attrs['cal_price'] = lambda self: self.price * self.discountreturn type.__new__(cls, name, bases, attrs)

上面程序定义了一个 ItemMetaClass 类,该类继承了 type 类,并重写了 new 方法,在重写该方法时为目标类动态添加了一个 cal_price 方法。

metaclass 类的 new 方法的作用是:当程序使用 class 定义新类时,如果指定了 metaclass,那么 metaclass 的 new 方法就会被自动执行。

例如,如下程序使用 metaclass 定义了两个类:

In [113]:
# 定义Book类
class Book(metaclass=ItemMetaClass):__slots__ = ('name', 'price', '_discount')def __init__(self, name, price):self.name = nameself.price = price@propertydef discount(self):return self._discount@discount.setterdef discount(self, discount):self._discount = discount
# 定义cellPhone类
class CellPhone(metaclass=ItemMetaClass):__slots__ = ('price', '_discount' )def __init__(self, price):self.price = price@propertydef discount(self):return self._discount@discount.setterdef discount(self, discount):self._discount = discount

上面程序定义了 Book 和 CellPhone 两个类,在定义这两个类时都指定了 metaclass 信息,因此当 Python 解释器在创建这两个类时,ItemMetaClass 的 new 方法就会被调用,用于修改这两个类。

ItemMetaClass 类的 new 方法会为目标类动态添加 cal_price 方法,因此,虽然在定义 Book、CellPhone 类时没有定义 cal_price() 方法,但这两个类依然有 cal_price() 方法。如下程序测试了 Book、CellPhone 两个类的 cal_price() 方法:

In [114]:
b = Book("Python基础教程", 89)
b.discount = 0.76
# 创建Book对象的cal_price()方法
print(b.cal_price())

67.64

In [115]:
cp = CellPhone(2399)
cp.discount = 0.85
# 创建CellPhone对象的cal_price()方法
print(cp.cal_price())

2039.1499999999999

从上面的运行结果来看,通过使用 metaclass 可以动态修改程序中的一批类,对它们集中进行某种修改。这个功能在开发一些基础性框架时非常有用,程序可以通过使用 metaclass 为某一批需要具有通用功能的类添加方法。

转载于:https://www.cnblogs.com/xinmomoyan/p/10809032.html

03python面向对象编程5相关推荐

  1. 【面向对象编程】(4) 类的继承,重构父类中的方法

    各位同学好,今天和大家分享一下面向对象编程中,类的三大特征之继承.主要介绍:子类继承父类的基本方法:重写父类的类方法:重构父类的初始化方法:super() 方法.本节主要是单继承,多继承在下一节中介绍 ...

  2. 【面向对象编程】(3) 类之间的交互,依赖关系,关联关系

    各位同学好,今天和大家分享一下面向对象编程中,类之间的交互,类之间的依赖关系和关联关系.有不明白的可见前一章节:https://blog.csdn.net/dgvv4/article/details/ ...

  3. 【面向对象编程】(1) 类实例化的基本方法

    各位同学好,本章节和大家分享一下面向对象编程的一些方法,通过一些案例带大家由浅入深掌握面向对象的编程. 1. 最基本的类实例化 创建类的方法是 class 变量名: ,实例化方法是 类名() ,分配属 ...

  4. C#编程概念系列(一):面向对象编程

    系列文章索引目录:http://www.cnblogs.com/loner/archive/2013/05/09/3068211.html 引子: 面向对象编程:这个在当下已不是什么时髦的概念,但通过 ...

  5. JavaScript面向对象编程

    自从有了Ajax这个概念,JavaScript作为Ajax的利器,其作用一路飙升.JavaScript最基本的使用,以及语法.浏览器对象等等东东在这里就不累赘了.把主要篇幅放在如何实现JavaScri ...

  6. python面向对象的优点_Python面向对象编程——总结面向对象的优点

    Python面向对象编程--总结面向对象的优点 一.从代码级别看面向对象 1.在没有学习类这个概念时,数据与功能是分离的 def exc1(host,port,db,charset): conn=co ...

  7. 转载知乎上的一篇:“ 面向对象编程的弊端是什么?”

    2019独角兽企业重金招聘Python工程师标准>>> 弊端是,没有人还记得面向对象原本要解决的问题是什么. 1.面向对象原本要解决什么(或者说有什么优良特性) 似乎很简单,但实际又 ...

  8. c语言面向对象编程中的类_C ++中的面向对象编程

    c语言面向对象编程中的类 Object oriented programming, OOP for short, aims to implement real world entities like ...

  9. ruby 新建对象_Ruby面向对象编程的简介

    ruby 新建对象 by Saul Costa 由Saul Costa Object-oriented programming (OOP) is a programming paradigm orga ...

最新文章

  1. 导弹拦截(pascal)
  2. dotNet中,取得指定日期所在月份的最后一天
  3. 快逸报表参数查询前报表不显示
  4. 新增数据时遇到特殊字符
  5. 修改Linux中的用户名
  6. jquery中post,get,ajax请求相关
  7. 【转】异步编程:.NET 4.5 基于任务的异步编程模型(TAP)
  8. 传百度无人车计划分拆,百度回复:不实信息,目前未有分拆计划
  9. Java 200+ 面试题补充③ Dubbo 模块
  10. 《转》TCP的三次握手与四次挥手(详解+动图)
  11. 基于java的邮件服务器以及webmail的搭建
  12. python提取百度首页链接_python获取百度热榜链接的实例方法
  13. vue引入jsmind(右键菜单)
  14. Matlab编程风格指南--Richard Johnson(命名规则,文件与结构,基本语句,布局,注释与文档)
  15. 离散数学学习笔记——集合的符号表示
  16. 数据库系统概论-数据库恢复技术
  17. 【概率论与数理统计】1.5 独立性
  18. 20条职业发展建议,送给拒绝原地踏步的你
  19. 睡眠是锁定计算机怎么设置密码,笔记本电脑如何设置睡眠唤醒密码?
  20. katago安装使用

热门文章

  1. 利用sdkman安装kotlin和java环境
  2. python学习第十六天 --继承进阶篇
  3. 【MySQL】MySQL for Mac 环境变量的配置
  4. Linux命令行下载文件百度云盘
  5. [ActiveRecord]之 CRUD
  6. 【系统架构】大规模的C++项目代码层次结构
  7. hbase shell相关命令
  8. JAVA b2b2c多用户商城系统源码-服务发现服务端EurekaServer微服务
  9. [iOS] 使用 blockable NSTimer 避免内存泄漏
  10. Git-如何回滚代码 revert和reset