python面向对象

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python面向对象相关的知识,希望对你有一定的参考价值。

python面向对象

一、面向对象的概念

1. 面向对象(OOP)是什么

面向对象是一种编程范式,前人把其中一些普遍适用且行之有效的编程模式归结为“范式”。常见的编程范式有:面向过程编程:OPP、面向对象编程:OOP、函数式编程。Python既可以面向过程编程,也可以面向对象编程。

面向过程编程的步骤:

1)分析出解决问题所需要的步骤;

2)用函数把这些步骤一次实现;

3)一个一个地调用这些函数来解决问题;

面向对象编程的步骤:

1)把构成问题的事务分解、抽象成各个对象;

2)结合这些对象的共有属性,抽象出类;

3)类层次化结构设计--继承 和 合成;

4)用类和实例进行设计和实现来解决问题。

2. 面向对象编程的特点

面向对象编程达到了软件工程的3个目标:重用性、灵活性、扩展性,而这些目标是通过以下几个主要特点实现的:

封装: 可以隐藏实现细节,使代码模块化

继承: 可以通过扩展已存在的类来实现代码重用,避免重复编写相同的代码

多态: 封装和继承的目的都是为了实现代码重用, 而多态是为了实现接口重用,使得类在继承和派生的时候能够保证任何一个类的实例都能正确调用约定好、相同的属性和方法。

Python不像Java中有专门的“接口”定义,Python中的接口与类没有什么区别,但是我们可以通过在一个被用作接口的类的方法体中定义raise NotImplementedError异常,来强制子类必须重新实现该方法。

二、类的封装

封装是面向对象的主要特征之一,是对象和类概念的主要特性。简单的说,一个类就是一个封装了数据以及操作这些数据的方法的逻辑实体,它向外暴露部分数据和方法,屏蔽具体的实现细节。除此之外,在一个对象内部,某些数据或方法可以是私有的,这些私有的数据或方法是不允许外界访问的。通过这种方式,对象对内部数据提供了不同级别的保护以防止程序中无关的部分意外的改变或错误使用了对象的私有部分,下面我们通过类的定义和实例化的实例来说明一下Python中的是如何实现对这些不同等级数据的保护的。

1. 类的定义

类的定义是对显示事务的抽象过程和能力,类是一个对象/实例的模板,也是一个特殊的对象/实例(Pythobn中一切皆对象,类本身也是一个对象)

现在我们来定义个Person类,它有以下3个属性,同时做出如下假设:

  • nationality:国籍
  • name:姓名
  • id:身份证号码
  • 所有人的国籍基本都是相同的,且允许直接通过类或实例来访问,允许随意修改
  • 大部分人的姓名是不同的,且允许直接通过类的实例来访问和随意修改
  • 所有人的身份证号码都是不一样的,且不允许直接通过类或实例来访问或随意修改

import uuid

 

class Person(object):

    nationality = ‘China‘

   

    def __init__(self, name):

        self.name = name

        self.__id = str(uuid.uuid1())

   

    def hello(self):

        print(‘Hi, i am %s, from %s, my id is %s‘ % (self.name, self.nationality, self.__id))

   

    def get_id(self):

        print(self.__id)

        return self.__id

2. 类的实例化

类实例化的方式:类名([参数...])

其中参数是__init__方法中除了第一个self参数之外的其他参数,上面定义的这个Person类中,实例化时需要传递的参数只有一个name。比如我们来实例化3个Person对象,他们的name分别是 tom 和 jerry:

tom = Person(‘tom‘)

jerry = Person(‘jerry‘)

jack = Person(‘jack‘)

3. 不同保护等级的属性说明

公有属性/类属性

直接定义在class下的属性就是公有属性/类属性,比如上面那个Person类中的nationality属性就是共有属性。“公有”的意思是这个属性是这个类的所有实例对象共同所有的。

print(Person.nationality, tom.nationality, jerry.nationality, jack.nationality)

 

tom.nationality = ‘USA‘

print(Person.nationality, tom.nationality, jerry.nationality, jack.nationality)

 

Person.nationality = ‘India‘

print(Person.nationality, tom.nationality, jerry.nationality, jack.nationality)

输出结果如下:

China China China China

China USA China China

India USA India India

 

结论:

- 公有属性/静态属性 可以直接通过类直接访问,也可以直接通过实例进行访问;

- 通过类的某个实例对公有属性进行修改,实际上是对该实例添加了一个与类的公有属性名称相同的成员属性,对原公有属性是没有影响的,不会影响其他实例获取的该公有属性的值;

- 通过类对公有属性进行修改,必然是会改变公有属性原有的值,他对该类所有的实例是都有影响的。

成员属性/实例属性

成员属性,又称成员变量 或 实例属性,成员属性是该类的每个实例对象单独持有的属性。成员属性需要在类的__init__方法中进行声明,比如上面的Person类中定义的name属性就是一个成员属性。

print(tom.name, jerry.name, jack.name)

 

jerry.name = ‘jerry01‘

print(tom.name, jerry.name, jack.name)

输出结果:

tom jerry jack

tom jerry01 jack

结论:

- 成员属性可以直接通过实例对象来访问和更改;

- 成员属性是每个实例对象独有的,某个实例对象的成员属性被更改不会影响其他实例对象的相同属性的值;

- 成员属性的值不能通过类来访问和修改;

私有属性

私有属性和成员属性一样,是在__init__方法中进行声明,但是属性名需要以双下划线__开头,比如上面定义的Person中的__id属性。私有属性是一种特殊的成员属性,它只允许在实例对象的内部(成员方法或私有方法中)访问,而不允许在实例对象的外部通过实例对象或类来直接访问,也不能被子类继承。

通过实例对象访问私有属性:

print(tom.__id)

输出结果

Traceback (most recent call last):

    ...

AttributeError: ‘Person‘ object has no attribute ‘__id‘

通过类访问私有属性:

print(Person.__id)

输出结果:

Traceback (most recent call last):

  ...

AttributeError: type object ‘Person‘ has no attribute ‘__id‘

通过类的成员方法访问私有属性:

tom.hello()

jerry.hello()

jack.hello()

输出结果:

Hi, i am tom, from China, my id is b6ac08c6-9dae-11e7-993f-208984d7aa83

Hi, i am jerry, from China, my id is b6ac08c7-9dae-11e7-b508-208984d7aa83

Hi, i am jack, from China, my id is b6ac08c8-9dae-11e7-9ace-208984d7aa83

结论:

- 私有变量不能通过类直接访问;

- 私有变量也不能通过实例对象直接访问;

- 私有变量可以通过成员方法进行访问。

那么要访问私有变量怎么办呢? 有两种办法:

办法1:通过一个专门的成员方法返回该私有变量的值,比如上面定义的get_id()方法。

tom_id = tom.get_id()

jerry_id = jerry.get_id()

jack_id = jack.get_id()

 

print(tom_id, jerry_id, jack_id)

输出结果:

46bc6b5c-9dd6-11e7-8306-208984d7aa83 46cbfe68-9dd6-11e7-b5d1-208984d7aa83 46cbfe69-9dd6-11e7-9b5c-208984d7aa83

办法2:通过 实例对象._类名__私有变量名 的方式来访问

print(tom._Person__id, jerry._Person__id, jack._Person__id)

输出结果:

e1f4ee86-9dd6-11e7-a186-208984d7aa83 e1f5b1f8-9dd6-11e7-b1c3-208984d7aa83 e1f5b1f9-9dd6-11e7-b74a-208984d7aa83

总结

  • 公有属性、成员属性 和 私有属性 的受保护等级是依次递增的;
  • 私有属性 和 成员属性 是存放在已实例化的对象中的,每个对象都会保存一份;
  • 公有属性是保存在类中的,只保存一份;
  • 哪些属性应该是公有属性的,哪些属性应该是私有属性 需要根据具体业务需求来确定。

 

三、类的继承

继承是一个从一般到特殊的过程,子类可以继承现有类的所有功能,而不需要重新实现代码。继承提高了代码重用性和扩展性。

Python中类的继承按照父类中的方法是否已实现可分为两种:

  • 实现继承 :指直接继承父类的属性和已定义并实现的的方法;
  • 接口继承 :仅继承父类类的属性和方法名称,子类必须自行实现方法的具体功能代码。

如果是根据要继承的父类的个数来分,有可以分为:

  • 单继承: 只继承1个父类
  • 多继承: 继承多个父类

4. 类继承实例

类的继承关系

父类--Person

class Person(object):

    def __init__(self, name, age):

        self.name = name

        self.age = age

   

    def walk(self):

        print(‘%s is walking...‘ % self.name)

       

    def talk(self):

        print(‘%s is talking...‘ % self.name )

子类--Teacher

class Teacher(Person):

    def __init__(self, name, age, level, salary):

        super(Teacher, self).__init__(name, age)

        self.level = level

        self.salary = salary

 

    def teach(self):

        print(‘%s is teaching...‘ % self.name)

子类--Class

class Student(Person):

    def __init__(self, name, age, class_):

        Person.__init__(self, name, age)

        self.class_ = class_

 

    def study(self):

        print(‘%s is studying...‘ % self.name)

子类实例化

t1 = Teacher(‘张老师‘, 33, ‘高级教师‘, 20000)

s1 = Student(‘小明‘, 13, ‘初一3班‘)

 

t1.talk()

t1.walk()

t1.teach()

 

s1.talk()

s1.walk()

s1.study()

输出结果:

张老师 is talking...

张老师 is walking...

张老师 is teaching...

小明 is talking...

小明 is walking...

小明 is studying...

继承说明

  • Teacher类和Student类都继承Person类,Teacher和Student是Person的子类/派生类,Person是Teacher和Student的父类/基类/超类;
  • Teacher和Student对Person的继承属于实现继承,且是单继承;
  • 子类需要在自己的__init__方法中的第一行位置调用父类的构造方法,上面给出了两种方法:
  • super(子类名, self).__init__(父类构造参数)。如super.(Teacher, self).__init__(name, age)
  • 父类名.__init__(self, 父类构造参数)。如Person.__init__(self, name, age),这是老式的用法。
  • 子类 Teacher 和 Student 也可以在自己的类定义中重新定义父类中的talk()和walk()方法,改变其实现代码,这叫做方法重写

 

四、类的多态

多态是指相同的成员方法名称,但是成员方法的代码实现却各不相同。这里所说的多态是通过继承接口的方式实现的。Python中可以通过在一个成员方法体中抛出一个NotImplementedError异常来强制继承该接口的子类在调用该方法前必须先实现该方法的功能代码。

接口--Animal

class Animal(object):

    def __init__(self, name):

        self.name = name

 

    def walk(self):

        raise NotImplemented(‘Subclass must implement the abstract method by self‘)

 

    def talk(self):

        raise NotImplemented(‘Subclass must implement the abstract method by self‘)

子类--Dog

class Dog(Animal):

    pass

执行代码:

dog = Dog(‘大黄‘)

dog.talk()

输出结果:

Traceback (most recent call last):

  ...

    raise NotImplemented(‘Subclass must implement the abstract method by self‘)

TypeError: ‘NotImplementedType‘ object is not callable

可见,此时子类必须自己先实现talk方法才能调用。

实现了接口方法的子类--Dog 和 Duck

class Dog(Animal):

    def talk(self):

        print(‘%s is talking:旺旺...‘ % self.name)

 

    def walk(self):

        print(‘%s 是一条小狗,用4条腿走路‘ % self.name)

 

class Duck(Animal):

    def talk(self):

        print(‘%s is talking: 嘎嘎...‘ % self.name)

 

    def walk(self):

        print(‘%s 是一只鸭子,用两条腿走路‘ % self.name)

执行代码:

dog = Dog(‘大黄‘)

dog.talk()

dog.walk()

 

duck = Duck(‘小白‘)

duck.talk()

duck.walk()

输出结果:

大黄 is talking:旺旺...

大黄 是一条小狗,用4条腿走路

小白 is talking: 嘎嘎...

小白 是一只鸭子,用两条腿走路

由此可知:

  • 接口的所有子类拥有接口中定义的所有同名的方法;
  • 接口的所有子类在调用接口中定义的方法时,必须先自己实现方法代码;
  • 接口的各个子类在实现接口中同一个方法时,具体的代码实现各不相同,这就是多态。

 

五、属性方法、类方法、静态方法

类中封装的是数据和操作数据的方法。数据就是属性,属性分为:公有属性/类变量、成员属性/实例变量 和 私有属性。现在我们来说说类中的方法,类中的方法分为以下几种:

  • 成员方法: 上面定义的都是成员方法,通常情况下,它们与成员属性相似,是通过类的实例对象去访问;成员方法的第一个参数必须是当前实例对象,通常写为self;实际上。
  • 私有方法: 以双下划线开头的成员方法就是私有方法,与私有属性类似,只能在实例对象内部访问,且不能被子类继承;私有方法的第一个参数也必须是当前实例对象本身,通常写为self;
  • 类方法:以@classmethod来装饰的成员方法就叫做类方法,它要求第一次参数必须是当前类,通常写为cls。与公有属性/静态属性相似,除了可通过实例对象进行访问,还可以直接通过类名去访问。类方法只能访问公有属性,不能访问成员属性,因此第一个参数传递的是代表当前类的cls,而不是表示实例对象的self。
  • 静态方法: 以@staticmethod来装饰的成员方法就叫做静态方法,静态方法通常都是通过类名去访问,且严格意义上来讲,静态方法已经与这个类没有任何关联了,因为静态方法不要求必须传递实例对象或类参数,这种情况下它不能访问类中的任何属性和方法。
  • 属性方法:是指可以像访问成员属性那样去访问这个方法;它的第一个参数也必须是当前实例对象,且该方法必须要有返回值。

我们先来定义这样一个类:

import uuid

 

class Person(object):

    nationality = ‘China‘

 

    def __init__(self, name, age):

        self.name = name

        self.age = age

        self.__id = str(uuid.uuid1())

 

    # 成员方法/实例方法

    def sayHello(self):

        print(‘Hello, i am %s from %s, i am %d years old.‘ % (self.name, self.nationality, self.age))

 

    # 私有方法

    def __func0(self):

        print(‘private method: func0‘)

        print(self.name, self.age, self.__id, self.nationality)

 

    # 类方法

    @classmethod

    def func1(cls):

        print(cls.nationality)

 

    # 静态方法

    @staticmethod

    def func2(a, b):

        print(a + b)

 

    #属性方法

    @property

    def func3(self):

        return ‘%s: %d‘ % (self.name, self.age)

执行代码:

p = Person(‘Tom‘, 18)

 

p.sayHello()

Person.sayHello(p)

 

Person.func1()

p.func1()

 

Person.func2(3, 4)

p.func2(3, 4)

 

 

print(p.func3)

输出结果:

Hello, i am Tom from China, i am 18 years old.

Hello, i am Tom from China, i am 18 years old.

 

China

China

 

7

7

 

Tom: 18

总结:

  • 成员方法也可以通过类名去访问,但是有点多此一举的感觉;
  • 类方法和静态方法也可以通过实例对象去访问,但是通常情况下都是通过类名直接访问的;
  • 类的各种方法,能访问哪些属性实际上是跟方法的参数有关的:比如成员方法要求第一个参数必须是一个该类的实例对象,那么实例对象能访问的属性,成员方法都能访问,而且还能访问私有属性;类方法要求第一个参数必须是当前类,因此它只能访问到公有属性,而访问不到成员属性 和 私有属性;静态方法对参数没有要求,也就意味着我们可以任意给静态方法定义参数;假如我们给静态方法定义了表示当前类的参数,那么就可以访问公有属性;假如我们给静态方法定义了表示当前类的实例对象的参数,那么就可以访问成员属性;假如我们没有给静态方法定义这两个参数,那么就不能访问该类或实例对象的任何属性。

 

六、类的特殊成员属性及特殊成员方法

我们上面提到过:名称以双下划线__开头的属性是私有属性,名称以双下划线__开头的方法是私有方法。这里我们要来说明的是,Python的类中有一些内置的、特殊的属性和方法,它们的名称是以双下划线__开头,同时又以双下划线__结尾。这些属性和方法不再是私有属性和私有方法,它们是可以在类的外部通过实例对象去直接访问的,且它们都有着各自特殊的意义。

1. 类的特殊成员属性

属性名称

说明

__doc__

类的描述信息

__module__

表示当前操作的对象对应的类的定义所在的模块名

__class__

表示当前操作的对象对应的类名

__dict__

一个字典,保存类的所有的成员(包括属性和方法)或实例对象中的所有成员属性

现在来看一个实例:

在dog.py模块定义一个Dog类

class Dog(object):

    """这是一个Dog类"""

    # print(‘Hello, This is a dog.‘)

    color = ‘白色‘

 

    def __init__(self, name):

        self.name = name

        self.__id = 1234

 

    def func1(self):

        pass

 

    def __func1(self):

        pass

 

    @classmethod

    def func2(cls):

        pass

 

    @staticmethod

    def func3():

        pass

在test.py模块执行下面的代码

from dog import Dog

 

dog1 = Dog(‘泰迪‘)

 

print(dog1.__doc__)

print(dog1.__module__)

print(dog1.__class__)

print(dog1.__dict__)

print(Dog.__dict__)

输出结果

这是一个Dog类

dog

<class ‘dog.Dog‘>

{‘name‘: ‘泰迪‘, ‘_Dog__id‘: 1234}

{‘__dict__‘: <attribute ‘__dict__‘ of ‘Dog‘ objects>, ‘__module__‘: ‘dog‘, ‘func2‘: <classmethod object at 0x000001DF0C658F98>, ‘color‘: ‘白色‘, ‘func3‘: <staticmethod object at 0x000001DF0C658FD0>, ‘_Dog__func1‘: <function Dog.__func1 at 0x000001DF0C63E400>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘Dog‘ objects>, ‘__doc__‘: ‘这是一个Dog类‘, ‘__init__‘: <function Dog.__init__ at 0x000001DF0C63E2F0>, ‘func1‘: <function Dog.func1 at 0x000001DF0C63E378>}

总结:

  • 实例对象.__dict__ 和 类.__dict__ 的值是不同的:实例对象.__dict__的值中只包含成员属性和私有属性,类.__dict__的值中包含公有属性/类属性和所有类型的方法;
  • __module__和__class__的值可用于反射,来实例化一个类的对象。

2. 类的特殊成员方法

方法名称

说明

__init__

类构造方法,通过类创建对象时会自动触发执行该方法

__del__

类析构方法,当对象在内存中被释放时,会自动触发执行该方法。比如实例对象的作用域退出时,或者执行 del 实例对象操作时。

__str__

如果一个类中定义了__str__方法,那么在打印对象时默认输出该方法的返回值,否则会打印出该实例对象的内存地址。

__xxxitem__

是指__getitem__、__setitem__、__delitem这3个方法,它们用于索引操作,比如对字典的操作,分别表示 获取、设置、删除某个条目、数据。可以通过这些方法来定义一个类对字典进行封装,从而可以对字典中key的操作进行控制,尤其是删除操作。

__new__

该方法会在__init__方法之前被执行,该方法会创建并返回一个新的实例对象,然后传递给__init__。另外需要说明的是,这不是一个成员方法,而是一个静态方法。

__call__

是把自己(实例对象)作为一个函数去调用,而函数的调用方式是函数名()。也就是说,当我们执行实例对象()或者 类名()()这样的操作时会触发执行该方法。

示例1

先来定义这样一个类:

class Person(object):

    def __call__(self, *args, **kwargs):

        print(self.name, ‘__call__‘)

 

    def __init__(self, name, age):

        self.name = name

        self.age = age

        print(self.name, ‘__init__‘)

 

    def __del__(self):

        print(self.name, ‘__del__‘)

 

    def __str__(self):

        print(self.name, ‘__str__‘)

        return ‘%s: %d‘% (self.name, self.age)

执行下面的代码:

print(‘--------实例化对象-----------‘)

p = Person(‘Tom‘, 18)

print(‘--------打印实例对象-----------‘)

print(p)

print(‘--------把实例对象作为方法进行调用-----------‘)

p()  # 等价于 Person(‘Tom‘, 18)()

print(‘--------程序运行结束-----------‘)

输出结果:

--------实例化对象-----------

Tom __init__

--------打印实例对象-----------

Tom __str__

Tom: 18

--------把实例对象作为方法进行调用-----------

Tom __call__

--------程序运行结束-----------

Tom __del__

可以看到,所有代码都执行完后,进程退出时实例对象的__del__方法才被调用,这是因为对象要被销毁了。

示例2

定义一个类似字典的类

class MyDict(object):

    def __init__(self, init=None):

        self.__dict = init if init is not None else {}

 

    def __setitem__(self, key, value):

        print(‘__setitem__‘, key)

        self.__dict[key] = value

 

    def __getitem__(self, item):

        print(‘__getitem__‘, item)

        return self.__dict.get(item, None)

 

    def __delitem__(self, key):

        print(‘__delitem__‘, key)

        if key is not None and key.startswith(‘wh‘):

            print(‘You can not delete this item ‘)

            return None

        return self.__dict.pop(key, None)

执行下面的代码

# 类实例化与get item

my_dict = MyDict(init={‘what‘: ‘打豆豆‘, ‘who‘: ‘企鹅团‘, ‘time‘: ‘吃饱睡好之后‘})

print(my_dict[‘who‘], my_dict[‘time‘], my_dict[‘what‘])

 

# set item

my_dict[‘num‘] = ‘10次‘

print(my_dict[‘who‘], my_dict[‘time‘], my_dict[‘what‘], my_dict[‘num‘])

 

# del item

del my_dict[‘num‘]

print(my_dict[‘num‘])

 

del my_dict[‘what‘]

print(my_dict[‘what‘])

输出结果

__getitem__ who

__getitem__ time

__getitem__ what

企鹅团 吃饱睡好之后 打豆豆

 

__setitem__ num

__getitem__ who

__getitem__ time

__getitem__ what

__getitem__ num

企鹅团 吃饱睡好之后 打豆豆 10次

 

__delitem__ num

__getitem__ num

None

 

__delitem__ what

You can not delete this item

__getitem__ what

打豆豆

可见,如果一个类实现了__setitem__,__getitem、__delitem 这几个方法,就可以执行一些类似字典一样的操作,比如上面用到的:

  • my_dict[‘KEY‘] 会自动调用my_dict实例对象的__getitem__方法;
  • my_dict[‘KEY‘] = VALUE 会自动调用my_dict实例对象的__setitem__方法;
  • del my_dict[‘KEY‘] 会自动调用my_dict实例独享的__delitem__方法;
    而我们定义这样一个类的目的在于,我们可以更好对字典操作进行控制,比如上面的例子中我们不允许删除key以‘wh‘开头的条目。

 

七、继承层级关系中子类的实例对象对属性的查找顺序问题

简单来说,就是要大家搞明白2个问题:1)子类的实例对象调用的某个属性或方法到底是父类的还是自己的;2)如果是多继承(同时继承多个父类),调用的的到底是哪个父类的属性或方法,查找顺序是怎样的。

1. 单继承的情况

A、B、C三个类的定义如下:

class A(object):

    def __init__(self, name):

        self.name = name

 

    def func1(self):

        print(‘class A: func1‘)

 

    def func2(self):

        print(‘class A: func2‘)

 

class B(A):

    def __init__(self, name, age):

        super(B, self).__init__(name)

        self.age = age

 

    def func2(self):

        print(‘class B: func2‘)

 

class C(A):

    def func1(self):

        print(‘class C: func1‘)

 

    def func3(self):

        print(‘class C: func3‘)

现在要执行这段代码:

objB = B(‘Tom‘, 18)

objC = C(‘Jerry‘)

 

print(objB.name, objB.age)

print(objC.name)

objB.func1()

objC.func1()

 

输出结果:

Tom 18

Jerry

class A: func1

class C: func1

这是最简单的情况,子类肯定是先找自己有没有这个属性或方法,有的话直接调用自己的,没有再去父类里面找。因此不做过多赘述。

A与B两个类的定义如下:

class A(object):

    def __init__(self, name):

        self.name = name

 

    def func1(self):

        print(‘class A: func1‘)

        self.func2()

 

    def func2(self):

        print(‘class A: func2‘)

 

class B(A):

    def __init__(self, name, age):

        super(B, self).__init__(name)

        self.age = age

 

    def func2(self):

        print(‘class B: func2‘)

现在要执行下面的代码:

objB = B(‘Tom‘, 18)

objB.func1()

分析1:

class B 是class A的子类,因此它会继承class A的的方法func1和func2。但是,class B已经重写了func2,可以理解为class A中的func2方法已经被覆盖了,class B现在只能看到自己重写后的那个func2方法,所以func1中调用的应该是class B 重写后的func2方法。

分析2:

有的同学可能不太能理解,class A中的方法怎么能调用class B中的方法呢?下面我们来看下class B与class
A的包含关系图:

 技术分享

 

因为子类 class B继承了 class A的内容,因此绿框中的内容(class A)是属于蓝框(class B)中的一部分,他们应该看做一个整体。绿框中的func1是可以调用绿框外的func2的,因为他们都是objB中的成员方法。其实理解这些之后,现在我们来套用开始那句话:

  • func1方法的查找与调用: objB调用func1方法,发现class B本身(蓝框内的直接内容)并没有该方法,所以去父类class A(蓝框内的绿框)中去找,发现找到了,于是进行调用;
  • func2方法的查找与调用: func1中调用了func2方法,这个时候还是先找子类class B本身(蓝框内的直接内容),发现找到了,于是直接调用子类自己的func2方法。

因此上面这段代码的执行结果是:

class A: func1

class B: func2

2. 多继承的情况

新式类 与 经典类

Python 2.2引入了新式类,与它对应的是经典类,这里我们仅仅是解释下他们的概念,为讲解下面的内容做铺垫,不会深入讨论的它们的之间的区别。这里我们主要说明一下几个点就可以了:

  • Python 2.x中,默认是经典类,只有显示继承了object的才是新式类;
  • Python 3.x中,默认就是新式类,经典类已经被废弃;
  • 新式类的子类也是新式类

深度优先 与 广度优先

深度优先 可以理解为 纵向优先,广度优先 可以理解为 水平方法优先。我们知道,类与类之间是有层级关系的,父类与子类是纵向的层级关系,同一个父类的多个直接子类是水平方向的同级关系。

 技术分享

 

上图中 A是父类、B和C是继承A的子类,D是同时继承B和C的子类。此时D的一个实例对象去查找一个父类中的属性或方法的查找顺序就有两种可能,但是这两种查找顺序中第一个查找的父类必然都是B:

  • B-->A-->C:这就是深度优先,因为优先查找的是与B上一层级的、纵向的A
  • B-->C-->A:这就是广度优先,因为优先查找的是与B同一层级的、横向的C

实例

定义以下几个类:

class A(object):

    def func1(self):

        print(‘class A: func1‘)

 

    def func2(self):

        print(‘class A: func2‘)

 

class B(A):

    def func3(self):

        print(‘class B: func3‘)

 

class C(A):

    def func1(self):

        print(‘class C: func1‘)

 

class D(B, C):

    pass

执行如下代码:

objD = D()

objD.func1()

Python 2.7 和 Python 3.5的输出结果都是一样的:

class C: func1

我们更改下A的定义,不显示的指定其继承object:

class A():

    def func1(self):

        print(‘class A: func1‘)

 

    def func2(self):

        print(‘class A: func2‘)

 

class B(A):

    def func3(self):

        print(‘class B: func3‘)

 

class C(A):

    def func1(self):

        print(‘class C: func1‘)

 

class D(B, C):

    pass

再来执行同样的代码:

objD = D()

objD.func1()

Python 2.7的输出结果:

class A: func1

Python 3.5的输出结果:

class C: func1

结论

Python 3.x中无论是否显示指定继承对象,所有的类都是新式类,那么我们根据上面的两个实例的输出结果可以得出这样的结论:在多继承的情况下,经典类查找父类属性或方法的顺序是深度优先,新式类查找父类属性的顺序是广度优先。

 



以上是关于python面向对象的主要内容,如果未能解决你的问题,请参考以下文章

Python --013--面向对象

Python—面向对象

python之面向对象

Python 面向对象

python 面向对象

Python面向对象