我心中的王者:Python-第12章 类-面向对象的程序设计
Python其实是一种面向对象的编程(Object Oriented Programming),在Python中其实所有的数据类型皆是对象,Python也允许程序设计师自创数据类型,这种自创的数据类型就是本章的主题类(class)。
设计程序时可以将世间万物分组归类,然后使用类(class)定义你的分类,笔者在本章将举一系列不同的类,扩展读者的思维。
12-1 类的定义与使用
类的语法定义如下:
class Classname( ) # 类名称第一个字母必须大写
statement1
…
statementn
本节将以银行为例,说明最基本的类观念。
12-1-1 定义类
程序实例ch12_1.py:Banks的类定义。
# ch12_1.py
class Banks():
# 定义银行类别
title = 'Taipei Bank' # 定义属性
def motto(self): # 定义方法
return "以客为尊"
执行结果 这个程序没有输出结果。
对上述程序而言,Banks是类名称,在这个类中笔者定义了一个属性(attribute)title与一个方法(method)motto。
在类内定义方法(method)的方式与第11章定义函数的方式相同,但是不可以称之为函数(function),必须称之为方法(method),在程序设计时我们可以随时调用函数,但是只有属于该类的对象(object)才可调用相关的方法。
12-1-2 操作类的属性与方法
若是想操作类的属性与方法首先需定义该类的对象(object)变量,可以简称对象,然后使用下列方式操作。
object.类的属性
object.类的方法( )
程序实例ch12_2.py:扩充ch12_1.py,列出银行名称与服务宗旨。
# ch12_2.py
class Banks():
# 定义银行类别
title = 'Taipei Bank' # 定义属性
def motto(self): # 定义方法
return "以客为尊"
userbank = Banks() # 定义对象userbank
print("目前服务银行是 ", userbank.title)
print("银行服务理念是 ", userbank.motto())
执行结果
目前服务银行是 Taipei Bank
银行服务理念是 以客为尊
从上述执行结果可以发现我们成功地存取了Banks类内的属性与方法了。上述程序观念是,程序第8行定义了userbank当作是Banks类的对象,然后使用userbank对象读取了Banks类内的title属性与motto( )方法。这个程序主要是列出title属性值与motto( )方法传回的内容。
12-1-3 类的构造函数
建立类很重要的一个工作是初始化整个类,所谓的初始化类是在类内建立一个初始化方法(method),这是一个特殊方法,当在程序内定义这个类的对象时将自动执行这个方法。初始化方法有一个固定名称是“init( )”,写法是init左右皆是2个底线字符,init其实是initialization的缩写,通常又将这类初始化的方法称构造函数(constructor)。在这初始化的方法内可以执行一些属性变量设定,下列笔者先用一个实例做解说。
程序实例ch12_3.py:重新设计ch12_2.py,设定初始化方法,同时存第一笔开户的钱100元入银行,然后列出存款金额。
# ch12_3.py
class Banks():
# 定义银行类别
title = 'Taipei Bank' # 定义属性
def __init__(self, uname, money): # 初始化方法
self.name = uname # 设定存款者名字
self.balance = money # 设定所存的钱
def get_balance(self): # 获得存款余额
return self.balance
hungbank = Banks('hung', 100) # 定义对象hungbank
print(hungbank.name.title( ), " 存款余额是 ", hungbank.get_balance())
执行结果
Hung 存款余额是 100
上述在程序12行定义Banks类的hungbank对象时,Banks类会自动启动__init__( )初始化函数,在这个定义中self是必需的,同时需放在所有参数的最前面(相当于最左边),Python在初始化时会自动传入这个参数self,代表的是类本身的对象,未来在类内想要参照各属性与函数执行运算皆要使用self,可参考第6、7和10行。
在这个Banks类的__init__(self, uname, money )方法中,有另外2个参数uname和money,未来我们在定义Banks类的对象时(第12行)需要传递2个参数,分别给uname和money。至于程序第6和7行内容如下:
self.name = uname ; name是Banks类的属性
self.balance = money ; balance是Banks类的属性
读者可能会思考,既然__init__这么重要,为何ch12_2.py没有这个初始化函数仍可运行,其实对ch12_2.py而言是使用预设没有参数的__init__( )方法。
在程序第9行另外有一个get_balance(self)方法,在这个方法内只有一个参数self,所以调用时可以不用任何参数,可以参考第13行。这个方法目的是传回存款余额。
程序实例ch12_4.py:扩充ch12_3.py,主要是增加执行存款与提款功能,同时在类内可以直接列出目前余额。
# ch12_4.py
class Banks():
# 定义银行类别
title = 'Taipei Bank' # 定义属性
def __init__(self, uname, money): # 初始化方法
self.name = uname # 设定存款者名字
self.balance = money # 设定所存的钱
def save_money(self, money): # 设计存款方法
self.balance += money # 执行存款
print("存款 ", money, " 完成") # 打印存款完成
def withdraw_money(self, money): # 设计提款方法
self.balance -= money # 执行提款
print("提款 ", money, " 完成") # 打印提款完成
def get_balance(self): # 获得存款余额
print(self.name.title(), " 目前余额: ", self.balance)
hungbank = Banks('hung', 100) # 定义对象hungbank
hungbank.get_balance() # 获得存款余额
hungbank.save_money(300) # 存款300元
hungbank.get_balance() # 获得存款余额
hungbank.withdraw_money(200) # 提款200元
hungbank.get_balance() # 获得存款余额
执行结果
Hung 目前余额: 100
存款 300 完成
Hung 目前余额: 400
提款 200 完成
Hung 目前余额: 200
其实类建立完成后,我们随时可以使用多个对象引用这个类的属性与函数,可参考下列实例。
程序实例ch12_5.py:使用与ch12_4.py相同的Banks类,然后定义2个对象使用操作这个类。下列是与ch12_4.py不同的程序代码内容。
# ch12_5.py
class Banks():
# 定义银行类别
title = 'Taipei Bank' # 定义属性
def __init__(self, uname, money): # 初始化方法
self.name = uname # 设定存款者名字
self.balance = money # 设定所存的钱
def save_money(self, money): # 设计存款方法
self.balance += money # 执行存款
print("存款 ", money, " 完成") # 打印存款完成
def withdraw_money(self, money): # 设计提款方法
self.balance -= money # 执行提款
print("提款 ", money, " 完成") # 打印提款完成
def get_balance(self): # 获得存款余额
print(self.name.title(), " 目前余额: ", self.balance)
hungbank = Banks('hung', 100) # 定义对象hungbank
johnbank = Banks('john', 300) # 定义对象johnbank
hungbank.get_balance() # 获得hung存款余额
johnbank.get_balance() # 获得john存款余额
hungbank.save_money(100) # hung存款100
johnbank.withdraw_money(150) # john提款150
hungbank.get_balance() # 获得hung存款余额
johnbank.get_balance() # 获得john存款余额
执行结果
Hung 目前余额: 100
John 目前余额: 300
存款 100 完成
提款 150 完成
Hung 目前余额: 200
John 目前余额: 150
12-1-4 属性初始值的设定
在先前程序的Banks类中第4行title是设为“Taipei Bank”,其实这是初始值的设定,通常Python在设初始值时是将初始值设在__init__( )方法内,下列这个程序在定义Banks类对象时,省略开户金额,相当于定义Banks类对象时只要2个参数。
程序实例ch12_6.py:设定开户(定义Banks类对象)只要姓名,同时设定开户金额是0元,读者可留意第7和8行的设定。
# ch12_6.py
class Banks():
# 定义银行类别
def __init__(self, uname): # 初始化方法
self.name = uname # 设定存款者名字
self.balance = 0 # 设定开户金额是0
self.title = "Taipei Bank" # 设定银行名称
def save_money(self, money): # 设计存款方法
self.balance += money # 执行存款
print("存款 ", money, " 完成") # 打印存款完成
def withdraw_money(self, money): # 设计提款方法
self.balance -= money # 执行提款
print("提款 ", money, " 完成") # 打印提款完成
def get_balance(self): # 获得存款余额
print(self.name.title(), " 目前余额: ", self.balance)
hungbank = Banks('hung') # 定义对象hungbank
print("目前开户银行 ", hungbank.title) # 列出目前开户银行
hungbank.get_balance() # 获得hung存款余额
hungbank.save_money(100) # hung存款100
hungbank.get_balance() # 获得hung存款余额
执行结果
目前开户银行 Taipei Bank
Hung 目前余额: 0
存款 100 完成
Hung 目前余额: 100
12-2 类的访问权限——封装(encapsulation)
学习类至今可以看到我们可以从程序直接引用类内的属性(可参考ch12_6.py的第22行)与方法(可参考ch12_6.py的第23行),像这种类内的属性可以让外部引用的称公有(public)属性,而可以让外部引用的方法称公有方法。其实前面所使用的Banks类内的属性与方法皆是公有属性与方法。但是程序设计时可以发现,外部直接引用时也代表可以直接修改类内的属性值,这将造成类数据不安全。
因此Python也提供一个私有属性与方法的观念,这个观念的主要精神是类外无法直接更改类内的私有属性,类外也无法直接调用私有方法,这个观念又称封装(encapsulation)。
12-2-1 私有属性
为了确保类内的属性的安全,其实有必要限制外部无法直接获取类内的属性值。
程序实例ch12_7.py:外部直接获取属性值,造成存款余额不安全的实例。
# ch12_7.py
class Banks():
# 定义银行类别
def __init__(self, uname): # 初始化方法
self.name = uname # 设定存款者名字
self.balance = 0 # 设定开户金额是0
self.title = "Taipei Bank" # 设定银行名称
def save_money(self, money): # 设计存款方法
self.balance += money # 执行存款
print("存款 ", money, " 完成") # 打印存款完成
def withdraw_money(self, money): # 设计提款方法
self.balance -= money # 执行提款
print("提款 ", money, " 完成") # 打印提款完成
def get_balance(self): # 获得存款余额
print(self.name.title(), " 目前余额: ", self.balance)
hungbank = Banks('hung') # 定义对象hungbank
hungbank.get_balance()
hungbank.balance = 10000 # 类别外直接窜改存款余额
hungbank.get_balance()
执行结果
Hung 目前余额: 0
Hung 目前余额: 10000
上述程序第23行笔者直接在类外就更改了存款余额了,当第24行列出存款余额时,可以发现在没有经过Banks类内的save_money( )方法存钱动作,整个余额就从0元增至10000元。为了避免这种现象产生,Python对于类内的属性增加了私有属性(private attribute)的观念,应用方式是定义时在属性名称前面增加__(2个底线),定义为私有属性后,类外的程序就无法引用了。
程序实例ch12_8.py:重新设计ch12_7.py,主要是将Banks类的属性定义为私有属性,这样就无法由外部程序修改了。
# ch12_8.py
class Banks():
# 定义银行类别
def __init__(self, uname): # 初始化方法
self.__name = uname # 设定私有存款者名字
self.__balance = 0 # 设定私有开户金额是0
self.__title = "Taipei Bank" # 设定私有银行名称
def save_money(self, money): # 设计存款方法
self.__balance += money # 执行存款
print("存款 ", money, " 完成") # 打印存款完成
def withdraw_money(self, money): # 设计提款方法
self.__balance -= money # 执行提款
print("提款 ", money, " 完成") # 打印提款完成
def get_balance(self): # 获得存款余额
print(self.__name.title(), " 目前余额: ", self.__balance)
hungbank = Banks('hung') # 定义对象hungbank
hungbank.get_balance()
hungbank.balance = 10000 # 类别外直接窜改存款余额
hungbank.get_balance()
执行结果
Hung 目前余额: 0
Hung 目前余额: 0
请读者留意第6、7和8行笔者设定私有属性的方式,上述程序第23行笔者尝试修改存款余额,但从输出结果可以知道修改失败,因为执行结果的存款余额是0。对上述程序而言,存款余额只会在以存款(save_money( ))和提款(withdraw_money( ))方法被触发时,依参数金额更改。
12-2-2 私有方法
既然类有私有的属性,那么也有私有方法(private method),它的观念与私有属性类似,类外的程序无法调用。至于定义方式与私有属性相同,只要在方法前面加上__(2个底线)符号即可。若是延续上述程序实例,我们可能会遇上换汇的问题,通常银行在换汇时会针对客户对银行的贡献订出不同的汇率与手续费,这个部分是客户无法得知的,碰上这类的应用就很适合以私有方法处理换汇程序,为了简化问题,下列是在初始化类时,先设定美金与台币的汇率以及换汇的手续费,其中汇率(__rate)与手续费率(__service_charge)皆是私有属性。
下列是使用者可以调用的公有方法,在这里只能输入换汇的金额。
在上述公有方法中调用了__cal_rate(usa_d),这是私有方法,类外无法调用使用,下列是此私有方法的内容。
在上述私有方法中可以看到内部包含比较敏感且不适合外部人参与的数据。
程序实例ch12_9.py:下列是私有方法应用的完整程序代码实例。
# ch12_9.py
class Banks():
# 定义银行类别
def __init__(self, uname): # 初始化方法
self.__name = uname # 设定私有存款者名字
self.__balance = 0 # 设定私有开户金额是0
self.__title = "Taipei Bank" # 设定私有银行名称
self.__rate = 30 # 预设美金与台币换汇比例
self.__service_charge = 0.01 # 换汇的服务费
def save_money(self, money): # 设计存款方法
self.__balance += money # 执行存款
print("存款 ", money, " 完成") # 打印存款完成
def withdraw_money(self, money): # 设计提款方法
self.__balance -= money # 执行提款
print("提款 ", money, " 完成") # 打印提款完成
def get_balance(self): # 获得存款余额
print(self.__name.title(), " 目前余额: ", self.__balance)
def usa_to_taiwan(self, usa_d): # 美金兑换台币方法
self.result = self.__cal_rate(usa_d)
return self.result
def __cal_rate(self,usa_d): # 计算换汇这是私有方法
return int(usa_d * self.__rate * (1 - self.__service_charge))
hungbank = Banks('hung') # 定义对象hungbank
usdallor = 50
print(usdallor, " 美金可以兑换 ", hungbank.usa_to_taiwan(usdallor), " 台币")
执行结果
50 美金可以兑换 1485 台币
12-3 类的继承
在面向对象程序设计中类是可以继承的,其中被继承的类称父类(parent class)或基类(base class),继承的类称子类(child class)或衍生类(derived class)。类继承的最大优点是许多父类的公有方法或属性,在子类中不用重新设计,可以直接引用。
在程序设计时,基类(base class)必须在衍生类(derived class)前面,整个程序代码结构如下:
衍生类继承了基类的公有属性与方法,同时也可以有自己的属性与方法。
12-3-1 衍生类继承基类的实例应用
程序实例ch12_10.py:延续Banks类建立一个分行Shilin_Banks,这个衍生类没有任何数据,直接引用基类的公有函数,执行银行的存款作业。下列是与ch12_9.py不同的程序代码。
# ch12_10.py
class Banks():
# 定义银行类别
def __init__(self, uname): # 初始化方法
self.__name = uname # 设定私有存款者名字
self.__balance = 0 # 设定私有开户金额是0
self.__title = "Taipei Bank" # 设定私有银行名称
self.__rate = 30 # 预设美金与台币换汇比例
self.__service_charge = 0.01 # 换汇的服务费
def save_money(self, money): # 设计存款方法
self.__balance += money # 执行存款
print("存款 ", money, " 完成") # 打印存款完成
def withdraw_money(self, money): # 设计提款方法
self.__balance -= money # 执行提款
print("提款 ", money, " 完成") # 打印提款完成
def get_balance(self): # 获得存款余额
print(self.__name.title(), " 目前余额: ", self.__balance)
def usa_to_taiwan(self, usa_d): # 美金兑换台币方法
self.result = self.__cal_rate(usa_d)
return self.result
def __cal_rate(self,usa_d): # 计算换汇这是私有方法
return int(usa_d * self.__rate * (1 - self.__service_charge))
class Shilin_Banks(Banks):
# 定义士林分行
pass
hungbank = Shilin_Banks('hung') # 定义对象hungbank
hungbank.save_money(500)
hungbank.get_balance()
执行结果
存款 500 完成
Hung 目前余额: 500
上述第35和36行所引用的方法就是基类Banks的公有方法。
12-3-2 如何取得基类的私有属性
基于保护原因,类外是无法直接取得类内的私有属性,即使是它的衍生类也无法直接读取,如果真要取得可以使用return方式,传回私有属性内容。
程序实例ch12_11.py:衍生类对象取得基类的银行名称title的属性。
# ch12_11.py
class Banks():
# 定义银行类别
def __init__(self, uname): # 初始化方法
self.__name = uname # 设定私有存款者名字
self.__balance = 0 # 设定私有开户金额是0
self.__title = "Taipei Bank" # 设定私有银行名称
self.__rate = 30 # 预设美金与台币换汇比例
self.__service_charge = 0.01 # 换汇的服务费
def save_money(self, money): # 设计存款方法
self.__balance += money # 执行存款
print("存款 ", money, " 完成") # 打印存款完成
def withdraw_money(self, money): # 设计提款方法
self.__balance -= money # 执行提款
print("提款 ", money, " 完成") # 打印提款完成
def get_balance(self): # 获得存款余额
print(self.__name.title(), " 目前余额: ", self.__balance)
def usa_to_taiwan(self, usa_d): # 美金兑换台币方法
self.result = self.__cal_rate(usa_d)
return self.result
def __cal_rate(self,usa_d): # 计算换汇这是私有方法
return int(usa_d * self.__rate * (1 - self.__service_charge))
def bank_title(self): # 获得银行名称
return self.__title
class Shilin_Banks(Banks):
# 定义士林分行
pass
hungbank = Shilin_Banks('hung') # 定义对象hungbank
print("我的存款银行是: ", hungbank.bank_title())
执行结果
我的存款银行是: Taipei Bank
12-3-3 衍生类与基类有相同名称的属性
程序设计时,衍生类也可以有自己的初始化__init__( )方法,同时也有可能衍生类的属性与方法名称和基类重复,碰上这个状况Python会先找寻衍生类是否有这个名称,如果有则先使用,如果没有则使用基类的名称内容。
程序实例ch12_12.py:这个程序主要是将Banks类的title属性改为公有属性,但是在衍生类中则有自己的初始化方法,主要是基类与衍生类均有title属性,不同类对象将呈现不同的结果。下列是第8行的内容。
# ch12_12.py
class Banks():
# 定义银行类别
def __init__(self, uname): # 初始化方法
self.__name = uname # 设定私有存款者名字
self.__balance = 0 # 设定私有开户金额是0
self.title = "Taipei Bank" # 设定公有银行名称
self.__rate = 30 # 预设美金与台币换汇比例
self.__service_charge = 0.01 # 换汇的服务费
def save_money(self, money): # 设计存款方法
self.__balance += money # 执行存款
print("存款 ", money, " 完成") # 打印存款完成
def withdraw_money(self, money): # 设计提款方法
self.__balance -= money # 执行提款
print("提款 ", money, " 完成") # 打印提款完成
def get_balance(self): # 获得存款余额
print(self.__name.title(), " 目前余额: ", self.__balance)
def usa_to_taiwan(self, usa_d): # 美金兑换台币方法
self.result = self.__cal_rate(usa_d)
return self.result
def __cal_rate(self,usa_d): # 计算换汇这是私有方法
return int(usa_d * self.__rate * (1 - self.__service_charge))
def bank_title(self): # 获得银行名称
return self.title
class Shilin_Banks(Banks):
# 定义士林分行
def __init__(self, uname):
self.title = "Taipei Bank - Shilin Branch" # 定义分行名称
jamesbank = Banks('James') # 定义Banks类别对象
print("James's banks = ", jamesbank.title) # 打印银行名称
hungbank = Shilin_Banks('Hung') # 定义Shilin_Banks类别对象
print("Hung's banks = ", hungbank.title) # 打印银行名称
下列是修改部分程序代码内容。
执行结果
James's banks = Taipei Bank
Hung's banks = Taipei Bank - Shilin Branch
从上述可知Banks类对象James所使用的title属性是Taipei Bank,Shilin_Banks类对象Hung所使用的title属性是Taipei Bank - Shilin Branch。
12-3-4 衍生类与基类有相同名称的方法
程序设计时,衍生类也可以有自己的方法,同时也有可能衍生类的方法名称和基类方法名称重复,碰上这个状况Python会先找寻衍生类是否有这个名称,如果有则先使用,如果没有则使用基类的名称内容。
程序实例ch12_13.py:衍生类与基类名称重复的实例,这个程序的基类与衍生类均有bank_title( )函数,Python会触发bank_title( )方法的对象去判别应使用哪一个方法执行。
# ch12_13.py
class Banks():
# 定义银行类别
def __init__(self, uname): # 初始化方法
self.__name = uname # 设定私有存款者名字
self.__balance = 0 # 设定私有开户金额是0
self.__title = "Taipei Bank" # 设定私有银行名称
self.__rate = 30 # 预设美金与台币换汇比例
self.__service_charge = 0.01 # 换汇的服务费
def save_money(self, money): # 设计存款方法
self.__balance += money # 执行存款
print("存款 ", money, " 完成") # 打印存款完成
def withdraw_money(self, money): # 设计提款方法
self.__balance -= money # 执行提款
print("提款 ", money, " 完成") # 打印提款完成
def get_balance(self): # 获得存款余额
print(self.__name.title(), " 目前余额: ", self.__balance)
def usa_to_taiwan(self, usa_d): # 美金兑换台币方法
self.result = self.__cal_rate(usa_d)
return self.result
def __cal_rate(self,usa_d): # 计算换汇这是私有方法
return int(usa_d * self.__rate * (1 - self.__service_charge))
def bank_title(self): # 获得银行名称
return self.__title
class Shilin_Banks(Banks):
# 定义士林分行
def __init__(self, uname):
self.title = "Taipei Bank - Shilin Branch" # 定义分行名称
def bank_title(self): # 获得银行名称
return self.title
jamesbank = Banks('James') # 定义Banks类别对象
print("James's banks = ", jamesbank.bank_title()) # 打印银行名称
hungbank = Shilin_Banks('Hung') # 定义Shilin_Banks类别对象
print("Hung's banks = ", hungbank.bank_title()) # 打印银行名称
执行结果
James's banks = Taipei Bank
Hung's banks = Taipei Bank - Shilin Branch
上述程序的观念如下:
上述第30行的bank_title( )属于Banks类,第37行的bank_title( )属于Shilin_Banks类。第40行是Banks对象,所以第41行会触发第30行的bank_title( )方法。第42行是Shilin_Banks对象,所以第43行会触发第37行的bank_title( )方法。其实上述方法就是面向对象的多型(polymorphism),但是多型不一定需要是有父子关系的类。读者可以将以上想成方法多功能化,相同的函数名称,放入不同类型的对象可以产生不同的结果。至于使用者不需要知道是如何设计的,隐藏在内部的设计细节交由程序设计师负责。12-4节笔者还会举实例说明。
12-3-5 衍生类引用基类的方法
衍生类引用基类的方法时需使用super( ),下列将使用另一类的类了解这个观念。
程序实例ch12_14.py:这是一个衍生类调用基类方法的实例,笔者首先建立一个Animals类,然后建立这个类的衍生类Dogs,Dogs类在初始化中会使用super( )调用Animals类的初始化方法,可参考第14行,经过初始化处理后,mydog.name将由“lily”变为“My pet lily”。
# ch12_14.py
class Animals():
"""Animals类别, 这是基类 """
def __init__(self, animal_name, animal_age ):
self.name = animal_name # 纪录动物名称
self.age = animal_age # 纪录动物年龄
def run(self): # 输出动物 is running
print(self.name.title(), " is running")
class Dogs(Animals):
"""Dogs类别, 这是Animal的衍生类别 """
def __init__(self, dog_name, dog_age):
super().__init__('My pet ' + dog_name.title(), dog_age)
mycat = Animals('lucy', 5) # 建立Animals对象以及测试
print(mycat.name.title(), ' is ', mycat.age, " years old.")
mycat.run()
mydog = Dogs('lily', 6) # 建立Dogs对象以及测试
print(mydog.name.title(), ' is ', mydog.age, " years old.")
mydog.run()
执行结果
Lucy is 5 years old.
Lucy is running
My Pet Lily is 6 years old.
My Pet Lily is running
12-3-6 三代同堂的类与取得基类的属性super( )
在继承观念里,我们也可以使用Python的super( )方法取得基类的属性,这对于设计三代同堂的类是很重要的。
下列是一个三代同堂的程序,在这个程序中有祖父(Grandfather)类,它的子类是父亲(Father)类,父亲类的子类是Ivan类。其实Ivan要取得父亲类的属性很容易,可是要取得祖父类的属性时就会碰上困难,解决方式是在Father类与Ivan类的__init__( )方法中增加下列设定:
super( ).__init__( ) # 将父类的属性复制
这样就可以使Ivan取得祖父(Grandfather)类的属性了。
程序实例ch12_15.py:这个程序会建立一个Ivan类的对象ivan,然后分别调用Father类和Grandfather类的方法打印信息,接着分别取得Father类和Grandfather类的属性。
# ch12_15
class Grandfather():
""" 定义祖父的资产 """
def __init__(self):
self.grandfathermoney = 10000
def get_info1(self):
print("Grandfather's information")
class Father(Grandfather): # 父类别是Grandfather
""" 定义父亲的资产 """
def __init__(self):
self.fathermoney = 8000
super().__init__()
def get_info2(self):
print("Father's information")
class Ivan(Father): # 父类别是Father
""" 定义Ivan的资产 """
def __init__(self):
self.ivanmoney = 3000
super().__init__()
def get_info3(self):
print("Ivan's information")
def get_money(self): # 取得资产明细
print("\nIvan资产: ", self.ivanmoney,
"\n父亲资产: ", self.fathermoney,
"\n祖父资产: ", self.grandfathermoney)
ivan = Ivan()
ivan.get_info3() # 从Ivan中获得
ivan.get_info2() # 流程 Ivan -> Father
ivan.get_info1() # 流程 Ivan -> Father -> Grandtather
ivan.get_money() # 取得资产明细
执行结果
Ivan's information
Father's information
Grandfather's information
Ivan资产: 3000
父亲资产: 8000
祖父资产: 10000
上述程序各类的相关图形如下:
12-3-7 兄弟类属性的取得
假设有一个父亲(Father)类,这个父亲类有2个儿子,分别是Ivan类和Ira类,如果Ivan类想取得Ira类的属性iramoney,可以使用下列方法。
Ira( ).iramoney # Ivan取得Ira的属性iramoney
程序实例ch12_16.py:设计3个类,Father类是Ivan和Ira类的父类,所以Ivan和Ira算是兄弟类,这个程序可以从Ivan类分别读取Father和Ira类的资产属性。这个程序最重要的是第21行,请留意取得Ira属性的写法。
# ch12_16
class Father():
""" 定义父亲的资产 """
def __init__(self):
self.fathermoney = 10000
class Ira(Father): # 父类别是Father
""" 定义Ira的资产 """
def __init__(self):
self.iramoney = 8000
super().__init__()
class Ivan(Father): # 父类别是Father
""" 定义Ivan的资产 """
def __init__(self):
self.ivanmoney = 3000
super().__init__()
def get_money(self): # 取得资产明细
print("Ivan资产: ", self.ivanmoney,
"\n父亲资产: ", self.fathermoney,
"\nIra资产 : ", Ira().iramoney) # 注意写法
ivan = Ivan()
ivan.get_money() # 取得资产明细
执行结果
Ivan资产: 3000
父亲资产: 10000
Ira资产 : 8000
上述程序各类的相关图形如下:
12-4 多型(polymorphism)
在12-3-4节笔者已经有说明基类与衍生类有相同方法名称的实例,其实那就是本节欲说明的多型(polymorphism)的基本观念,但是多型(polymorphism)的观念是不局限在必须有父子关系的类中的。
程序实例ch12_17.py:这个程序有3个类,Animals类是基类,Dogs类是Animals类的衍生类,基于继承的特性所以2个类皆有which( )和action( )方法,另外设计了一个与上述无关的类Monkeys,这个类也有which( )和action( )方法,然后程序分别调用which( )和action( )方法,程序会由对象类判断应该使用哪一个方法响应程序。
# ch12_17.py
class Animals():
"""Animals类别, 这是基类 """
def __init__(self, animal_name):
self.name = animal_name # 纪录动物名称
def which(self): # 回传动物名称
return 'My pet ' + self.name.title()
def action(self): # 动物的行为
return ' sleeping'
class Dogs(Animals):
"""Dogs类别, 这是Animal的衍生类别 """
def __init__(self, dog_name): # 纪录动物名称
super().__init__(dog_name.title())
def action(self): # 动物的行为
return ' running in the street'
class Monkeys():
"""猴子类别, 这是其他类别 """
def __init__(self, monkey_name): # 纪录动物名称
self.name = 'My monkey ' + monkey_name.title()
def which(self): # 回传动物名称
return self.name
def action(self): # 动物的行为
return ' running in the forest'
def doing(obj): # 列出动物的行为
print(obj.which(), "is", obj.action())
my_cat = Animals('lucy') # Animals物件
doing(my_cat)
my_dog = Dogs('gimi') # Dogs物件
doing(my_dog)
my_monkey = Monkeys('taylor') # Monkeys物件
doing(my_monkey)
执行结果
My pet Lucy is sleeping
My pet Gimi is running in the street
My monkey Taylor is running in the forest
上述程序各类的相关图形如下:
对上述程序而言,第30行的my_cat是Animal类对象,所以在31行此对象会触发Animal类的which( )和action( )方法。第32行的my_dog是Dogs类对象,所以在33行此对象会触发Dogs类的which( )和action( )方法。第34行的my_monkey是Monkeys类对象,所以在35行此对象会触发Monkeys类的which( )和action( )方法。
12-5 多重继承
在面向对象的程序设计中,也常会发生一个类继承多个类的应用,此时子类也同时继承了多个类的方法。在这个时候,读者应该了解当多个父类拥有相同名称的方法时,应该先执行哪一个父类的方法。在程序中可用下列语法代表继承多个类。
class 类名称(父类1, 父类2, … , 父类n):
类内容
程序实例ch12_18.py:这个程序Ivan类继承了Father和Uncle类,Grandfather类则是Father和Uncle类的父类。在这个程序中笔者只设定一个Ivan类的对象ivan,然后由这个类分别调用action3( )、action2( )和action1( ),其中Father和Uncle类同时拥有action2( )方法,读者可以观察最后是执行哪一个action2( )方法。
# ch12_18.py
class Grandfather():
""" 定义祖父类别 """
def action1(self):
print("Grandfather")
class Father(Grandfather):
""" 定义父亲类别 """
def action2(self): # 定义action2()
print("Father")
class Uncle(Grandfather):
""" 定义叔父类别 """
def action2(self): # 定义action2()
print("Uncle")
class Ivan(Father, Uncle):
""" 定义Ivan类别 """
def action3(self):
print("Ivan")
ivan = Ivan()
ivan.action3() # 顺序 Ivan
ivan.action2() # 顺序 Ivan -> Father
ivan.action1() # 顺序 Ivan -> Father -> Grandfather
执行结果
Ivan
Father
Grandfather
上述程序各类的相关图形如下:
程序实例ch12_19.py:这个程序基本上是重新设计ch12_18.py,主要是Father和Uncle类的方法名称不一样,Father类是action3( ),Uncle类是action2( ),这个程序在建立Ivan类的ivan对象后,会分别启动各类的actionX( )方法。
# ch12_19.py
class Grandfather():
""" 定义祖父类别 """
def action1(self):
print("Grandfather")
class Father(Grandfather):
""" 定义父亲类别 """
def action3(self): # 定义action3()
print("Father")
class Uncle(Grandfather):
""" 定义叔父类别 """
def action2(self): # 定义action2()
print("Uncle")
class Ivan(Father, Uncle):
""" 定义Ivan类别 """
def action4(self):
print("Ivan")
ivan = Ivan()
ivan.action4() # 顺序 Ivan
ivan.action3() # 顺序 Ivan -> Father
ivan.action2() # 顺序 Ivan -> Father -> Uncle
ivan.action1() # 顺序 Ivan -> Father -> Uncle -> Grandfather
执行结果
Ivan
Father
Uncle
Grandfather
12-6 type与instance
一个大型程序设计可能是由许多人合作设计,有时我们想了解某个对象变量的数据类型,或是所属类关系,可以使用本节所述的方法。
12-6-1 type( )
这个函数先前已经使用许多次了,可以使用type( )函数得到某一对象变量的类。
程序实例ch12_20.py:列出类对象与对象内方法的数据类型。
# ch12_20.py
class Grandfather():
""" 定义祖父类别 """
pass
class Father(Grandfather):
""" 定义父亲类别 """
pass
class Ivan(Father):
""" 定义Ivan类别 """
def fn(self):
pass
grandfather = Grandfather()
father = Father()
ivan = Ivan()
print("grandfather对象类型: ", type(grandfather))
print("father对象类型 : ", type(father))
print("ivan对象类型 : ", type(ivan))
print("ivan对象fn方法类型 : ", type(ivan.fn))
执行结果
grandfather对象类型: <class '__main__.Grandfather'>
father对象类型 : <class '__main__.Father'>
ivan对象类型 : <class '__main__.Ivan'>
ivan对象fn方法类型 : <class 'method'>
由上图可以得到类的对象类型是class,同时会列出“main.类的名称”。如果是类内的方法同时也列出“method”方法。
12-6-2 isinstance( )
isinstance( )函数可以传回对象的类是否属于某一类,它包含2个参数,它的语法如下:
isinstance(对象, 类) # 可传回True或False
如果对象的类是属于第2个参数类或属于第2个参数的子类,则传回True,否则传回False。
程序实例ch12_21.py:一系列isinstance( )函数的测试。
# ch12_21.py
class Grandfather():
""" 定义祖父类别 """
pass
class Father(Grandfather):
""" 定义父亲类别 """
pass
class Ivan(Father):
""" 定义Ivan类别 """
def fn(self):
pass
grandfa = Grandfather()
father = Father()
ivan = Ivan()
print("ivan属于Ivan类别: ", isinstance(ivan, Ivan))
print("ivan属于Father类别: ", isinstance(ivan, Father))
print("ivan属于GrandFather类别: ", isinstance(ivan, Grandfather))
print("father属于Ivan类别: ", isinstance(father, Ivan))
print("father属于Father类别: ", isinstance(father, Father))
print("father属于Grandfather类别: ", isinstance(father, Grandfather))
print("grandfa属于Ivan类别: ", isinstance(grandfa, Ivan))
print("grandfa属于Father类别: ", isinstance(grandfa, Father))
print("grandfa属于Grandfather类别: ", isinstance(grandfa, Grandfather))
执行结果
ivan属于Ivan类别: True
ivan属于Father类别: True
ivan属于GrandFather类别: True
father属于Ivan类别: False
father属于Father类别: True
father属于Grandfather类别: True
grandfa属于Ivan类别: False
grandfa属于Father类别: False
grandfa属于Grandfather类别: True
12-7 特殊属性
当设计或是看到别人设计的Python程序时,若是遇到__xx__类的字符串就要特别留意了,这些大多数是特殊属性或方法,笔者将简要说明几个重要常见的属性。
12-7-1 文档字符串_doc__
文档字符串的英文原意是文档字符串(docstring),Python鼓励程序设计师在设计函数或类时,尽量为函数或类增加文档的批注,未来可以使用__doc__特殊属性列出此文档批注。
程序实例ch12_22.doc:将文档批注应用在函数。
# ch12_22.py
def getMax(x, y):
'''文文件字符串实例
建议x, y是整数
这个函数将传回较大值'''
if int(x) > int(y):
return x
else:
return y
print(getMax(2, 3)) # 打印较大值
print(getMax.__doc__) # 打印文文件字符串docstring
执行结果
3
文文件字符串实例
建议x, y是整数
这个函数将传回较大值
程序实例ch12_23.doc:将文档批注应用在类与类内的方法。
# ch12_23.py
class Myclass:
'''文文件字符串实例
Myclass类别的应用'''
def __init__(self, x):
self.x = x
def printMe(self):
'''文本文件字符串实例
Myclass类别内printMe方法的应用'''
print("Hi", self.x)
data = Myclass(100)
data.printMe()
print(data.__doc__) # 打印Myclass文文件字符串docstring
print(data.printMe.__doc__) # 打印printMe文文件字符串docstring
执行结果
Hi 100
文文件字符串实例
Myclass类别的应用
文本文件字符串实例
Myclass类别内printMe方法的应用
了解以上观念后,如果读者看到有一个程序代码如下:
以上只是列出Python系统内部有关字符串的docstring。
12-7-2 __name__属性
如果你是Python程序设计师,常在网络上看别人写的程序,一定会经常在程序末端看到下列叙述:
if __name__ == ‘__main__':
doSomething( )
初学Python时,笔者照上述撰写,程序一定可以执行,当时不晓得意义,现在觉得应该要告诉读者。如果上述程序是自己执行,那么__name__就一定是__main__。
程序实例ch12_24.py:一个程序只有一行,就是打印__name__。
# ch12_24.py
print('ch12_24.py module name = ', __name__)
执行结果
ch12_24.py module name = __main__
经过上述实例,我们知道,如果程序是自己执行,name__就是__main。所以下列程序实例可以列出结果。
程序实例ch12_25.py:name == __main__的应用。
# ch12_25.py
def myFun():
print("__name__ == __main__")
if __name__ == '__main__':
myFun()
执行结果
__name__ == __main__
如果ch12_24.py是被import到另一个程序,则__name__是本身的文件名。
程序实例ch12_26.py:这个程序import导入ch12_24.py,结果__name__变成了ch12_24。
# ch12_26.py
import ch12_24
执行结果
ch12_24.py module name = ch12_24
程序实例ch12_27.py:这个程序import导入ch12_25.py,由于__name__已经不再是__main__,所以程序没有任何输出。
# ch12_27.py
import ch12_25
执行结果
所以总结就是__name__可以判别这个程序是自己执行或是被其他程序import导入当成模块使用。
12-8 类的特殊方法
12-8-1 str( )方法
这是类的特殊方法,可以协助返回易读取的字符串。
程序实例ch12_28.py:在没有定义__str__( )方法下,列出类的对象。
# ch12_28.py
class Name:
def __init__(self, name):
self.name = name
a = Name('Hung')
print(a)
执行结果
<__main__.Name object at 0x0000017A3F84B510>
上述在没有定义__str__( )方法下,我们获得了一个不太容易阅读的结果。
程序实例ch12_29.py:在定义__str__( )方法下,重新设计上一个程序。
# ch12_29.py
class Name:
def __init__(self, name):
self.name = name
def __str__(self):
return '%s' % self.name
a = Name('Hung')
print(a)
执行结果
Hung
上述定义了__str__( )方法后,就得到一个适合阅读的结果了。对于程序ch12_29.py而言,如果我们在Python Shell窗口输入a,将一样获得不容易阅读的结果。
12-8-2 repr( )方法
上述原因是,如果只是在Python Shell窗口输入类变量a,系统是调用__repr__( )方法做响应,为了要获得容易阅读的结果,我们也需定义此方法。
程序实例ch12_30.py:定义__repr__( )方法,其实此方法内容与__str__( )相同,所以可以用等号取代。
# ch12_30.py
class Name:
def __init__(self, name):
self.name = name
def __str__(self):
return '%s' % self.name
__repr__ = __str__
a = Name('Hung')
print(a)
执行结果
Hung
12-8-3 iter( )方法
建立类的时候也可以将类定义成一个迭代对象,类似list或tuple,供for … in循环内使用,这时类需设计next( )方法,取得下一个值,直到达到结束条件,可以使用raise StopIteration (第15章会解说,raise)终止继续。
程序实例ch12_31.py:Fib序列数的设计。
# ch12_31.py
class Fib():
def __init__(self, max):
self.max = max
def __iter__(self):
self.a = 0
self.b = 1
return self
def __next__(self):
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib
for i in Fib(100):
print(i)
执行结果
0
1
1
2
3
5
8
13
21
34
55
89