python语言基础-2 面向对象-2.3 继承

声明:本内容非盈利性质,也不支持任何组织或个人将其用作盈利用途。本内容来源于参考书或网站,会尽量附上原文链接,并鼓励大家看原文。侵删。

2.3 继承

继承是类之间的一种重要关系之一。类之间的关联关系是通过设置类属性(一般是添加关联的id)关联的,这将在学习数据库时详细说到;类之间的聚合关系一般是自然关系的反映,是显而易见的,不需要在类层面做联系(可能在对象层面做联系);类之间的依赖关系,则直接以一种类型做另一种类型的成员。那么,继承关系要如何体现在类的代码上呢?这就是本节要研究的问题。

2.3.1 python中继承的特性

python是支持多继承的,这一点与C++相同,而与Java只支持单继承不同。

多继承的一个重要的问题是当出现“菱形继承”的问题时,是采用深度优先的继承策略还是采用广度优先的继承策略。python中继承时,分为经典类和新式类,经典类按深度优先查找,新式类按广度优先查找,但从python3开始全都为新式类了,所以都按广度优先查找。如下:
在这里插入图片描述

2.3.2 python中继承的实现

(1)继承的一般格式

python中的继承是直接将父类写在子类名称后面的括号中。一般我们会遇到以下几种情况的定义类的格式:

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def eat(self):
        pass
    
class Person():
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def eat(self):
        pass

class Student(Person):  #  如果多继承,括号中可以写多个父类,以逗号分隔
    def __init__(self, name, age, class):
        super.__init__(name, age)  # 继承属性的定义
        self.class = class  # 自有属性的定义

    def study(self, course):  # 定义自有方法
        pass
        
    def eat(self):  # 子类中出现与父类中相同的方法时,用子类中的方法
        super().eat  # 重写时可以依靠父类的代码
        pass

在python2中Animal类和Person类没有继承或没有显式继承,它们都是经典类;Student类有显式继承,是新式类。而在python3中,Animal、Person和Student都是新式类。

(2)继承的示例

python中实现单继承的方式很简单:class <类名>(父类名):,由于python中没有访问权限修饰符,因此也不区分继承方式。如下:

# 父类与子类在同一文件中
class Person():  # 父类
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def eat(self):
        pass


class Student(Person):  # 子类
    def __init__(self, name, age, clazz):
        super().__init__(name, age)  # 继承属性的定义
        self.clazz = clazz  # 自有属性的定义

    def study(self, course):  # 定义自有方法
        pass

    def eat(self):  # 子类中出现与父类中相同的方法时,用子类中的方法
        super().eat()  # 重写时可以依靠父类的代码
        pass

当父类与子类在不同文件或不同包时,要增加一些操作:

# 父类,位于object包下的Person.py文件中
class Person():
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def eat(self):
        print("Person can eat.")
        
        
# 子类,位于object包下的Student.py文件中
from object import Person  # 先执行导入模块,object为包名,Person为模块名(就是Person.py文件名去掉后缀)


class Student(Person.Person):  # Person.Person表示调用Person模块中的Person对象;这与使用其他模块中的函数的方式是一样的:模块名.函数名();
    def __init__(self, name, age, clazz):
        super().__init__(name, age)  # 继承属性的定义
        self.clazz = clazz  # 自有属性的定义

    def study(self, course):  # 定义自有方法
        pass

    def eat(self):  # 子类中出现与父类中相同的方法时,用子类中的方法
        super().eat() # 重写时可以依靠父类的代码


stu1 = Student("zhaosi", 23, "三班")

stu1.eat()

在java中继承另一个文件中的父类时,如果同包可以直接用父类名(亦即父类文件名);如果不同包,只需先导包,再直接用父类名即可(在本包中没有同名类的情况下)。但python中这么做是不行的,因这python中并不强制要求文件名与类名一致;并且即便一致的情况下,python仍认为文件与文件内部的类或函数是不同类型的东西。比如,我们在Person类后面用print(Person) 打印,输出的是<class ‘__main__.Person’>;但是我们在另一个文件中,用from object import Person导入模块后用print(Person) 打印,输出的是<module ‘object.Person’ from ‘C:\RYH\Office\Python\qf2205\object\Person.py’> ;明显前者是一个类,后者是一个.py文件,也就是说两处的Person指代的不是同一个东西。

如果是多继承,则注意复杂的继承关系。如下:

class A(object):  # A作为了B和C的父类;
    def __init__(self, name):
        self.name = name
        print("in A")


class B(A):
    def __init__(self, name):
        self.name = name
        print("in B")
        super().__init__(name)


class C(A):
    def __init__(self, name):  # 当子类的__init__方法中示显示调用父类的构造方法时,这里会报阴影,但这并不算代码错误,只是提示用户不要忘记调用父类初始化方法,依然能正常运行;
        self.name = name
        print("in C")


class D(B, C):  # D同时继承了B和C;
    pass


obj = D('myname')

# 当继承关系比较复杂时,可以使用__mro__类属性查看类的继承关系
print(D.__mro__)  # 打印的列表中有一定会有<class 'object'>,因为这是所有类的共同超类。


# python3以后默认所有的类都有共同的祖先类object。查看object的源码
class Developer(object):
    pass

data = Developer()
data.name = 'CooMark'
data.age = 30

print(type(object))

关于多继承中继承构造函数的问题,D同时继承了B和C,按出现顺序D会先继承B的构造函数,如果B中没有构造函数,那么D该继承 A还是C?这个问题在python2中涉及到新式类(继承object的类是新式类)与经典类(没有写父类的是经典类)的概念;经典类是按照深度优先查找,新式类按照广度优先查找。但在python3以上的版本中,只有新式类(即默认所有类继承自object),因此继承方式全按广度优先。由于可以断定,如果B中没有构造,D将优先从C中继承构造。

(3)可以从父类继承的成员

实际上子类可以继承父类的所有成员,如:实例属性(包括公有实例属性与私有实例属性)、类属性、实例方法、类方法、静态方法;但理论上私有实例属性还是直接属性父类的。如下:

class Person():  # 父类
    nicheng = "null"
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.__value = 5

    def eat(self):
        print("Person can eat.")

    @classmethod
    def run(cls):
        print("{}正在跑".format(cls.nicheng))

    @staticmethod
    def look(who):
        print("正在看{}".format(who))


class Student(Person):  # 子类
    def __init__(self, name, age, clazz):
        super().__init__(name, age)  # 访问父类中的初始化方法
        self.clazz = clazz

s = Student("hang", 18, 4)
print(s.name)  # 子类对象访问继承自父类的公有实例属性
print(s._Person__value)  # 子类对象访问继承自父类的私有实例属性,虽然可以调用,但却是以父类的私有属性调用的。
print(s.nicheng)  # 子类对象访问继承自父类的类属性
s.eat()  # 子类对象调用继承自父类的实例方法
s.run()  # 子类对象调用继承自父类的类方法
s.look("mm")  # 子类对象调用继承自父类的静态方法

print(dir(s))  # 该语句的打印结果为['_Person__value', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'clazz', 'eat', 'look', 'name', 'nicheng', 'run']。可以看到,子类对象中也是有__value这个私有属性的,但其改变之后的名称符号是父类下的_Person__value。

# 子类正规访问父类私有实例属性的方式,是通过该私有属性对应的公开方法访问,即通过getter与setter访问和修改。

2.3.3 子类访问父类成员

外部访问父类成员可以通过子类对象或子类进行,如上一节所示。那么如何在子类中访问父类成员?

python中用super()函数来访问父类属性和方法。由于与C一样支持多重继承,必须要区别是访问哪个父类的成员,因此python中用函数来访问父类属性,而不是用关键字。

super()函数是父类关键字,使用super()可以访问父类的类属性、实例方法、类方法及静态方法,但不能访问实例属性(因为实例属性是定义在__init__方法下的)。如下:

class Person():  # 父类
    nicheng = "null"
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.__value = 5

    def eat(self):
        print("Person can eat.")

    @classmethod
    def run(cls):
        print("{}正在跑".format(cls.nicheng))

    @staticmethod
    def look(who):
        print("正在看{}".format(who))


class Student(Person):  # 子类
    def __init__(self, name, age, clazz):
        super().__init__(name, age)  # 访问父类中的初始化方法
        self.clazz = clazz

    def study(self, course):
        super().look(course)  # 访问父类中的静态方法
        pass

    def eat(self):
        super().eat()  # 访问父类中的实例方法
        print(super().nicheng)  # 访问父类中的类属性

    def show(self):
        # print(super()._Person__value)  # 访问父类中的私有实例属性,不能使用super()
        # print(super().name)  # 访问父类中的公有实例属性,不能使用super()
        print(self._Person__value)  # 访问父类中的私有实例属性,可以使用self
        print(self.name)  # 访问父类中的公有实例属性,可以使用self
        super().run()  # 访问父类中的类方法

s = Student("hang", 18, 4)
s.eat()
s.show()
s.study("mm")


# super.nicheng = "11"  # 用super编译是不报错的,但运行会报错,这样是不能访问父类属性的,因为super在python中是一个类,这样的语句相当于调用super类中的属性,但super中是没有该属性的;
print(super)  # 打印会显示super为一个class
print(super(Person))

2.3.4 方法重写

python中方法重写有以下规则:

  1. 在父子类中;
  2. 方法名相同;
  3. 参数列表可以相同,也可以不同;
  4. 与返回值类型无关(python定义函数不用预设返回值类型);
  5. 与访问权限无关(python没有访问权限关键字);
  6. 静态方法可以被继承,但是不能被重写;
# 方法的重写
# 当我们调用一个对象的方法时
#    会优先去当前对象中寻找是否具有该方法,如果有则直接调用
#    如果没有则去对象的父类中寻找,如果父类中有则直接调用父类中的方法
#    如果还是没有则去父类中的父类中寻找,以此类推,直到找到object ,
#    如果始祖父类也没有, 就报错

class Animal:
    def run(self):
        print('动物会跑~~~')

    def sleep(self):
        print('动物睡觉~~~')

class Dog(Animal):
    def bark(self):
        print('汪汪汪~~~') 

    def run(self):
        print('狗跑~~~~')

d = Dog()
d.run()


# 如果参数名不同,则子类对象要完全按照自己的方法进行调用
class Animal:
    def run(self):
        print('动物会跑~~~')

    def sleep(self):
        print('动物睡觉~~~')

class Dog(Animal):
    def bark(self):
        print('汪汪汪~~~')

    def run(self, who):
        print('{}狗跑~~~~'.format(who))

d = Dog()
d.run("我的")
# d.run()  # 若按照父类中的方法调用则会出错
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值