python3基础篇(八)——面向对象

python3基础篇(八)——面向对象

前言
1 阅读这篇文章我能学到什么?
  这篇文章将为你详细介绍python3中的面向对象程序设计,你将学会如何定义一个类和使用一个对象。

——如果你觉得这是一篇不错的文章,希望你能给一个小小的赞,感谢您的支持。

  python3是一门面向对象的语言,掌握好面向对象的程序设计是用好python3的前提。如果你理解c++的“万物皆对象”概念那学习起来就轻松很多了。
  面向对象是一个抽象的程序设计概念,是软件开发的方法。起初它来自于程序设计,而如今面向对象概念已经扩展到数据库系统、交互结构、应用结构、应用平台、分布式系统、网络管理结构、CAD计数、人工智能等领域。这种思想就像种子一样已经到处传播并发芽生长。
  对象编程将任何事物(可以是客观存在或抽象的,客观的比如桌子板凳,抽象的比如时间和爱情)都看做具有 属性方法 的对象,又从同一类事物中抽象出他们的 共性 (比如人类共有的属性是年龄和体重,共有的方法是学习和吃饭)形成类。这就是 面向对象 的概念,也是对象和类的关系。

——作者:这段话是我自己总结的,如有不对欢迎指正

  通过上面的描述我们知道了对象是描述事物的,而类是对象的共性的抽象。 面向对象编程 (注意区分面向对象和面向对象编程)就是以操控对象的形式去编程,要创建对象就必须为对象定义相应的类,类描述了对象的属性和方法。

1 定义带有属性和方法的简单类

  python3中使用关键字class定义类,后面接类名,不要忘了后面的:符号,类主体至少要比class缩进一个空格或table。类的主体中定义属性和方法。

  属性是描述对象的共性特征的,比如年龄和身高,对应编程语言中存储数据的变量。方法是对象能完成的动作,比如“我”可以写博客也可以吃饭,对应编程语言能完成特定功能的函数。 面向对象的三大特征是封装、继承、多态 ,其中封装和继承主要是为了增加代码的重用性,多态主要是为了以不变的代码应对万变的需求。
  下面我们来设计一个类——Human,Human的共性就是都具有年龄和身高,年龄会长大而身高会长高。当然还具有很多其他共性,我们不可能一一写出来,只需要设计出我们关心的几个共性。
代码示例:

class Human:                               #定义类,也可写为class Human()
    "对类的描述"                           #描述可省略
    def __init__(self):
        #类的成员变量)
        self.Age = 18
        self.Height = 183

    #类中的方法(类的成员函数)
    def AddHeight(self):
        self.Height += 1
        return self.Height

    def AddAge(self):
        self.Age += 1
        return self.Age

Calm = Human()                              #将类实例化成对象Calm

print(Calm.Height)                          #访问对象的属性Height
print(Calm.Age)                             #访问对象的属性Age
print(Calm.AddHeight())                     #调用对象的方法
print(Calm.AddAge())                        #调用对象的方法

运行结果:

183
18
184
19

  类中定义了属性AgeHeight,并设定了初始值18和183,AddHeightAddAge为类的方法。类定义好后Calm = Human()这句代码即实现将描述对象共性的类实例化出一个具体的对象Calm,这就像工厂里的模具能取出形状大小一样的物件一样。我们说过面向对象编程是把事物都看做具有属性和方法的对象,面向对象编程是通过操控的形式编程。现在有了对象Calm,我们就可以通过它去访问对象Calm的属性和方法了。通过成员运算符.即可访问到该对象下的属性和方法。
  上面代码里的self是什么作用?python3规定,类中的方法区别于普通函数,类方法参数列表的第一个参数(python3并没有规定第一个参数名只能是self,只要符合命名规则即可,用self是大家通用的习惯)必须用于接收对象自己。这样做有三个目的:

  • 从写法上区别于普通函数。
  • 将函数和对象绑定在一起,这样就清楚这个函数对应哪个对象了。
  • 方便在类方法中操作类中的其他属性和方法。

  为什么说self就是对象自己呢?我们写一段简单的代码输出看一下对象地址,输出看一下self就知道了
代码示例:

class cTest:
    def Function(self):
        print(self)

Test1 = cTest()
Test1.Function()                            #print(self)
print(Test1)                                #print(Test1)

Test2 = cTest()
Test2.Function()                            #print(self)
print(Test2)                                #print(Test2)

运行结果:

<__main__.cTest object at 0x0000010DDA0C7400>
<__main__.cTest object at 0x0000010DDA0C7400>
<__main__.cTest object at 0x0000010DDA142CA0>
<__main__.cTest object at 0x0000010DDA142CA0>

  从上面代码可以看出对象Test1Test2是两个不同的对象,因此具有不同的对象地址(对应计算机上存储空间不同),但是它们各自的self是完全等价于对象自己的。就类似于c++和java中的this指针。
  明白了self后现在我们回到Human这个类的话题。AddHeightAddAge的第一参数是self即表明了它们是类的成员方法,同时将成员方法和对象关联起来,在成员方法拿到对象实例后就可以在成员方法内部访问对象的属性和方法。所以self.Heightself.Age是成员方法通过对象自己的实例访问了自己的属性。并且有了self将对象和方法关联起来,100个Human对象self.Age访问的都是自己独立的属性Age。(各有各的年龄)。
代码示例:

class Human:                               #定义类
    "对类的描述"                           #描述可省略
    def __init__(self):
        #类的成员变量)
        self.Age = 18
        self.Height = 183

    #类中的方法(类的成员函数)
    def AddHeight(self):
        self.Height += 1
        return self.Height

    def AddAge(self):
        self.Age += 1
        return self.Age

Calm = Human()                              #将类实例化成对象Calm
XiaoMing = Human()
print(Calm.Age)
print(XiaoMing.Age)
print("-------------------------------")
XiaoMing.Age = 19                           #修改了小明的年龄为19,但是不会影响Calm的18岁年龄
print(Calm.Age)
print(XiaoMing.Age)

运行结果:

18
18
-------------------------------
18
19

2 类的构造函数

  我们从Human类创建了两个对象CalmXiaoMing,它们的年龄都是18岁。如果我们要实现这样一个功能,对象创建时默认初始年龄是0岁并且具有初始升高(这更符合Human类的特点)。这就涉及到构造函数的概念。构造函数是一种特殊的类内方法,在对象被创建(实例化)时构造函数自动被调用,它的主要作用是对对象的成员变量进行初始化赋值。python3的构造函数名固定为__init__。构造函数可以省略也可被用户重载,省略时作用相当于没有参数和函数体的无参构造函数。

2.1 类的无参构造函数

代码示例:

class cTest:
    #重载无参构造函数,具有函数体
    def __init__(self):
        #午餐构造函数的函数体
        print("call: __init__(self)")
        self.Parameter = 0
        print(self.Parameter)

Object1 = cTest()

运行结果:

call: __init__(self)
0

  当一个类没有__init__构造函数时,系统会默认调用无参无函数体的构造函数。当用户重载__init__构造函数时,创建对象时将会调用用户定义后的构造函数。

2.2 类的带参构造函数

  构造函数重载后可带参,类实例化为对象时参数列表按照带参构造函数参数列表。

class cTest:
    #重载无参构造函数
    def __init__(self, Parameter1, Parameter2):
        #午餐构造函数的函数体
        print("call: __init__(self, Parameter1, Parameter2)")
        self.Parameter = Parameter1 * Parameter2
        print(self.Parameter)

Object1 = cTest(2, 5)

运行结果:

call: __init__(self, Parameter1, Parameter2)
10

2.3 构造函数与普通成员函数区别

  • 构造函数函数名只能是__init__,而普通成员函数函数名完全有用户定义。
  • 普通程序函数定义后才存在,未定义也就不存在,而每个类都自带一个默认构造函数(作用相当于无参无函数体构造函数),可由用户重载。
  • 普通成员函数需要用户主动去调用,而构造函数在对象创建(实例化)时自动调用。普通成员函数可以被调用多次,而构造函数只在对象创建时自动调用一次。
  • 用法上普通成员函数一般用于实现逻辑,构造函数一般用于对成员变量进行初始化。
  • 数量上一个类可以有多个普通成员函数,但只能有一个构造函数。

2.4 构造函数的多态

  在c++和Java里类中构造函数可以有多个,只要他们参数列表不同(参数个数、参数顺序、参数类型)即可。而python3中类的构造函数只能有一个。面向对象的三大特征是封装、继承、多态。python3的一个构造函数怎么实现构造函数的多台呢?
  我们知道python3的变量是不分类型的,这一点可以实现不同参数类型的构造函数以及参数顺序不同的构造函数。其次python3的函数参数是可以带默认值的,这一点可以实现参数个数不同的构造函数。另外python3的函数支持变长参数,这一点也可以实现不同参数个数的构造函数。这些特性支撑起了python3构造函数的多态性。

2.4.1 构造函数多态——参数类型不一致

代码示例:

class cTest:
    def __init__(self, Parameter):
        self.Parameter = Parameter
        print(self.Parameter)

#不同参数类型的构造函数
Object1 = cTest(2)                         #Number
Object2 = cTest("a")                       #String
Object3 = cTest((1, 2))                    #Tuple
Object4 = cTest([1, 2])                    #List
Object5 = cTest({1, 2})                    #Set
Object6 = cTest({"a":1, "b":2})            #Dictionary

运行结果:

2
a
(1, 2)
[1, 2]
{1, 2}
{'a': 1, 'b': 2}

  根据python3变量没有类型的特性,实现了构造函数的不同参数类型下的多态。

2.4.2 构造函数多态——参数顺序不一致

代码示例:

class cTest:
    def __init__(self, Parameter1,  Parameter2):
        self.Parameter1 = Parameter1
        self.Parameter2 = Parameter2
        print(self.Parameter1, self.Parameter2)

Object1 = cTest(1, "a")                             #Num String
Object2 = cTest("a", 1)                             #String Num

运行结果:

1 a
a 1

  根据python3变量没有类型的特性,实现了构造函数的不同参数类型顺序下的多态。

2.4.3 构造函数多态——参数个数不一致

代码示例:

class cTest:
    def __init__(self, *Parameter):                 #元组形式不定长参数
        self.Parameter = Parameter
        print(self.Parameter)

Object1 = cTest(1)                                  #1个参数
Object2 = cTest(1, "a")                             #2个参数
Object3 = cTest(1, "a", 2, 3)                       #4个参数

运行结果:

(1,)
(1, 'a')
(1, 'a', 2, 3)

  根据python3变量没有类型的特性,实现了构造函数的不同参数数量下的多态。
  因此python3实现构造函数的多态性并不需要像c++和java那样一个类重载多个构造函数,它只需要一个构造函数就够了。

  我们继续前面的Human类的例子。我们利用构造函数实现每个Human对象产生后Age初始为0,并且具有初始Height。
代码示例:

class Human:                               #定义类
    #类的构造函数
    def __init__(self, Height):
        self.Age = 0
        self.Height = Height

    #类中的方法(类的成员函数)
    def AddHeight(self):
        self.Height += 1
        return self.Height

    def AddAge(self):
        self.Age += 1
        return self.Age

Calm = Human(50)                              #将类实例化成对象Calm
print(Calm.Age, Calm.Height)

运行结果:

0 50

3 类的析构函数

  python3中的变量都可被主动(使用del关键字)或被动(作用域结束)释放(回收内存空间),当对象被释放时会自动调用一次析构函数。每个类都有一个析构函数,当用户未显式定义时会调用默认的析构函数,否则会调用用户重载的析构函数。默认析构函数作用等同于无函数体的析构函数。析构函数的函数名是固定的即__del__
代码示例:

class cTest:
    def __init__(self):
        print("__init__")

    def __del__(self):                      #对象被释放时自动调用
        print("__del__")

Object = cTest()
print("--------------------------")
del Object                                  #释放对象,触发调用析构函数
#print(Object.Parameter)                    #error 此时Object对象已经不存在了,不能继续使用

运行结果:

__init__
--------------------------
__del__

  请勿给析构函数定义除了self外的其他参数。
  上面讲了python3的构造函数和析构函数,它们有其特殊性,但是如果你通过对象主动去调用也不会报错,构造函数和析构函数会像普通函数那样执行。但是不建议这样操作。
代码示例:

class cTest:
    def __init__(self):
        print("__init__")
        self.Parameter = 1

    def __del__(self):                      #对象被释放时自动调用
        print("__del__")

Object = cTest()
print("--------------------------")
Object.__init__()                          #主动调用构造函数不会让对象被重新创建
Object.__del__()                           #主动调用析构函数不会让对象被释放
print(Object.Parameter)                    #对象没有被释放,此时操作对象依旧有效

运行结果:

__init__
--------------------------
__init__
__del__
1
__del__

  可以看到最后又输出了一次__del__,这是因为程序运行结束,超出了对象的作用域范围因此引起对象被自动释放从而调用了析构函数。

4 类中的变量

  类内的变量可以分为三类,一类是在类中但不在方法内的变量,一类是在构造函数内的,一类是在普通成员函数内的。

4.1 类内变量

  我们把在类中,但不在方法内的变量称为类内变量。类内变量初始化定义在类中,定义时不需要通过self将其与对象关联起来。类定义后类内变量即存在,可以通过类访问,即使类还没有实例化任何对象。也可通过对象访问类内变量,当对象没有定义自己的类内变量时访问的都是类的类内变量,也即每个对象可以拥有自己独立的类内变量,但这需要在定义后否则默认都是类的类内变量。

class cTest:
    Parameter1 = 1                      #Parameter1是类变量,不需要通过self调用

    def __init__(self):
        pass

Object1 = cTest()
print(cTest.Parameter1)                 #类内变量通过类访问
print(Object1.Parameter1)               #类内变量通过对象访问

print("---------------1------------------")
#在对象未定义类内变量时,访问对象的类内变量会自动访问类的类内变量
Object2 = cTest()
print(Object2.Parameter1)
cTest.Parameter1 = 10                   #通过类名修改类内变量
print(cTest.Parameter1)
print(Object1.Parameter1)
print(Object2.Parameter1)
print("---------------2-----------------")
#给对象创建类内变量后其就具有了独立的类内变量,值不再等于类的类内变量
Object1.Parameter1 = 20                 #通过对象修改类内变量,也即给对象Object1创建类内变量
print(cTest.Parameter1)
print(Object1.Parameter1)
print(Object2.Parameter1)

print("---------------3-----------------")
cTest.Parameter1 = 30                   #通过类名修改类内变量
print(cTest.Parameter1)
print(Object1.Parameter1)
print(Object2.Parameter1)

print("---------------4----------------")
Object2.Parameter1 = 40                 #通过对象修改类内变量,也即给对象Object2创建类内变量
print(cTest.Parameter1)
print(Object1.Parameter1)
print(Object2.Parameter1)

print("---------------5----------------")
#对象的类内变量删除后,访问对象的类内变量即访问类的类内变量
del Object1.Parameter1                  #删除对象的类内变量
del Object2.Parameter1                  #删除对象的类内变量
print(cTest.Parameter1)
print(Object1.Parameter1)
print(Object2.Parameter1)

运行结果:

1
1
---------------1------------------
1
10
10
10
---------------2-----------------
10
20
10
---------------3-----------------
30
20
30
---------------4----------------
30
20
40
---------------5----------------
30
30
30

4.2 成员变量

  成员变量为定义在构造函数内的变量,它需要通过self将其与对象关联起来使得可以通过对象进行访问。因为定义在构造函数内,因此成员变量在对象创建后(也即构造函数调用后)即存在。每个对象都具有自己独立的一份成员变量。
代码示例:

class cTest:
    def __init__(self):
        self.Parameter1 = 1             #置于构造函数内,对象被创建后即存在
        Parameter2 = 2                  #置于构造函数内,对象被创建后即存在,但构造函数只能调用一次,这样定义没有价值

Object1 = cTest()                       #构造函数调用后成员变量即存在
print(Object1.Parameter1)               #访问成员变量。因为使用了self将其与特定对象关联,因此可以通过对此昂访问
#print(Object1.Parameter2)              #error 函数内的局部变量,定义时未使用self与对象关联因此无法通过对象访问

Object2 = cTest()
print(Object2.Parameter1)

print("------------------------------")
Object1.Parameter1 = 0                  #修改对象Object1的成员变量,不会引起其他对象成员变量的改变
print(Object1.Parameter1)
print(Object2.Parameter1)

运行结果:

1
1
------------------------------
0
1

4.2 成员函数内局部变量

  成员函数内局部变量和普通函数内局部变量类似。不同在于类的成员函数内局部变量可被self关联到对象,使得能通过对象访问。
代码示例:

class cTest:
    def Function(self):
        self.Parameter1 = 1             #局部变量被self关联到对象
        Parameter2 = 2                  #未通过self关联到对象

Object1 = cTest()
Object1.Function()                      #成员函数调用后成员函数内局部变量即存在
print(Object1.Parameter1)               #访问成员变量。因为使用了self将其与特定对象关联,因此可以通过对象访问
#print(Object1.Parameter2)              #error 函数内的局部变量,定义时未使用self与对象关联因此无法通过对象访问

Object2 = cTest()
Object2.Function()
print(Object2.Parameter1)

print("------------------------------")
Object1.Parameter1 = 0                  #修改对象Object1的成员变量,不会引起其他对象成员变量的改变
print(Object1.Parameter1)
print(Object2.Parameter1)

运行结果:

1
1
------------------------------
0
1

  类成员函数内的局部变量在成员函数调用后存在。不同的类拥有自己独立的成员函数,也相应是独立的成员函数内局部变量。被self修饰后的局部变量可被子类继承。

5 类的继承

  类的继承描述的是一种子类和父类之间的关系。它是面向对象的一个重要概念,目的是增强代码的重用性。继承是在已有类的基础上创建新的类,已有类称为基类(也称父类、超类),新类称为派生类(也称子类)。派生类可以使用基类中定义的公有型成员变量或成员函数(私有属性和方法不能不能被子类继承)。一个派生类可以只继承自一个基类,称为单继承,也可以继承自多个基类,称为多继承。
  语法上,类定义时在类名后()内的类名即表示该类继承自的基类,只写一个基类时是单继承,多个则为多继承,没有时可省略空的()

5.1 单继承

  我们尝试定义三个类,分别是:HumanManWoman,用Human作为ManWoman的基类。试着通过派生类去访问父类的方法和属性。
代码示例:

class Human:                                       #定义基类
    def __init__(self, Name, Height):              #Human的构造函数
        print("Call Human:__init__")
        self.Name = Name
        self.Age = 0
        self.Height = Height

    #类中的方法(类的成员函数)
    def AddHeight(self):
        self.Height += 1
        return self.Height

    def AddAge(self):
        self.Age += 1
        return self.Age

class Man(Human):
    def __init__(self, Name, Height):
        print("Call Man:__init__")
        self.Gender = "Man"                                 #性别为男
        Human.__init__(self, Name, Height)         #调用父类构造函数。注意第一个参数是self,表示将子类对象传给父类

    #打猎
    def Hunting(self):
        print("hunting")

    #谈论年龄和身高
    def Speak(self):
        print(self.Age, self.Height)               #派生类成员方法中调用基类的属性

    #成长
    def GrowUp(self):
        Human.AddAge(self)                         #派生类成员方法中调用基类成员方法
        Human.AddHeight(self)

class Woman(Human):
    def __init__(self, Name, Height):
        print("Call Man:__init__")
        self.Gender = "Woman"
        super().__init__(Name, Height)         #调用父类构造函数。第一个参数不需是self

    def Pick(self):
        print("pick")

    # 谈论年龄和身高
    def Speak(self):
        print(self.Age, self.Height)           #派生类成员方法中调用基类的属性

    # 成长
    def GrowUp(self):
        super().AddAge()                       #派生类成员方法中调用基类成员方法
        super().AddHeight()


Calm = Man("Calm", 50)                             #实例化Man对象
print("---------------------------")
XiaoHua = Woman("XiaoHua", 48)                     #实例化Woman对象

print("---------------------------")
print(Calm.Age, Calm.Height)                       #派生类成员调用继承自基类的属性
Calm.AddAge()                                      #派生类成员调用继承自基类的方法
Calm.AddHeight()
print(Calm.Age, Calm.Height)

print("---------------------------")
print(Calm.Gender)                                 #派生类调用自己的属性
print(XiaoHua.Gender)
Calm.Hunting()                                     #派生类调用自己特有的方法
XiaoHua.Pick()

print("---------------------------")
Calm.GrowUp()
Calm.Speak()
print("---------------------------")
XiaoHua.GrowUp()
XiaoHua.Speak()

运行结果:

Call Man:__init__
Call Human:__init__
---------------------------
Call Man:__init__
Call Human:__init__
---------------------------
0 50
1 51
---------------------------
Man
Woman
hunting
pick
---------------------------
2 52
---------------------------
1 49

  派生类可以继承所有基类的公有属性和方法。通过派生类对象访问基类的属性和方法可直接 派生类对象名.基类属性名/方法名 ,与访问派生类自己的方法和属性语法一致。在派生类的方法中访问基类的属性可通过 self.基类属性名 ,与派生类访问自己的属性一致。在派生类中访问基类的方法时有两种形式, 基类类名.基类方法名 ,但是需要注意这种形式基类方法调用参数列表的第一个参数必须是传递基类对象自身,也即第一个参数需要为self,另外一种形式是 super().基类方法名 ,这种形式参数列表里不需要self,super是python3的内置函数,单继承推荐这种写法,简洁。
  在派生类对象产生是会自动调用派生类的构造函数,而基类的构造函数需要在派生类的构造函数里手动显式调用。

5.2 多继承

  当一个l类有多个基类时,派生类和多个基类之间就形成了多继承的关系。派生类可以继承基类全部的全局属性和方法。
代码示例:

class cA:
    ParameterA1 = 1

    def __init__(self):
        print("call A __init__")
        self.ParameterA2 = 2

    def FunctionA(self):
        self.ParameterA3 = 3

class cB():
    ParameterB1 = 1

    def __init__(self):
        print("call B __init__")
        self.ParameterB2 = 2

    def FunctionB(self):
        self.ParameterB3 = 3

class cC(cA, cB):
    ParameterC1 = 1

    def __init__(self):
        #super().__init__()                         #该函数只能调用类继承列表里的第一个类(即cA)的构造函数
        cA.__init__(self)
        cB.__init__(self)
        print("call C __init__")
        self.ParameterC2 = 2

    def FunctionC(self):
        self.ParameterC3 = 3

Object = cC()
print(Object.ParameterA1)
print(Object.ParameterB1)
print(Object.ParameterC1)
print("-------------------------------")
print(Object.ParameterA2)
print(Object.ParameterB2)
print(Object.ParameterC2)
print("-------------------------------")
Object.FunctionA()
Object.FunctionB()
Object.FunctionC()
print(Object.ParameterA3)
print(Object.ParameterB3)
print(Object.ParameterC3)

运行结果:

call A __init__
call B __init__
call C __init__
1
1
1
-------------------------------
2
2
2
-------------------------------
3
3
3

  基类构造函数调用父类构造函数时,由于多继承有多个父类构造函数因此调用基类的构造函数需要使用 类名.构造函数名 形式,构造函数参数列表第一个参数必须是传递对象本身即self。派生类对象可以使用所有基类的公有属性和方法,当然派生类自己的成员方法和属性也可以调用。派生类的构造函数里分别调用了基类cAcB的构造函数。

5.3 多级继承

  如果存在A、B、C三个类,B继承于A,C继承于B,那么实例化一个C类的对象,这个对象拥有C类全部的属性和方法,并且具有A和B全部的公有属性和方法。
代码示例:

class cA:
    ParameterA1 = 1

    def __init__(self):
        print("call A __init__")
        self.ParameterA2 = 2

    def FunctionA(self):
        self.ParameterA3 = 3

class cB(cA):
    ParameterB1 = 1

    def __init__(self):
        print("call B __init__")
        super().__init__()
        self.ParameterB2 = 2

    def FunctionB(self):
        self.ParameterB3 = 3

class cC(cB):
    ParameterC1 = 1

    def __init__(self):
        print("call C __init__")
        super().__init__()
        self.ParameterC2 = 2

    def FunctionC(self):
        self.ParameterC3 = 3

Object = cC()
print(Object.ParameterA1)
print(Object.ParameterB1)
print(Object.ParameterC1)
print("-------------------------------")
print(Object.ParameterA2)
print(Object.ParameterB2)
print(Object.ParameterC2)
print("-------------------------------")
Object.FunctionA()
Object.FunctionB()
Object.FunctionC()
print(Object.ParameterA3)
print(Object.ParameterB3)
print(Object.ParameterC3)

运行结果:

call C __init__
call B __init__
call A __init__
1
1
1
-------------------------------
2
2
2
-------------------------------
3
3
3

  实例化类cC的对象时,自动调用类cC的构造函数,在类cC的构造函数里又调用了基类cB的构造函数,然后在类cB的构造函数里又调用了类cA的构造函数。多级继承就是这样层层找基类。与多继承类似,类cC的对象object可以使用类cC的全部方法和属性,且继承了类cB和类cA的全部公有属性和方法。

6 方法重载(覆写)

  方法重载不止限于类中,普通函数也可以被重载。c++中两个函数函数名相同但是参数列表不同会被认为是不同的函数,可以同时存在,但是在python3只要函数名相同就被认为是同一个函数,并且最终调用会指向最新一次的函数定义。

6.1 普通函数重载

代码示例:

def Function1(n):
    print("Function A")

def Function1(n, m):               #重载了Function1并且参数列表不同
    print("Function B")

def Function2():
    print("Function C")

def Function2():                    #重载了Function2
    print("Function D")

#Function1(1)                       #error 一个参数的方法已被重载
Function1(1, 2)
Function2()

运行结果:

Function B
Function D

  Function2被定义了两次,第二次会覆盖第一次定义。Function1虽然两次定义参数列表不同,但是函数名相同,第二次定义依然会覆盖第一次定义。总之python3以函数名作为函数区分,重复定义相同的函数名会将原来的实现覆盖。

6.2 类方法重载

  类具有继承性,因此关于类的方法重载有两种情况,一种是重载自己的方法,一种是重载父类的方法。

6.2.1 类重载自己的方法

  与普通函数重载类似。
代码示例:

class cA:
    def Function1(self):                     #Function第一次定义
        print("Function1 1")

    def Function1(self):                     #重载了旧的定义
        print("Function1 2")

    def Function2(self, n):
        print("Function2 1")

    def Function2(self, n, m):
        print("Function2 2")

Object = cA()
Object.Function1()
#Object.Function1(1)                          #error 一个参数的方法已被重载
Object.Function2(1, 2)

运行结果:

Function1 2
Function2 2

  函数名相同,新的定义覆盖了旧的定义。

6.2.2 类重载基类的方法和属性

  派生类可以继承基类的公有方法,可以在派生类中对基类的公有方法进行重载。基类方法被派生类重载后,派生类对象仍然可以通过super内置函数访问到基类的被重载方法。类中除了方法还具有属性,派生类可以通过定义同名属性来重载基类的属性。
代码示例:

class cA:
    Parameter1 = 1

    def __init__(self):
        self.Parameter2 = 1

    def Function1(self):
        print("cA Function1")
        self.Parameter3 = 1

    def Function2(self, n):
        print("cA Function2")

class cB(cA):
    Parameter1 = 2

    def __init__(self):
        super().__init__()
        self.Parameter2 = 2

    def Function1(self):
        print("cB Function1")
        self.Parameter3 = 2

    def Function2(self, n, m):
        print("cB Function2")


Object = cB()
Object.Function1()                              #派生类重载了基类的方法,此时调用的是基类重载后的方法
print(Object.Parameter1)
print(Object.Parameter2)
print(Object.Parameter3)
print("------------------------")
Object.Function2(1, 2)                          #此时调用的是基类重载后的方法
print("------------------------")
super(cB, Object).Function2(2)                  #通过super调用到了重载前的基类方法
print(super(cB, Object).Parameter1)             #通过super函数访问到了基类的类内变量
#print(super(cB, Object).Parameter2)            #不能通过super访问到基类的成员变量
#print(super(cB, Object).Parameter3)            #不能通过super访问到基类的

运行结果:

cB Function1
2
2
2
------------------------
cB Function2
------------------------
cA Function2
1

6.3 类的运算符重载

  和c++一样,python3的运算符也支持重载,方便成员的运算。python3为类提供了一些专有的方法,它们可被重载。
常见的类运算符重载方法:

函数名 含义
init 构造函数
del 析构函数
repr 打印转换
len 获取长度
cmp 比较运算
call 函数调用
add 加运算
sub 减运算
truediv 除运算
mod 求余运算
pow 乘方

  类的内置函数还有很多,以上只是常用的,它们都可以被重载,即用户去定义实现规则。
  以下代码展示了对象之间的运算
代码示例:

class Arithmetic:
    def __init__(self, Num):
        self.Num = Num

    def __str__(self):                              #重载对象的打印输出
        return "Num: %d" % (self.Num)

    def __add__(self, other):                       #重载两个对象加运算
        print("__add__")
        return self.Num + other.Num

    def __sub__(self, other):                       #重载两个对象差运算
        print("__sub__")
        return self.Num - other.Num

    def __mul__(self, other):
        print("__mul__")
        return self.Num * other.Num

    def __truediv__(self, other):
        print("__mul__")
        return self.Num / other.Num

Object1 = Arithmetic(10)
Object2 = Arithmetic(2)
print(Object1)                                       #打印对象
print(Object2)                                       #打印对象
print(Object1 + Object2)
print(Object1 - Object2)
print(Object1 * Object2)
print(Object1 / Object2)

运行结果:

Num: 10
Num: 2
__add__
12
__sub__
8
__mul__
20
__mul__
5.0

  当类的内置方法被重载后,其对象进行的相关运算会调用类重载运算符后的方法进行运算。
  以下代码展示了对象与数值之间的运算。
代码示例:

class Arithmetic:
    def __init__(self, Num):
        self.Num = Num

    def __str__(self):                              #重载对象的打印输出
        return "Num: %d" % (self.Num)

    def __add__(self, Num):                         #重载对象与数值加运算
        print("__add__")
        return self.Num + Num

Object = Arithmetic(10)
print(Object + 3)                                   #对象与数值相加

运算结果:

__add__
13

7 类的私有属性和方法

  python3中属性或方法名前加上两个下划线_前缀,声明该属性或方法为私有。私有属性或方法不能再类的外部被使用或直接访问。私有属性和方法不能被继承。未加双_的为公有属性或方法。

7.1 类中私有属性和方法

代码示例:

class cTest:
    __Parameter1 = 1

    def __init__(self):
        self.__Parameter2 = 2

    def Function1(self):
        self.__Parameter3 = 3

    def __Function2(self):
        print("__Function2")

    def Function3(self):                            #公有方法
        self.__Function2()
        print(self.__Parameter1)                    #成员方法访问私有类内变量
        print(self.__Parameter2)                    #成员方法访问私有成员变量
        print(self.__Parameter3)                    #成员方法访问私有函数内局部变量

Object = cTest()
#print(Object.__Parameter1)                         #error 私有类内变量不能被外部访问
#print(cTest.__Parameter1)
#print(Object.__Parameter2)                         #error 私有成员变量不能被外部访问
Object.Function1()
#print(Object.__Parameter3)                         #error 私有成员函数内局部变量不能被外部访问
#Object.__Function2()                               #error 私有成员方法不能被外部访问
Object.Function3()                                  #通过公有成员函数去间接访问私有属性和方法

运行结果:

__Function2
1
2
3

  虽然私变量不能被外部访问,但是可以通过公有方法去间接访问私有属性或方法。

7.2 类继承时的私有属性和方法

  公有的属性(函数内部未被self修饰的局部变量不能被继承)和方法可被子类继承。
代码示例:

class cA:
    __Parameter1 = 1

    def __init__(self):
        self.__Parameter2 = 2

    def Function1(self):
        self.__Parameter3 = 3

    def __Function2(self):
        print("__Function2")

    def Function3(self):                            #公有方法
        self.__Function2()
        print(self.__Parameter1)                    #成员方法访问私有类内变量
        print(self.__Parameter2)                    #成员方法访问私有成员变量
        print(self.__Parameter3)                    #成员方法访问私有函数内局部变量

class cB(cA):
    pass

Object = cB()
#print(Object.__Parameter1)                         #error 私有类内变量不能被继承
#print(cB.__Parameter1)
#print(Object.__Parameter2)                         #error 私有成员变量不能继承
Object.Function1()
#print(Object.__Parameter3)                         #error 私有成员函数内局部变量不能被继承
#Object.__Function2()                               #error 私有成员方法不能被继承
Object.Function3()                                  #通过继承到的公有成员函数去间接访问继承到的私有属性和方法

运行结果:

__Function2
1
2
3
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章