赞
踩
面向对象是 Python 最重要的特性,在 Python 中一切数据类型都是面向对象的。
Python 支持面向对象的三大特性:封装性、继承性和多态性,子类继承父类同样可以继承到父类的变量和方法。
面向对象编程是最有效的软件编写方法之一。在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,你定义一大类对象都有的通用属性(类的成员变量)和通用行为(类的成员方法)。基于类创建对象时,每个对象都自动具备这种通用属性和通用行为,然后可根据需要赋予每个对象独特的个性。
根据类来创建对象被称为实例化,这让你能够使用类的实例。
面向对象编程的思想是:按照真实世界客观事物的自然规律进行分析,客观世界中存在什么样的实体,构建的软件系统就存在什么样的实体。
例如,在真实世界的学校里,会有学生和老师等实体,学生有学号、姓名、所在班级等属性(数据),学生还有学习、提问、吃饭和走路等操作(方法)。学生只是抽象的描述,这个抽象的描述称为“类”。在学校里活动的是学生个体,即张同学、李同学等,这些具体的个体称为“对象(object)”,对象也称为“类的实例(instance)”。
在现实世界中有类和对象,软件世界中也有类和对象,只不过它们会以某种计算机编程语言编写的程序代码形式存在,这就是面向对象编程(Object Oriented Programming, OOP)。
面向对象编程思想有三个基本特性:封装性、继承性和多态性。
在现实世界中封装的例子比比皆是。例如,一台计算机内部极其复杂,有主板、CPU、硬盘和内存等,而一般用户不需要了解它的内部细节,不需要知道主板的型号、CPU主频、硬盘和内存的大小,于是计算机制造商用机箱把计算机封装起来,对外提供一些接口,如鼠标、键盘和显示器等,这样当用户使用计算机时就变得非常方便。
面向对象的封装和真实世界的目的是一样的。封装能够使外部访问者不能随意存取对象的内部数据,隐藏了对象的内部实现细节,只保留有限的对外接口。外部访问者不用关心对象的内部细节,操作对象变得简单。
在现实世界中继承也是无处不在。例如轮船与客轮之间的关系,客轮是一种特殊的轮船,拥有轮船的全部特征和行为,即数据和操作。在面向对象编程中,轮船是一般的类,客轮是特殊的类,特殊类拥有一般类的全部数据和操作,称为特殊类继承一般类。一般类称为“父类”或“基类”,特殊类称为“子类”或“派生类”。为了统一,本文中一般类统称为“父类”,特殊类统称为“子类”。
多态性是指在父类中成员被子类继承后,可以具有不同的状态或表现的行为。在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。
Python 中的数据类型都是类,类是组成 Python 程序的基本要素,它封装了一类对象的数据(成员变量)和操作(成员方法)。
Python 语言中一个类的实现包括类定义和类体。类定义的语法格式如下:
- class 类名 [ (父类) ]:
- 类体
其中,class 是声明类的关键字,“类名” 是用户自定义的类名,自定义类名首先应该是合法的标识符,且应该遵循 Python 命名规范,采用大驼峰命名法。“父类” 声明当前类继承的父类,父类可以省略声明,表示直接继承 Python 根类 object 类。
例如,定义一个动物(Animal)类代码如下:
- class Animal(object):
- # 类体
- pass
解析:上述代码声明了动物类,它继承了 object 类,object 类是所有类的根类,在 Python 中任何一个动物类都直接或间接继承 object 类,所以 (object) 部分代码可以省略不写。
<提示> 代码的 pass 语句什么操作都不执行,用来维持程序结构的完整。有些不想编写的代码,又不想有语法错误,可以使用 pass 语句占位。
上面章节讲过,类的实例化可生成对象,所以 “对象” 也称为 “实例”。一个对象的生命周期包括三个阶段:创建、使用和销毁。销毁对象时 Python 的垃圾回收机制会自动回收不再使用的对象的内存空间,不需要程序员负责。程序员只需要关心对象的创建和使用。
Python 创建对象很简单,就是在类后面加上一对小括号,表示调用类的构造方法,这就创建了一个对象,示例代码如下:
- # 创建一个Animal类的对象
- animal = Animal()
Animal 类是上一节定义的动物类,Animal() 表达式创建了一个动物类对象,并把创建的对象赋值给 animal 变量,animal 是指向动物类对象的一个引用。通过 animal 变量可以使用刚刚创建的动物类对象。如下代码打印出动物类对象相关信息。
- # coding=utf-8
-
- # 定义一个Animal类
- class Animal(object):
- # 类体
- pass
-
- animal = Animal()
- print(animal)
运行结果:
- > python test.py
- <__main__.Animal object at 0x000001DDEF44A4A0>
解析:print() 函数打印对象会输出一些很难懂的信息。事实上,print() 函数调用了对象的 __str__() 方法输出字符串信息,__str__() 是 object 类的一个方法,它会返回有关该对象的描述信息,由于本例中 Animal 类的 __str__() 方法是默认实现的,所以会返回这些难懂的信息,如果要打印出友好的信息,需要在 Animal 类体中重写 __str__() 方法。
<提示> __str__() 这种双下划线开始和结尾的方法是 Python 保留的,有着特殊的含义,称为魔法方法。
在类体中可以包含类的成员,类成员如下图 1 所示,其中包括成员变量、成员方法和属性。成员变量又分为实例变量和类变量,成员方法又分为实例方法、类方法和静态方法。
<提示> 在 Python 类成员中有 attribute 和 property,见上图1。attribute 是类中保存数据的变量,如果需要对 attribute 进行封装,那么在类的外部为了访问这些 attribute,往往会提供一些 setter 和 getter 访问器。setter 访问器是对 attribute 赋值的方法,getter 访问器是取 attribute 值的方法,这些方法在创建和调用时都比较麻烦,于是 Python 又提供了 property,property 本质上就是 setter 和 getter 访问器,是一种方法。一般情况下,attribute 和 property 中文都翻译为 “属性”,这样很难区分两者的含义,也有很多书将 attribute 翻译为 “特性”。“属性” 和 “特性” 在中文中区别也不大。其实很多编程语言都有 attribute 和 property 概念,例如 Objective-C 中 attribute 称为成员变量(或字段),property 称为属性。本文也将采用 Objective-C 提法,将 attribute 翻译为 “成员变量”,而 property 翻译为 “属性”。
“实例变量” 就是某个实例(或对象)特有的 “数据”,例如你家狗狗的年龄、性别和体重与邻居家狗狗的年龄、性别和体重是不同的。
Python 中定义实例变量的示例代码如下:
- # coding=utf-8
-
- # 定义一个Animal类
- class Animal(object): # --1
- """ 定义动物类 """
- def __init__(self, age, sex, weight): # --2
- self.age = age # 初始化实例变量的年龄 --3
- self.sex = sex # 初始化实例变量的性别
- self.weight = weight # 初始化实例变量的体重
-
- animal = Animal(2, 'male', 10.0) # 创建一个实例变量animal,即一个Animal类对象
- print('年龄: {0}'.format(animal.age)) # --4
- print('性别: {0}'.format(animal.sex))
- print('体重: {0}'.format(animal.weight))
解析:上述代码第 1 处是定义 Animal 动物类,代码第 2 处是构造方法,构造方法是用来创建和初始化实例变量的,有关构造方法下文会再详细介绍,这里不再赘述。构造方法中的 self 指向当前对象实例,在创建对象实例时会自动调用构造方法,并将当前对象实例的引用赋值给形参 self,从而使得 self 也指向当前对象实例,self 类似于C++中的 this 指针的作用。代码第 3 处是在创建和初始化实例变量 age,其中 self.age 表示当前对象实例的实例变量。代码第 4 处是访问 age 实例变量,实例变量需要通过 “实例名.实例变量” 的形式访问。
“类变量”是所有实例(或对象)共有的变量。例如有一个 Account(银行账号)类,它有三个成员变量:amount(账户金额)、interest_rate(利率)和 owner(账户名)。在这三个成员变量中,amount 和 owner 会因人而异,对于不同的账户这些内容是不同的,而所有账户的 interest_rate 是相同的。amount 和 owner 成员变量与账户个体实例有关,称为 “实例变量”,interest_rate 成员变量与个体实例无关,或者说是所有账户实例共享的,这种成员变量称为 “类变量”。
类变量示例代码如下:
- # coding=utf-8
-
- class Account:
- """ 定义银行账户类 """
- interest_rate = 0.0688 # 类变量利率 --1
-
- def __init__(self, owner, amount):
- self.owner = owner # 定义实例变量账户名并初始化
- self.amount = amount # 定义实例变量账户金额
-
- account = Account('Godlike', 2_000_000.0)
-
- print('账户名: {0}'.format(account.owner)) # --2
- print('账户金额: ${0}'.format(account.amount))
- print('利率: {0}'.format(Account.interest_rate)) # --3
运行结果:
- > python test.py
- 账户名: Godlike
- 账户金额: $2000000.0
- 利率: 0.0688
解析:代码第 1 处是创建并初始化类变量。创建类变量与实例变量不同,类变量要在方法之外定义。代码第 2 处是访问实例变量,通过 “实例名.实例变量” 的形式访问。代码第 3 处是访问类变量,通过 “类名.类变量” 的形式访问。“类名.类变量” 事实上是有别于包和模块的另一种形式的命名空间。
<注意> 不要通过实例存取类变量数据。当通过实例读取变量时,Python 解释器会先从实例中找这个变量,如果没有找到再到类中去找;当通过实例为变量赋值时,无论类中是否有该同名变量,Python 解释器都会创建一个同名实例变量。
在上面的类变量示例中添加如下代码:
- print('Account 利率: {0}'.format(Account.interest_rate))
- print('account 利率: {0}'.format(account.interest_rate)) # --1
-
- print('account 实例所有变量:{0}'.format(account.__dict__)) # --2
- account.interest_rate = 0.01 # --3
- account.interest_rate2 = 0.01 # --4
- print('account 实例所有变量:{0}'.format(account.__dict__)) # --5
运行结果:
- Account 利率: 0.0688
- account 利率: 0.0688
- account 实例所有变量:{'owner': 'Godlike', 'amount': 2000000.0}
- account 实例所有变量:{'owner': 'Godlike', 'amount': 2000000.0, 'interest_rate': 0.01, 'interest_rate2': 0.01}
解析:(1)上述代码第 1 处通过实例 account 读取 interest_rate 变量,解释器发现 account 实例中没有该变量,然后会在 Account 类中找,如果类中也没有,会发生 AttributeError 错误。虽然通过实例读取 interest_rate 变量可以实现,但不符合 Python 设计规范。代码第 2 处打印出实例 account 所有的 “实例变量”,可以发现并不包括 “类变量”。
(2)代码第 3 处为 account.interest_rate 变量赋值,这样的操作下无论类中是否有同名类变量,都会创建一个新的实例变量。为了查看实例变量有哪些,可以通过 object 类提供的 __dict__ 变量查看,见代码第 2 处和第 5 处。从输出结果可见,代码第 3 处和代码第 4 处的赋值操作会导致新创建两个实例变量 interest_rate 和 interest_rate2。
<提示> 代码第 3 处和代码第 4 处能够在类之外创建实例变量,主要原因是 Python 的动态语言特性,Python 不能从语法层面禁止此事的发生。这样创建实例变量会引起很严重的问题,一方面,类的设计者无法控制一个类中有哪些成员变量;另一方面,这些实例变量无法通过类中的方法访问,因此,不建议这样做,最好是杜绝。
在 3.3 节和 3.4 节中都使用了 __init__() 方法,该方法用来创建和初始化实例变量,这种方法就是 “构造方法”。__init__() 方法也属于魔法方法。定义时它的第一个参数应该是 self,其后的参数才是用来初始化实例变量的。调用构造方法时不需要传入 self。
构造方法示例代码如下:
- # coding=utf-8
-
- class Animal(object):
- """ 定义动物类 """
- def __init__(self, age, sex, weight = 0.0): # --1
- self.age = age # 定义年龄实例变量
- self.sex = sex # 定义性别实例变量
- self.weight = weight # 定义体重实例变量
-
- a1 = Animal(8, 'male', 5.0) # --2
- a2 = Animal(16, 'male', 10.0)
- a3 = Animal(32, 'female') # --3
-
- print('a1:[age:{0}, sex:{1}, weight:{2}]'.format(a1.age, a1.sex, a1.weight))
- print('a2:[age:{0}, sex:{1}, weight:{2}]'.format(a2.age, a1.sex, a2.weight))
- print('a3:[age:{0}, sex:{1}, weight:{2}]'.format(a3.age, a3.sex, a3.weight))
运行结果:
- >python test.py
- a1:[age:8, sex:male, weight:5.0]
- a2:[age:16, sex:male, weight:10.0]
- a3:[age:32, sex:female, weight:0.0]
解析:上述代码第 1 处是定义构造方法,其中除了第一个参数 self 外,其他的参数可以有默认值,这也提供了默认值的构造方法,能够给调用者提供多个不同形式的构造方法。代码第 2 处和第 3 处是调用构造方法创建 Animal 对象实例,其中不需要传入 self,只需要提供后面的三个实际参数即可。
实例方法与实例变量一样都是某个实例(或对象)个体特有的。方法是在类中定义的函数。而定义实例方法时它的第一个参数也应该是 self,这个过程是将当前实例与该方法绑定起来,使该方法称为实例方法。
下面看一个定义实例方法示例。
- # coding=utf-8
-
- class Animal(object):
- """ 定义动物类 """
- def __init__(self, age, sex, weight = 0.0):
- self.age = age # 定义年龄实例变量
- self.sex = sex # 定义性别实例变量
- self.weight = weight # 定义体重实例变量
-
- def eat(self): # --1
- self.weight += 0.05
- print('eat...')
-
- def run(self): # --2
- self.weight -= 0.01
- print('run...')
-
- a1 = Animal(8, 'male', 10.0)
- print('a1体重:{0:0.02f}'.format(a1.weight))
- a1.eat() # --3
- print('a1体重:{0:0.02f}'.format(a1.weight))
- a1.run() # --4
- print('a1体重:{0:0.02f}'.format(a1.weight))
运行结果:
- >python test.py
- a1体重:10.00
- eat...
- a1体重:10.05
- run...
- a1体重:10.04
解析:上述代码第 1 处和第 2 处声明了两个方法,其中第一个参数是 self。代码第 3 处和第 4 处是调用这些实例方法,注意在调用这两个方法时不需要传入 self 实参。
“类方法” 与 “类变量” 类似也是属于类,不属于个体实例的方法,类方法不需要与实例绑定,但需要与类绑定,定义时它的第一个参数不是 self,而是类的 type 实例。type 是描述 Python 数据类型的类,Python 中所有数据类型都是 type 的一个实例。
定义类方法示例代码如下:
- # coding=utf-8
-
- class Account:
- """ 定义银行账户类 """
- interest_rate = 0.0688 # 类变量利率
-
- def __init__(self, owner, amount):
- self.owner = owner # 定义实例变量账户名
- self.amount = amount # 定义实例变量账户金额
-
- # 定义类方法
- @classmethod
- def interest_by(cls, amt): # --1
- return cls.interest_rate * amt # --2
-
- interest = Account.interest_by(200_000.0) # --3
- print('计算利息: {0:0.04f}'.format(interest))
运行结果:
- >python test.py
- 计算利息: 13760.0000
解析:(1)定义类方法有两个关键:第一,方法第一个参数 cls(见代码第1处)是 type 类型,是当前 Account 类型的实例;第二,方法使用装饰器 @classmethod 声明该方法是类方法。
<提示> 装饰器(Decorators) 是 Python 3.0 之后加入的新特性,以 @ 开头修饰函数、方法和类,用来修饰和约束它们,类似于 Java 中的注解。
(2)代码第2处是方法体,在类方法中可以访问其他的类变量和类方法,cls.interest_rate 是访问类变量 interest_rate。
<注意> 类方法可以访问类变量和其他类方法,但不能访问其他实例方法和实例变量。
(3)代码第3处是调用类方法 interest_by(),采用 “类名.类方法” 形式调用。从语法角度来说,也可以通过实例调用类方法,但这不符合编程规范,同样不建议这样做。
如果定义的方法既不想与实例绑定,也不想与类绑定,只是想把类作为它的命名空间,那么可以定义静态方法。
定义静态方法示例代码如下:
- # coding=utf-8
-
- class Account:
- """ 定义银行账户类 """
- interest_rate = 0.0688 # 类变量利率
-
- def __init__(self, owner, amount):
- self.owner = owner # 定义实例变量账户名
- self.amount = amount # 定义实例变量账户金额
-
- # 定义类方法
- @classmethod
- def interest_by(cls, amt):
- return cls.interest_rate * amt
-
- # 定义静态方法
- @staticmethod
- def interest_with(amt): # --1
- return Account.interest_by(amt) # --2
-
- interest1 = Account.interest_by(200_000.0)
- print('计算利息1: {0:0.04f}'.format(interest1))
- interest2 = Account.interest_with(200_000.0) # --3
- print('计算利息2: {0:0.04f}'.format(interest2))
-
- account = Account('Godlike', 500_000.0)
- interest3 = account.interest_with(account.amount) # --4
- print('计算利息3: {0:0.04f}'.format(interest3))
运行结果:
- >python test.py
- 计算利息1: 13760.0000
- 计算利息2: 13760.0000
- 计算利息3: 34400.0000
解析:上述代码第1处是定义静态方法,使用了 @staticmethod 装饰器,声明方法是静态方法,方法参数不指定 self 和 cls。代码第2处定义了类方法。定义静态方法与调用类方法类似,都是通过类名实现(见代码第3处),但也可以通过对象实例调用静态方法(见代码第4处)。
类方法与静态方法在很多场景下是类似的,只是在定义时有一些区别。类方法需要绑定类,静态方法不需要绑定类,静态方法与类的耦合度更加松散。在一个类中定义静态方法只是为了提供一个基于类名的命名空间。
封装性是面向对象编程的三大特性之一,Python 语言没有与封装性相关的关键字,它通过特定的名称实现变量和方法的封装。
默认情况下 Python 中的变量是公有的,可以在类的外部访问它们。如果想让它们成为私有变量,可以在变量前加上双下划线 "__"。
示例代码如下:
- # coding=utf-8
-
- class Animal(object):
- """ 定义动物类 """
- def __init__(self, age, sex, weight = 0.0):
- self.age = age # 定义年龄实例变量
- self.sex = sex # 定义性别实例变量
- self.__weight = weight # 定义体重实例变量 --1
-
- def eat(self):
- self.__weight += 0.05
- print('eat...')
-
- def run(self):
- self.__weight -= 0.01
- print('run...')
-
- a1 = Animal(2, 'male', 10.0)
- print('a1体重: {0:0.02f}'.format(a1.weight)) # --2
- a1.eat()
- a1.run()
运行结果:
- >python test.py
- Traceback (most recent call last):
- File "F:\python_work\面向对象编程\test.py", line 19, in <module>
- print('a1体重: {0:0.02f}'.format(a1.weight))
- AttributeError: 'Animal' object has no attribute 'weight'
-
- 错误信息: Animal类没有成员变量'weight'
解析:上述代码第1处在 weight 变量前加上双下划线,这会定义私有变量 __weight。__weight 变量在类内部访问没有问题,但是如果在类外部访问则会发生错误,见代码第2处。
<提示> Python 中并没有严格意义上的封装,所谓的私有变量只是形式上的限制。如果想在外部访问这些私有变量也是可以的,这些双下划线开头的私有变量只是换了一个名字,它们的命名规律为 "_类名__变量名",所以将上述代码 a1.weight 改成 a1._Animal__weight 就可以访问了,但这种访问方式并不符合规范,会破坏封装。可见,Python 的封装性靠的是程序员的自律,而非强制性的语法。
- # print('a1体重: {0:0.02f}'.format(a1.weight)) # --2
- print('a1体重: {0:0.02f}'.format(a1._Animal__weight)) # --3
将上述第2处的代码改成第3处的代码,就可以正确运行程序了,运行结果为:
- >python test.py
- a1体重: 10.00
- eat...
- run...
私有方法与私有变量的封装是类似的,只要在方法前加上双下划线就是私有方法了。
示例代码如下:
- # coding=utf-8
-
- class Animal(object):
- """ 定义动物类 """
- def __init__(self, age, sex, weight = 0.0):
- self.age = age # 定义年龄实例变量
- self.sex = sex # 定义性别实例变量
- self.__weight = weight # 定义体重实例变量
-
- def eat(self):
- self.__weight += 0.05
- print('eat...')
-
- def __run(self): # --1
- self.__weight -= 0.01
- print('run...')
-
- a1 = Animal(2, 'male', 10.0)
- a1.eat()
- a1.run() # --2
运行结果:
- >python test.py
- eat...
- Traceback (most recent call last):
- File "F:\python_work\面向对象编程\test.py", line 20, in <module>
- a1.run() # --1
- AttributeError: 'Animal' object has no attribute 'run'
-
- 错误信息: Animal类没有成员方法'run'
解析:上述代码第1处中 __run() 方法是私有方法。__run() 方法可以在类的内部访问,不能在类的外部访问,否则会发生错误,见代码第2处。
<提示> 如果一定要在类的外部访问私有方法也是可以的。与私有变量访问类似,命名规律为 "_类名__方法名"。这也不符合规范,也会破坏类的封装性。
封装通常是对成员变量进行封装。在严格意义上的面向对象设计中,一个类是不应该有公有的实例成员变量的,这些实例成员变量应该被设计为私有的,然后通过公有的 setter 和 getter 访问器访问。
私有 setter 和 getter 访问器的示例代码如下:
- # coding=utf-8
-
- class Animal(object):
- """ 定义动物类 """
- def __init__(self, age, sex, weight = 0.0):
- self.age = age # 定义年龄实例变量
- self.sex = sex # 定义性别实例变量
- self.__weight = weight # 定义体重实例变量
-
- def set_weight(self, weight): # --1
- self.__weight = weight
-
- def get_weight(self): # --2
- return self.__weight
-
- a1 = Animal(2, 'male', 10.0)
- print('a1体重: {0:0.2f}'.format(a1.get_weight())) # --3
- a1.set_weight(123.45)
- print('a1体重: {0:0.2f}'.format(a1.get_weight())) # --4
运行结果:
- >python test.py
- a1体重: 10.00
- a1体重: 123.45
解析:上述代码第1处中的 set_weight() 方法是 setter 访问器,它有一个参数 weight 用来给私有成员变量 __weight 传递值。代码第2处的 get_weight() 方法是 getter 访问器。代码第3处是调用 getter 访问器。代码第4处是调用 setter 访问器。
访问器形式的封装需要一个私有变量,需要提供 getter 访问器和一个 setter 访问器,只读变量不用提供 setter 访问器。总之,访问器形式的封装在编写代码时比较麻烦。为了解决这个问题,Python 中提供了属性(property),定义属性可以使用 @property 和 @属性名.setter 装饰器,@property 用来修饰 getter 访问器,@属性名.setter 用来修饰 setter 访问器。
使用属性修改前面的示例代码如下:
- # coding=utf-8
-
- class Animal(object):
- """ 定义动物类 """
- def __init__(self, age, sex, weight = 0.0):
- self.age = age # 定义年龄实例变量
- self.sex = sex # 定义性别实例变量
- self.__weight = weight # 定义体重实例变量
-
- @property
- def weight(self): # 替代 get_weight(self): --1
- return self.__weight
-
- @weight.setter
- def weight(self, weight): # 替代 set_weight(self, weight): --2
- self.__weight = weight
-
- a1 = Animal(2, 'male', 10.0)
- print('a1体重: {0:0.2f}'.format(a1.weight)) # --3
- a1.weight = 123.45 # a1.set_weight(123.45) # --4
- print('a1体重: {0:0.2f}'.format(a1.weight))
运行结果:
- 程>python test.py
- a1体重: 10.00
- a1体重: 123.45
解析:(1)上述代码第1处是定义属性 getter 访问器,使用了 @property 装饰器进行修饰,方法名就是属性名,这样就可以通过属性取值了,见代码第3处。
(2)代码第2处是定义属性 setter 访问器,使用了 @weight.setter 装饰器进行修饰,weight 是属性名,与 getter 和 setter 访问器方法名保持一致,可以通过 a1.weight = 123.45 赋值,见代码第4处。
从上述示例可见,属性本质上就是两个方法,在方法前加上装饰器使得方法成为属性(property)。属性使用起来类似于公有变量,可以在赋值符号 “=” 左边或右边,左边是被赋值,右边是取值。
<提示> 定义属性时应该先定义 getter 访问器,再定义 setter 访问器,即上述代码第1处和第2处不能颠倒,否则会出现错误。这是因为 @property 修饰 getter 访问器时,定义了 weight 属性,这样在后面使用 @weight.setter 装饰器才是合法的。
类的继承是面向对象编程语言的基本特性,多态性的前提是继承。
为了了解继承性,先看这样一个场景:一位面向对象的程序员小张,在编程过程中需要描述和处理个人信息,于是定义了类 Person,如下所示:
- class Person:
-
- def __init__(self, name, age):
- self.name = name # 名字
- self.age = age # 年龄
-
- def info(self):
- template = 'Person [name={0}, age={1}]'
- s = template.format(self.name, self.age)
- return s
一周之后,小张又遇到了新的需求,需要描述和处理学生信息,于是他又定义了一个新的类 Student,如下所示:
- class Student:
-
- def __init__(self, name, age, school):
- self.name = name # 名字
- self.age = age # 年龄
- self.school = school # 所在学校
-
- def info(self):
- template = 'Person [name={0}, age={1}, school={2}]'
- s = template.format(self.name, self.age, self.school)
- return s
我们可能会认为小张的做法能够被理解并认为这是可行的,但问题在于 Student 和 Person 两个类的结构太接近了,后者只比前者多了一个 school 实例变量,却要重复定义其他所有的内容,实在让人 “不甘心”。Python 提供了解决类似问题的机制,那就是类的继承,代码如下所示:
- class Student(Person): # --1
-
- def __init__(self, name, age, school): # --2
- super.__init__(name, age) # --3
- self.school = school # 所在学校 --4
解析:上述代码第 1 处是声明 Student 类继承自 Person 类,其中小括号中的是父类,如果没有指明父类(一对空的小括号或省略小括号),则默认父类为 object 类,object 类是 Python 的根类。代码第 2 处定义构造方法,子类中定义构造方法时首先要调用父类的构造方法,初始化父类实例变量。代码第 3 处 super.__init__(name, age) 语句是调用父类的构造方法,super() 函数是返回父类引用,通过它可以调用父类中的实例变量和方法。代码第 4 处是定义 school 实例变量,这个是子类新增的成员变量。
<提示> 子类继承父类时只是继承父类中公有的成员变量和方法,不能继承私有的成员变量和方法。
如果子类方法名与父类方法名相同,而且参数列表也相同,只是方法体不同,那么子类重写(Override)了父类的方法。
示例代码如下:
- # coding=utf-8
-
- class Animal(object):
- """ 定义动物类 """
- def __init__(self, age, sex = 'male', weight = 0.0):
- self.age = age # 定义年龄实例变量
- self.sex = sex # 定义性别实例变量
- self.weight = weight # 定义体重实例变量
-
- def eat(self): # --1
- self.weight += 0.05
- print('Animal eat...')
-
- class Dog(Animal):
- def eat(self): # --2
- self.weight += 0.02
- print('Dog eat...')
-
- a1 = Dog(2, 'female', 10.0)
- a1.eat()
运行结果:
- >python test.py
- Dog eat...
解析:上述代码第 1 处是父类中定义的 eat() 实例方法,子类继承父类并重写了 eat() 方法,见代码第 2 处。那么通过子类实例调用 eat() 方法时,会调用子类重写的 eat() 方法。
所谓多继承,就是一个子类有多个父类。大部分计算机编程语言如 Java、Swift 等,只支持单继承,不支持多继承。主要是多继承会发生方法冲突。例如,客轮是轮船也是交通工具,客轮的父类是轮船和交通工具,如果两个父类都定义了 run() 方法,子类客轮继承哪一个 run() 方法呢?
Python 支持多继承,但 Python 给出了解决方法名冲突的方案。这个方案是:当子类实例调用一个方法时,先从子类中查找,如果没有找到则查找父类。父类的查找顺序是按照子类声明的父类列表从左到右查找,如果没有找到再找父类的父类,依次查找下去。
多继承示例代码如下:
- # coding=utf-8
-
- class ParentClass1:
- def run(self):
- print('ParentClass1 run...')
-
- class ParentClass2:
- def run(self):
- print('ParentClass2 run...')
-
- class SubClass1(ParentClass1, ParentClass2):
- pass
-
- class SubClass2(ParentClass2, ParentClass1):
- pass
-
- class SubClass3(ParentClass1, ParentClass2):
- def run(self):
- print('SubClass3 run...')
-
- sub1 = SubClass1()
- sub1.run()
- sub2 = SubClass2()
- sub2.run()
- sub3 = SubClass3()
- sub3.run()
运行结果:
- >python test.py
- ParentClass1 run...
- ParentClass2 run...
- SubClass3 run...
解析:上述代码中定义了两个父类 ParentClass1 和 ParentClass2,以及三个子类 SubClass1、SubClass2 和 SubClass3,这三个子类都继承了 ParentClass1 和 ParentClass2 两个父类。当子类 SubClass1 的实例 sub1 调用 run() 方法时,Python 解释器会先查找当前子类是否有 run() 方法,如果没有则到父类中查找,按照父类列表从左至右的顺序,找到 ParentClass1 中的 run() 方法,所以最后调用的是 ParentClass1 中的 run() 方法。按照这个规律,其他两个实例 sub2 和 sub3 调用哪一个 run() 就很容易知道了。
在面向对象程序设计中,多态是一个非常重要的特性,理解多态有利于进行面向对象的分析与设计。
发生多态要有两个前提条件:第一、继承——多态发生一定是子类和父类之间;第二、重写——子类重写父类同名的方法。
下面通过一个示例解释什么是多态。如下图 2 所示,父类 Figure (几何图形)有一个 draw(绘图)函数,Figure(几何图形)有两个子类 Ellipse(椭圆形)和 Triangle(三角形),Ellipse 和 Triangle 重写 draw() 方法。Ellipse 和 Triangle 都有 draw() 方法,但具体实现的方式不同。
具体代码如下:
- # coding=utf-8
-
- # 几何图形类
- class Figue:
- def draw(self):
- print('draw Figue...')
-
- # 椭圆形类
- class Ellipse(Figue):
- def draw(self):
- print('draw Ellipse...')
-
- # 三角形类
- class Triangle(Figue):
- def draw(self):
- print('draw Triangle...')
-
- f1 = Figue() # --1
- f1.draw()
-
- f2 = Ellipse() # --2
- f2.draw()
-
- f3 = Triangle() # --3
- f3.draw()
运行结果:
- 程>python test.py
- draw Figue...
- draw Ellipse...
- draw Triangle...
解析:上述代码第2处和第3处符合多态的两个前提,因此会发生多态。而代码第1处不符合,没有发生多态。多态发生时,Python 解释器根据引用指向的实例调用它的方法。
<提示> 与 Java 等静态语言相比,多态性对于动态语言 Python 意义不大。多态性优势在于运行期动态特性。例如在 Java 中多态性是指,编译期声明变量是父类的类型,在运行期确定变量所引用的子类实例。而 Python 不需要声明变量的类型,没有编译,直接由解释器运行,运行期确定变量所引用的实例。
无论多态性对 Python 影响多大,Python 作为面向对象的编程语言多态性是存在的,这一点可以通过运行期检查类型证实,运行期类型检查使用 isinstance(object, classinfo) 函数,它可以检查 object 实例是否由 classinfo 类或 classinfo 子类所创建的。
在 6.1 节示例的基础上修改代码如下:
- # coding=utf-8
-
- # 集合图形类
- class Figue:
- def draw(self):
- print('draw Figue...')
-
- # 椭圆形类
- class Ellipse(Figue):
- def draw(self):
- print('draw Ellipse...')
-
- # 三角形类
- class Triangle(Figue):
- def draw(self):
- print('draw Triangle...')
-
- f1 = Figue() # 没有发生多态
- f1.draw()
-
- f2 = Ellipse() # 发生多态
- f2.draw()
-
- f3 = Triangle() # 发生多态
- f3.draw()
-
- print(isinstance(f1, Triangle)) # False --1
- print(isinstance(f2, Triangle)) # False
- print(isinstance(f3, Triangle)) # True
- print(isinstance(f2, Ellipse)) # True --2
- print(isinstance(f2, Figue)) # True --3
解析:上述代码第1处和第2、3处添加的代码,需要注意代码第3处的 isinstance(f2, Figue) 表达式是 True,f2 是 Ellipse 类创建的实例,而 Ellipse 是 Figure 类的子类,所以这个表达式返回 True,通过这样的表达式可以判断是否发生了多态。另外,还有一个类似于 isinstance(object, classinfo) 函数的 issubclass(class, classinfo) 函数,issubclass(class, classinfo) 函数用来检查 class 是否是 classinfo 的子类。示例代码如下:
- print(issubclass(Ellipse, Triangle)) # False
- print(issubclass(Ellipse, Figue)) # True
- print(issubclass(Triangle, Ellipse)) # False
多态性对于动态语言意义不同,在动态语言中有一种类型检查称为 “鸭子类型”,即一只鸟走起来像鸭子,游起来像鸭子,叫起来也像鸭子,那它就可以被当做 “鸭子类型”。鸭子类型不关注变量的类型,而是关注变量具有的方法。鸭子类型像多态一样工作,但是没有继承,只是像 “鸭子” 一样的行为(方法)就可以了。
鸭子类型示例代码如下:
- # coding=utf-8
-
- class Animal(object):
- def run(self):
- print('Animal run...')
-
- class Dog(Animal):
- def run(self):
- print('Dog run...')
-
- class Car:
- def run(self):
- print('Car run...')
-
- def go(animal): # 接收参数是Animal --1
- animal.run()
-
- go(Animal()) # 传入一个Animal类实例
- go(Dog()) # 传入一个Dog类实例
- go(Car()) # 传入一个Car类实例 --2
运行结果:
- >python test.py
- Animal run...
- Dog run...
- Car run...
解析:上述代码定义了三个类 Animal、Dog 和 Car,从代码和上图 3 所示可见,Dog 类继承了 Animal 类,而 Car 和 Animal 和 Dog 没有任何的关系,只是它们都有 run() 方法。代码第 1 处定义的 go() 函数设计时考虑接收 Animal 类型参数,但是由于 Python 解释器不做任何的类型检查,所以可以传入任何的实际参数。当代码第 2 处给 go() 函数传入 Car 类的实例时,它可以正常执行。这就是 “鸭子类型”。
在 Python 这样的动态语言中使用 “鸭子类型” 替代多态性设计,能够充分地发挥 Python 动态语言特点,但是也给软件设计者带来了困难,对程序员的要求也非常高。
Python 所有类都直接或间接继承自 object 类,它是所有类的 “祖先”。object 类有很多方法,本节重点介绍如下两个方法:
这些方法都是需要在子类中重写的,下面就详细解释一下它们的用法。
为了日志输出等处理方便,所有的对象都可以输出自己的描述信息。为此,可以重写 __str__() 方法。如果没有重写 __str__() 方法,则默认返回对象的类名,以及内存地址等信息。
下面看一个示例,在前面 5.1 节介绍过的 Person 类,重写它的 __str__() 方法代码如下:
- # coding=utf-8
-
- class Person:
-
- def __init__(self, name, age):
- self.name = name # 名字
- self.age = age # 年龄
-
- def __str__(self): # --1
- template = 'Person [name={0}, age={1}]'
- s = template.format(self.name, self.age)
- return s
-
- person = Person('Godlike', 18)
- print(person) # --2
运行结果:
- >python test.py
- Person [name=Godlike, age=18]
解析:上述代码第 1 处覆盖 __str__() 方法,返回什么样的字符串完全是自定义的,只要能够表示当前类和当前对象即可,本例是将 Person 成员变量拼接成为一个字符串。代码第 2 处是打印 person 对象,print() 函数会将对象的 __str__() 方法返回字符串,并打印输出。
在《Python学习笔记 - Python运算符》这篇博文中介绍同一性测试运算符时,曾经介绍过内容相等比较运算符 “==”,== 用来比较两个对象的内容是否相等。当使用运算符 == 比较两个对象时,在对象的内部是通过 __eq__() 方法进行比较的。
两个人(Person 对象)相等是指什么?是名字?是年龄?问题的关键是需要指定相等的规则,就是要指定比较的是哪些实例变量相等。所以为了比较两个 Person 对象是否相等,则需要重写 __eq__() 方法,在该方法中指定比较规则。
修改 Person 代码如下:
- # coding=utf-8
-
- class Person:
-
- def __init__(self, name, age):
- self.name = name # 名字
- self.age = age # 年龄
-
- def __str__(self): # --1
- template = 'Person [name={0}, age={1}]'
- s = template.format(self.name, self.age)
- return s
-
- def __eq__(self, other): # --1
- if self.name == other.name and self.age == other.age: # --2
- return True
- else:
- return False
-
- person1 = Person('Godlike', 18)
- person2 = Person('Godlike', 18)
- person3 = Person('Godlike', 20)
-
- print(person1 == person2) # True
- print(person1 == person3) # False
解析:上述代码第 1 处重写了 Person 类 __eq__() 方法,代码第 2 处是提供比较规则,即只有是 name 和 age 都相等才认为两个对象相等。代码中创建了三个 Person 对象 person1、person2 和 person3,person1 和 person2 这两个对象具体相同的 name 和 age,所以 person1 == person2 为 True。而 person1 和 person3 这两个对象,成员变量 age 的值不同,所以 person1 == person3 为 False。
枚举是用来管理一组相关的有限个数常量的集合,使用枚举可以提高程序的可读性,使代码更清晰且更易于维护。在 Python 中提供枚举类型,本质上是一种类。
在 Python 中定义枚举类的语法格式如下:
- class 枚举类名 (enum.Enum):
- 枚举常量列表
枚举类继承自 enum.Enum 类。枚举中会定义多个常量成员。枚举类 WeekDays 具体代码如下:
- # coding=utf-8
-
- import enum
-
- class WeekDays(enum.Enum): # --1
- # 枚举常量列表
- MONDAY = 1 # --2
- TUESDAY = 2
- WEDNESDAY = 3
- THURSDAY = 4
- FRIDAY = 10 # --3
-
- day = WeekDays.FRIDAY # --4
-
- print(day) # WeekDays.FRIDAY
- print(day.value) # 10
- print(day.name) # FRIDAY
运行结果:
- >python test.py
- WeekDays.FRIDAY
- 10
- FRIDAY
解析:上述代码第 1 处是定义 WeekDays 枚举类吗,它有 5 个常量成员,每一个常量成员值都需要进行初始化,见代码第 2 处和第 3 处。代码第 4 处是实例化枚举类 WeekDays,该实例为 FRIDAY。注意枚举类实例化与一般类不同,枚举类不能调用构造方法。枚举实例 value 属性是返回枚举值, name 属性返回枚举名。
<提示> 常量成员值可以是任意类型,多个成员的值也可以相同。
为了存储和使用方便,枚举类中的常量成员取值应该是整数,而且每一枚举常量应该有不同的取值。为了使枚举常量成员只能使用整数类型,可以使用 enum.IntEnum 作为枚举父类。为了防止常量成员值重复,可以为枚举类加上 @enum.unique 装饰器。具体示例代码如下:
- # coding=utf-8
-
- import enum
-
- @enum.unique # --1
- class WeekDays(enum.IntEnum): # --2
- # 枚举常量列表
- MONDAY = 1
- TUESDAY = 2
- WEDNESDAY = 3
- # WEDNESDAY = 'Wed.'
- THURSDAY = 4
- FRIDAY = 5
- # FRIDAY = 1
-
- day = WeekDays.FRIDAY
-
- print(day) # WeekDays.FRIDAY
- print(day.value) # 5
- print(day.name) # FRIDAY
解析:上述代码第 1 处是 WeekDays 枚举类的装饰器。代码第 2 处是定义枚举类型 WeekDays,其父类是 enum.IntEnum。如果尝试将其中的成员值修改为其他数据类型值,或修改为相同值(见注释代码),则会发生异常。
ValueError: invalid literal for int() with base 10: 'Wed.'
定义枚举类的主要目的是提高程序可读性,特别是在比较时,枚举类非常实用。示例代码如下:
- # coding=utf-8
-
- import enum
-
- @enum.unique
- class WeekDays(enum.IntEnum):
- # 枚举常量列表
- MONDAY = 1
- TUESDAY = 2
- WEDNESDAY = 3
- THURSDAY = 4
- FRIDAY = 5
- SATURDAY = 6
- SUNDAY = 7
-
- day = WeekDays.SATURDAY
-
- if day == WeekDays.MONDAY: # --1
- print('Working')
- elif day == WeekDays.SATURDAY: # --2
- print("Resting")
运行结果:
- >python test.py
- Resting
解析:上述代码第 1 处比较 day 是否为 星期一,代码第 2 处比较 day 是否为 星期六。从中可见,使用枚举成员要好于使用 1 和 5 这种无意义的值(这种无意义的值也称为魔鬼数字)。
本文主要介绍了 Python 面向对象编程基础知识,包括如何定义类,如何为类定义变量、方法,以及如何创建类的对象。Python 的类(class)其实就相当于类命名空间,在类中定义的方法就位于类命名空间内,因此使用类定义方法非常灵活,类不仅可以调用类方法、静态方法,也可以使用未绑定的方式调用实例方法。
本文还详细介绍了 Python 面向对象的三大特征:封装、继承 和 多态,Python 通过双下划线的方式来隐藏类中的成员。本文也讲解了 Python 的多继承机制,需要注意多继承中可能容易出错的问题。Python 的多态则大大提高了 Python 编程的灵活性。
- # coding=utf-8
-
- class Student:
- '''定义学生类'''
- def __init__(self, name, age, gender, phone, address, email):
- '''定义构造方法'''
- self.name = name
- self.age = age
- self.gender = gender
- self.phone = phone
- self.address = address
- self.email = email
-
- def eat(self, food):
- '''定义吃的实例方法'''
- print('%s 正在吃 %s' %(self.name, food))
-
- def drink(self, drink):
- '''定义喝的实例方法'''
- print('%s 正在喝 %s' %(self.name, drink))
-
- def play(self, sport, other):
- '''定义玩的实例方法'''
- print('%s 今年 %d 岁, 正和 %s 玩 %s' %(self.name, self.age, other, sport))
-
- def sleep(self):
- '''定义睡的实例方法'''
- print('%s 正在 %s 睡觉' %(self.name, self.address))
-
- def __repr__(self): # 重写object类的 __repr__() 方法,该方法主要实现"自我描述"功能
- return ('Student(name=%s, age=%d, phone=%s, address=%s, email=%s)' %(self.name, self.age, self.phone, self.address, self.email))
-
- if __name__ == '__main__':
- stu = Student('小明', 18, '男', '18812345678', 'Beijing`s home', 'xiaoming@gmail.com')
- stu.eat('Bread')
- stu.drink('Milk')
- stu.play('Honor of Kings', '小李子')
- stu.sleep()
运行结果:
- >python student.py
- 小明 正在吃 Bread
- 小明 正在喝 Milk
- 小明 今年 18 岁, 正和 小李子 玩 Honor of Kings
- 小明 正在 Beijing`s home 睡觉
- # coding=utf-8
-
- from student import Student
-
- contacts = [
- Student('Zhangsan', 18, 'Male', '18812345671', 'Beijing', 'zhangsan@gmail.com'),
- Student('Lisi', 18, 'Male', '18812345672', 'Wuhan', 'lisi@gmail.com'),
- Student('Wangwu', 18, 'Male', '18812345673', 'Shanghai', 'wangwu@gmail.com'),
- Student('Lihong', 18, 'Female', '18812345674', 'Shenzhen', 'lihong@gmail.com')
- ]
-
- def find_by_name(name):
- return [x for x in contacts if name in x.name] # 列表推导式
-
- def find_by_email(email):
- return [x for x in contacts if email in x.email]
-
- def find_by_address(address):
- return [x for x in contacts if address in x.address]
-
- if __name__ == '__main__':
- while True:
- t = input('请输入查找的方式,名字(n), Email(e),地址(a): ')
- k = input('请输入查找的关键字: ')
- if t == 'n':
- print(find_by_name(k))
- elif t == 'e':
- print(find_by_email(k))
- elif t == 'a':
- print(find_by_address(k))
- else:
- print('输入有误!')
- print() # 换行
- # coding=utf-8
-
- import math
-
- class Point(object):
- '''定义Point类'''
- def __init__(self, x, y):
- '''定义构造方法'''
- self.x = x
- self.y = y
-
- def distance(self, other):
- '''计算坐标系两点之间的距离'''
- return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
-
- def judge_triangle(self, p1, p2):
- '''判断3个坐标点组成的三角形'''
- self_p1 = self.distance(p1)
- self_p2 = self.distance(p2)
- p1_p2 = p1.distance(p2)
- # 如果self_p1长度最大
- if self_p1 > self_p2 and self_p1 > p1_p2:
- if self_p1 > (self_p2 + p1_p2):
- print('不是三角形')
- else:
- if (self_p1 ** 2) > (self_p2 ** 2 + p1_p2 ** 2):
- print('钝角三角形')
- elif (self_p1 ** 2) == (self_p2 ** 2 + p1_p2 ** 2):
- print('直角三角形')
- else:
- print('锐角三角形')
- # 如果self_p2长度最大
- if self_p2 > self_p1 and self_p2 > p1_p2:
- if self_p2 > (self_p1 + p1_p2):
- print('不是三角形')
- else:
- if (self_p2 ** 2) > (self_p1 ** 2 + p1_p2 ** 2):
- print('钝角三角形')
- elif (self_p2 ** 2) == (self_p1 ** 2 + p1_p2 ** 2):
- print('直角三角形')
- else:
- print('锐角三角形')
- # 如果p1_p2长度最大
- if p1_p2 > self_p1 and p1_p2 > self_p2:
- if p1_p2 > (self_p2 + p1_p2):
- print('不是三角形')
- else:
- if (p1_p2 ** 2) > (self_p1 ** 2 + self_p2 ** 2):
- print('钝角三角形')
- elif (p1_p2 ** 2) == (self_p1 ** 2 + self_p2 ** 2):
- print('直角三角形')
- else:
- print('锐角三角形')
-
- def __repr__(self):
- return ('Point(x=%s, y=%s)' %(self.x, self.y))
-
- if __name__ == '__main__':
- pt = Point(1, 1)
- print(pt.distance(Point(2, 3)))
- pt.judge_triangle(Point(4, 1), Point(5, 5))
运行结果:
- 2.23606797749979
- 钝角三角形
提示:cos(夹角) = (X▪Y) / |X||Y|,其中 X = AB × BC,Y = BC × CD,X▪Y 代表 X 与 Y 的点积,AB × BC 代表 AB 与 BC 的叉乘。
- # coding=utf-8
-
- import math
-
- class Points(object):
- '''定义Point类'''
- def __init__(self, x, y, z):
- '''定义构造方法'''
- self.x = x
- self.y = y
- self.z = z
-
- def __sub__(self, p):
- '''为减法提供支持的方法'''
- return Points((self.x - p.x), (self.y - p.y), (self.z - p.z))
-
- def dot(self, p):
- '''点积'''
- return (self.x + p.x) + (self.y + p.y) + (self.z + p.z)
-
- def cross(self, p):
- '''叉积'''
- return Points((self.y * p.z - self.z * p.y), (self.z * p.x - self.x * p.z), (self.x * p.y - self.y * p.x))
-
- def absolute(self):
- '''绝对值'''
- return math.sqrt((self.x ** 2 + self.y ** 2 + self.z ** 2))
-
- if __name__ == '__main__':
- points = list()
- print('请依次输入4个点的x y z(中间以空格隔开):')
- for i in range(4):
- a = list(map(float, input().split())) # 将输入的数字字符串转换成浮点数,map函数返回的是迭代器
- # 最后使用list()函数转换为列表
- points.append(a) # 将列表a追加到列表points中
-
- a, b, c, d = Points(*points[0]), Points(*points[1]), Points(*points[2]), Points(*points[3])
- X = (b - a).cross(c - b)
- Y = (c - b).cross(d - c)
- angle = math.acos((X.dot(Y)) / (X.absolute() * Y.absolute()))
-
- print("degrees=%.2f" %math.degrees(angle)) # 将角度angle从弧度转换为度数
示例运行结果:
- >python test.py
- 请依次输入4个点的x y z(中间以空格隔开):
- 1 0 0
- 0 1 0
- 0 0 1
- 2 2 2
- degrees=55.94
- # coding=utf-8
-
- class Transport:
- def move(self, distance):
- print('我移动了 %s 千米' % distance)
-
- class Car(Transport):
- def __init__(self, name, speed):
- self.name = name
- self.speed = speed
-
- def move(self, distance):
- print('%s 汽车以时速 %skm/h 在公路上开了 %s 千米' % (self.name, self.speed, distance))
-
- class Train(Transport):
- def __init__(self, speed):
- self.speed = speed
-
- def move(self, distance):
- print('高铁以时速 %skm/h 在铁轨上走了 %s 千米' %(self.speed, distance))
-
- class Plain(Transport):
- def __init__(self, speed):
- self.speed = speed
-
- def fly(self, distance):
- print('飞机以时速 %skm/h 在天空飞了 %s 千米' % (self.speed, distance))
-
- if __name__ == '__main__':
- car = Car('BYD', 80)
- car.move(50)
-
- train = Train(350)
- train.move(1080)
-
- plain = Plain(950)
- plain.fly(3000)
运行结果:
- BYD 汽车以时速 80km/h 在公路上开了 50 千米
- 高铁以时速 350km/h 在铁轨上走了 1080 千米
- 飞机以时速 950km/h 在天空飞了 3000 千米
《Python从小白到大牛(第1版-2018).pdf》第11章 - 面向对象编程
《疯狂Python讲义(2018.12).pdf》第3章 - 类和对象
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。