Python3基础篇(八)——面向对象
前言
阅读这篇文章我能学到什么?
这篇文章将为你详细介绍Python3中的面向对象程序设计,你将学会如何定义一个类和使用一个对象。
Python3是一门面向对象的语言,掌握好面向对象的程序设计是用好Python3的前提。如果你理解c++的“万物皆对象”概念那学习起来就轻松很多了。
面向对象是一个抽象的程序设计概念,是软件开发的方法。起初它来自于程序设计,而如今面向对象概念已经扩展到数据库系统、交互结构、应用结构、应用平台、分布式系统、网络管理结构、CAD计数、人工智能等领域。这种思想就像种子一样已经到处传播并发芽生长。
对象编程将任何事物(可以是客观存在或抽象的,客观的比如桌子板凳,抽象的比如时间和爱情)都看做具有 属性 和 方法 的对象,又从同一类事物中抽象出他们的 共性 (比如人类共有的属性是年龄和体重,共有的方法是学习和吃饭)形成类。这就是 面向对象 的概念,也是对象和类的关系。
——作者:这段话是我自己总结的,如有不对欢迎指正
通过上面的描述我们知道了对象是描述事物的,而类是对象的共性的抽象。 面向对象编程 (注意区分面向对象和面向对象编程)就是以操控对象的形式去编程,要创建对象就必须为对象定义相应的类,类描述了对象的属性和方法。
1 定义带有属性和方法的简单类
Python3中使用关键字class
定义类,后面接类名,不要忘了后面的:
符号,类主体至少要比class缩进一个空格或table。类的主体中定义属性和方法。
属性是描述对象的共性特征的,比如年龄和身高,对应编程语言中存储数据的变量。方法是对象能完成的动作,比如“我”可以写博客也可以吃饭,对应编程语言能完成特定功能的函数。 面向对象的三大特征是封装、继承、多态 ,其中封装和继承主要是为了增加代码的重用性,多态主要是为了以不变的代码应对万变的需求。
下面我们来设计一个类——Human,Human的共性就是都具有年龄和身高,年龄会长大而身高会长高。当然还具有很多其他共性,我们不可能一一写出来,只需要设计出我们关心的几个共性。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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()
print(Calm.Height) print(Calm.Age) print(Calm.AddHeight()) print(Calm.AddAge())
|
运行结果:
类中定义了属性Age
和Height
,并设定了初始值18和183,AddHeight
和AddAge
为类的方法。类定义好后Calm = Human()
这句代码即实现将描述对象共性的类实例化出一个具体的对象Calm
,这就像工厂里的模具能取出形状大小一样的物件一样。我们说过面向对象编程是把事物都看做具有属性和方法的对象,面向对象编程是通过操控的形式编程。现在有了对象Calm,我们就可以通过它去访问对象Calm的属性和方法了。通过成员运算符.
即可访问到该对象下的属性和方法。
上面代码里的self
是什么作用?Python3规定,类中的方法区别于普通函数,类方法参数列表的第一个参数(Python3并没有规定第一个参数名只能是self
,只要符合命名规则即可,用self
是大家通用的习惯)必须用于接收对象自己。这样做有三个目的:
- 从写法上区别于普通函数。
- 将函数和对象绑定在一起,这样就清楚这个函数对应哪个对象了。
- 方便在类方法中操作类中的其他属性和方法。
为什么说self
就是对象自己呢?我们写一段简单的代码输出看一下对象地址,输出看一下self
就知道了
代码示例:
1 2 3 4 5 6 7 8 9 10 11
| class cTest: def Function(self): print(self)
Test1 = cTest() Test1.Function() print(Test1)
Test2 = cTest() Test2.Function() print(Test2)
|
运行结果:
1 2 3 4
| <__main__.cTest object at 0x0000010DDA0C7400> <__main__.cTest object at 0x0000010DDA0C7400> <__main__.cTest object at 0x0000010DDA142CA0> <__main__.cTest object at 0x0000010DDA142CA0>
|
从上面代码可以看出对象Test1
和Test2
是两个不同的对象,因此具有不同的对象地址(对应计算机上存储空间不同),但是它们各自的self
是完全等价于对象自己的。就类似于c++和java中的this指针。
明白了self
后现在我们回到Human
这个类的话题。AddHeight
和AddAge
的第一参数是self
即表明了它们是类的成员方法,同时将成员方法和对象关联起来,在成员方法拿到对象实例后就可以在成员方法内部访问对象的属性和方法。所以self.Height
和self.Age
是成员方法通过对象自己的实例访问了自己的属性。并且有了self
将对象和方法关联起来,100个Human
对象self.Age
访问的都是自己独立的属性Age
。(各有各的年龄)。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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() XiaoMing = Human() print(Calm.Age) print(XiaoMing.Age) print("-------------------------------") XiaoMing.Age = 19 print(Calm.Age) print(XiaoMing.Age)
|
运行结果:
1 2 3 4 5
| 18 18 ------------------------------- 18 19
|
2 类的构造函数
我们从Human
类创建了两个对象Calm
和XiaoMing
,它们的年龄都是18岁。如果我们要实现这样一个功能,对象创建时默认初始年龄是0岁并且具有初始升高(这更符合Human
类的特点)。这就涉及到构造函数的概念。构造函数是一种特殊的类内方法,在对象被创建(实例化)时构造函数自动被调用,它的主要作用是对对象的成员变量进行初始化赋值。Python3的构造函数名固定为__init__
。构造函数可以省略也可被用户重载,省略时作用相当于没有参数和函数体的无参构造函数。
2.1 类的无参构造函数
代码示例:
1 2 3 4 5 6 7 8 9
| class cTest: def __init__(self): print("call: __init__(self)") self.Parameter = 0 print(self.Parameter)
Object1 = cTest()
|
运行结果:
当一个类没有__init__
构造函数时,系统会默认调用无参无函数体的构造函数。当用户重载__init__
构造函数时,创建对象时将会调用用户定义后的构造函数。
2.2 类的带参构造函数
构造函数重载后可带参,类实例化为对象时参数列表按照带参构造函数参数列表。
1 2 3 4 5 6 7 8 9
| class cTest: def __init__(self, Parameter1, Parameter2): print("call: __init__(self, Parameter1, Parameter2)") self.Parameter = Parameter1 * Parameter2 print(self.Parameter)
Object1 = cTest(2, 5)
|
运行结果:
1 2
| call: __init__(self, Parameter1, Parameter2) 10
|
2.3 构造函数与普通成员函数区别
- 构造函数函数名只能是__init__,而普通成员函数函数名完全有用户定义。
- 普通程序函数定义后才存在,未定义也就不存在,而每个类都自带一个默认构造函数(作用相当于无参无函数体构造函数),可由用户重载。
- 普通成员函数需要用户主动去调用,而构造函数在对象创建(实例化)时自动调用。普通成员函数可以被调用多次,而构造函数只在对象创建时自动调用一次。
- 用法上普通成员函数一般用于实现逻辑,构造函数一般用于对成员变量进行初始化。
- 数量上一个类可以有多个普通成员函数,但只能有一个构造函数。
2.4 构造函数的多态
在c++和Java里类中构造函数可以有多个,只要他们参数列表不同(参数个数、参数顺序、参数类型)即可。而Python3中类的构造函数只能有一个。面向对象的三大特征是封装、继承、多态。Python3的一个构造函数怎么实现构造函数的多台呢?
我们知道Python3的变量是不分类型的,这一点可以实现不同参数类型的构造函数以及参数顺序不同的构造函数。其次Python3的函数参数是可以带默认值的,这一点可以实现参数个数不同的构造函数。另外Python3的函数支持变长参数,这一点也可以实现不同参数个数的构造函数。这些特性支撑起了Python3构造函数的多态性。
2.4.1 构造函数多态——参数类型不一致
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12
| class cTest: def __init__(self, Parameter): self.Parameter = Parameter print(self.Parameter)
Object1 = cTest(2) Object2 = cTest("a") Object3 = cTest((1, 2)) Object4 = cTest([1, 2]) Object5 = cTest({1, 2}) Object6 = cTest({"a":1, "b":2})
|
运行结果:
1 2 3 4 5 6
| 2 a (1, 2) [1, 2] {1, 2} {'a': 1, 'b': 2}
|
根据Python3变量没有类型的特性,实现了构造函数的不同参数类型下的多态。
2.4.2 构造函数多态——参数顺序不一致
代码示例:
1 2 3 4 5 6 7 8
| class cTest: def __init__(self, Parameter1, Parameter2): self.Parameter1 = Parameter1 self.Parameter2 = Parameter2 print(self.Parameter1, self.Parameter2)
Object1 = cTest(1, "a") Object2 = cTest("a", 1)
|
运行结果:
根据Python3变量没有类型的特性,实现了构造函数的不同参数类型顺序下的多态。
2.4.3 构造函数多态——参数个数不一致
代码示例:
1 2 3 4 5 6 7 8
| class cTest: def __init__(self, *Parameter): self.Parameter = Parameter print(self.Parameter)
Object1 = cTest(1) Object2 = cTest(1, "a") Object3 = cTest(1, "a", 2, 3)
|
运行结果:
1 2 3
| (1,) (1, 'a') (1, 'a', 2, 3)
|
根据Python3变量没有类型的特性,实现了构造函数的不同参数数量下的多态。
因此Python3实现构造函数的多态性并不需要像c++和java那样一个类重载多个构造函数,它只需要一个构造函数就够了。
我们继续前面的Human类的例子。我们利用构造函数实现每个Human对象产生后Age初始为0,并且具有初始Height。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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) print(Calm.Age, Calm.Height)
|
运行结果:
3 类的析构函数
Python3中的变量都可被主动(使用del关键字)或被动(作用域结束)释放(回收内存空间),当对象被释放时会自动调用一次析构函数。每个类都有一个析构函数,当用户未显式定义时会调用默认的析构函数,否则会调用用户重载的析构函数。默认析构函数作用等同于无函数体的析构函数。析构函数的函数名是固定的即__del__
代码示例:
1 2 3 4 5 6 7 8 9 10 11
| class cTest: def __init__(self): print("__init__")
def __del__(self): print("__del__")
Object = cTest() print("--------------------------") del Object
|
运行结果:
1 2 3
| __init__ -------------------------- __del__
|
请勿给析构函数定义除了self
外的其他参数。
上面讲了Python3的构造函数和析构函数,它们有其特殊性,但是如果你通过对象主动去调用也不会报错,构造函数和析构函数会像普通函数那样执行。但是不建议这样操作。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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)
|
运行结果:
1 2 3 4 5 6
| __init__ -------------------------- __init__ __del__ 1 __del__
|
可以看到最后又输出了一次__del__
,这是因为程序运行结束,超出了对象的作用域范围因此引起对象被自动释放从而调用了析构函数。
4 类中的变量
类内的变量可以分为三类,一类是在类中但不在方法内的变量,一类是在构造函数内的,一类是在普通成员函数内的。
4.1 类内变量
我们把在类中,但不在方法内的变量称为类内变量。类内变量初始化定义在类中,定义时不需要通过self
将其与对象关联起来。类定义后类内变量即存在,可以通过类访问,即使类还没有实例化任何对象。也可通过对象访问类内变量,当对象没有定义自己的类内变量时访问的都是类的类内变量,也即每个对象可以拥有自己独立的类内变量,但这需要在定义后否则默认都是类的类内变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| class cTest: Parameter1 = 1
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 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 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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
将其与对象关联起来使得可以通过对象进行访问。因为定义在构造函数内,因此成员变量在对象创建后(也即构造函数调用后)即存在。每个对象都具有自己独立的一份成员变量。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class cTest: def __init__(self): self.Parameter1 = 1 Parameter2 = 2
Object1 = cTest() print(Object1.Parameter1)
Object2 = cTest() print(Object2.Parameter1)
print("------------------------------") Object1.Parameter1 = 0 print(Object1.Parameter1) print(Object2.Parameter1)
|
运行结果:
1 2 3 4 5
| 1 1 ------------------------------ 0 1
|
4.2 成员函数内局部变量
成员函数内局部变量和普通函数内局部变量类似。不同在于类的成员函数内局部变量可被self
关联到对象,使得能通过对象访问。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class cTest: def Function(self): self.Parameter1 = 1 Parameter2 = 2
Object1 = cTest() Object1.Function() print(Object1.Parameter1)
Object2 = cTest() Object2.Function() print(Object2.Parameter1)
print("------------------------------") Object1.Parameter1 = 0 print(Object1.Parameter1) print(Object2.Parameter1)
|
运行结果:
1 2 3 4 5
| 1 1 ------------------------------ 0 1
|
类成员函数内的局部变量在成员函数调用后存在。不同的类拥有自己独立的成员函数,也相应是独立的成员函数内局部变量。被self
修饰后的局部变量可被子类继承。
5 类的继承
类的继承描述的是一种子类和父类之间的关系。它是面向对象的一个重要概念,目的是增强代码的重用性。继承是在已有类的基础上创建新的类,已有类称为基类(也称父类、超类),新类称为派生类(也称子类)。派生类可以使用基类中定义的公有型成员变量或成员函数(私有属性和方法不能不能被子类继承)。一个派生类可以只继承自一个基类,称为单继承,也可以继承自多个基类,称为多继承。
语法上,类定义时在类名后()
内的类名即表示该类继承自的基类,只写一个基类时是单继承,多个则为多继承,没有时可省略空的()
。
5.1 单继承
我们尝试定义三个类,分别是:Human
、Man
、Woman
,用Human
作为Man
和Woman
的基类。试着通过派生类去访问父类的方法和属性。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| class Human: def __init__(self, Name, Height): 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)
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)
def Pick(self): print("pick")
def Speak(self): print(self.Age, self.Height)
def GrowUp(self): super().AddAge() super().AddHeight()
Calm = Man("Calm", 50) print("---------------------------") XiaoHua = Woman("XiaoHua", 48)
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()
|
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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类有多个基类时,派生类和多个基类之间就形成了多继承的关系。派生类可以继承基类全部的全局属性和方法。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| 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): 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)
|
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| call A __init__ call B __init__ call C __init__ 1 1 1 ------------------------------- 2 2 2 ------------------------------- 3 3 3
|
基类构造函数调用父类构造函数时,由于多继承有多个父类构造函数因此调用基类的构造函数需要使用 类名.构造函数名 形式,构造函数参数列表第一个参数必须是传递对象本身即self
。派生类对象可以使用所有基类的公有属性和方法,当然派生类自己的成员方法和属性也可以调用。派生类的构造函数里分别调用了基类cA
和cB
的构造函数。
5.3 多级继承
如果存在A、B、C三个类,B继承于A,C继承于B,那么实例化一个C类的对象,这个对象拥有C类全部的属性和方法,并且具有A和B全部的公有属性和方法。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| 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)
|
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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 普通函数重载
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def Function1(n): print("Function A")
def Function1(n, m): print("Function B")
def Function2(): print("Function C")
def Function2(): print("Function D")
Function1(1, 2) Function2()
|
运行结果:
Function2
被定义了两次,第二次会覆盖第一次定义。Function1
虽然两次定义参数列表不同,但是函数名相同,第二次定义依然会覆盖第一次定义。总之Python3以函数名作为函数区分,重复定义相同的函数名会将原来的实现覆盖。
6.2 类方法重载
类具有继承性,因此关于类的方法重载有两种情况,一种是重载自己的方法,一种是重载父类的方法。
6.2.1 类重载自己的方法
与普通函数重载类似。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class cA: def Function1(self): 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.Function2(1, 2)
|
运行结果:
函数名相同,新的定义覆盖了旧的定义。
6.2.2 类重载基类的方法和属性
派生类可以继承基类的公有方法,可以在派生类中对基类的公有方法进行重载。基类方法被派生类重载后,派生类对象仍然可以通过super
内置函数访问到基类的被重载方法。类中除了方法还具有属性,派生类可以通过定义同名属性来重载基类的属性。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| 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) print(super(cB, Object).Parameter1)
|
运行结果:
1 2 3 4 5 6 7 8 9
| 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 |
乘方 |
类的内置函数还有很多,以上只是常用的,它们都可以被重载,即用户去定义实现规则。
以下代码展示了对象之间的运算
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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)
|
运行结果:
1 2 3 4 5 6 7 8 9 10
| Num: 10 Num: 2 __add__ 12 __sub__ 8 __mul__ 20 __mul__ 5.0
|
当类的内置方法被重载后,其对象进行的相关运算会调用类重载运算符后的方法进行运算。
以下代码展示了对象与数值之间的运算。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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)
|
运算结果:
7 类的私有属性和方法
Python3中属性或方法名前加上两个下划线_
前缀,声明该属性或方法为私有。私有属性或方法不能再类的外部被使用或直接访问。私有属性和方法不能被继承。未加双_
的为公有属性或方法。
7.1 类中私有属性和方法
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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()
Object.Function1()
Object.Function3()
|
运行结果:
虽然私变量不能被外部访问,但是可以通过公有方法去间接访问私有属性或方法。
7.2 类继承时的私有属性和方法
公有的属性(函数内部未被self修饰的局部变量不能被继承)和方法可被子类继承。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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()
Object.Function1()
Object.Function3()
|
运行结果: