(請先看置頂博文)https://blog.csdn.net/GenuineMonster/article/details/104495419
目錄
(請先看置頂博文)https://blog.csdn.net/GenuineMonster/article/details/104495419
?
?
?
?
一、類和對象
1、類
2、對象
二、類的創建和實例化
1、類的創建
2、類的實例化
2.1 訪問屬性
2.2 調用方法?
2.3 創建多個實例
3、使用類和實例
3.1 給屬性指定默認值
3.2 修改屬性的值
三、繼承
1、子類的方法__init__()
2、在子類中定義新的屬性和方法
3、重寫父類的方法
4、將實例用作屬性
5、模擬實物
6、導入類
6.1 導入單個類
6.2 在一個模塊中存儲多個類
6.3 從一個模塊中導入多個類
6.4 導入整個模塊
6.5?導入模塊中的所有類
6.6?在一個模塊中導入另一個模塊
7、Python標準庫的些許知識
8、寫在最后
8.1 類編碼風格
8.2 總結
附錄 一? ?類的相關概念的理解?
?
?
一、類和對象
說起類的定義,不得不先提起面向對象編程。常見的編程思想有面向對象和面向過程。具體的內涵和區別可以到我的微信公眾號查看,這里就不贅述了:?https://mp.weixin.qq.com/s?__biz=MzkyNjAwODg1Mw==&mid=2247483701&idx=1&sn=7380c52bfbb7f73e7a71105e35fbf0fc&chksm=c23c924ff54b1b592b8be9cb2d14cf9ea5c46e5d85251f37564096919d15630911f0dc93de7b&token=177947859&lang=zh_CN#rd
在面向對象編程中,我們需要通過編寫代碼來表示現實世界中的事物。而現實世界中的事物千奇百怪,怎么表示呢?學過生物的都知道有:?界門綱目科屬種的分類方法。這里(面向對象)我們把現實世界的事物分為各個種類,簡稱“類”,并基于定義的類,創建現實世界的萬物。為了便于理解,我這里對類、對象、實例化給出一個非官方的理解性定義。
1、類
? ? ? ?類:是我們人對現實世界萬事萬物的抽象和提煉,把我們要研究的對象分為主要的有限的幾個類。舉個例子:王者榮耀手游。我們玩游戲時常見的就這么幾個類(僅根據我個人的分類標準進行的分類,旨在說明類的概念,勿剛):建筑類(防御塔、泉水、商店等)、人物類(各路英雄:亞瑟、后裔等)、小兵野獸類(敵方己方野區野怪以及雙方的小兵)。這么分類的標準在于“是否可被用戶操縱以及是否可以移動”,當然不同的人,有不同的分類方法。那么,你會發現,面向對象指的是我們以對象為出發點,對其進行分析,根據自己的標準進行分類。當我們分類完成的時候(這其實是軟件工程中的一小部分),就可以進行下一步了。我們既然可以把一些東西分為一類,那就說明他們有共同之處(同理,他們有共同之處才會被分為一類。如英雄類,均可移動和被用戶操縱,都有唯一的名字,都有英雄技能,都有英雄的屬性(攻擊速度、攻擊力、生命值等))。這些共同之處,構成了英雄類,那么英雄類的內部是否還可以細分?答案是肯定的。還可以細分為英雄屬性類和英雄技能類,但是在描述時,類中還有類,這似乎有點難以描述。所以類中細分出來的英雄屬性類和英雄技能類都被重命名了,叫類的屬性和類的方法。其中,英雄的名字、英雄屬性等都屬于屬性,英雄的技能和行為(擊殺小兵、擊殺野怪和英雄、死亡、復活、購買裝備)都屬于方法。
類的小節:編程語言中的類其實是人對現實世界萬事萬物的抽象和提煉,把我們要研究的對象分為主要的、有限的幾個類。類中一般包含兩個成分:屬性和方法(幾乎所有的、面向對象的程序設計語言都是這個術語)
偽代碼:以英雄類為例。
class 英雄():屬性:名字物理攻擊力物理防御力法術攻擊力法術防御力攻擊速度暴擊率生命值魔法值...方法:移動死亡復活一技能二技能三技能...
2、對象
正如書中說的那樣:我們在編寫類時,定義一大類對象都有的通用行為。在基于類創建對象時,每個對象都自動具備這種通用行為,然后可根據需要(需求)賦予每個對象獨特的個性。根據類創建對象被稱為實例化,我們可以指定在實例中存儲什么信息,或是完成哪些操作,甚至可以編寫一些類來擴展既有類的功能,讓相似的類能夠高效的共享代碼。使用類幾乎可以模擬任何東西,接下來通過一段例代碼展示一下Python3.x中的類。
(對類和對象概念不清楚的同學,可以去看本博文末尾的附錄一,有點大話王者榮耀的意思)
二、類的創建和實例化
1、類的創建
我們創建一個Dog類,里面將會有存儲名字和年齡,并且賦予小狗蹲下和打滾的能力。
class Dog(): # 在Python中,首字母大寫的名稱指的是類# 類定義的括號是空的,是因為我們要從空白創建這個類"""一次模擬小狗的簡單嘗試""" # 文檔字符串,對類的功能進行描述def __init__(self, name, age):self.name = name # 變量前都有前綴self,以self為前綴的變量都可供類中的所有方法使用self.age =age # 還可以通過類的任何實例來訪問這些變量。def sit(self):"""模擬小狗被命令時蹲下"""print(self.name.title() + " is now sitting.")def roll_over(self):"""模擬小狗被命令時打滾"""print(self.name.title() + " rolled over!")
? ? ? ?對代碼進行一個簡單的解讀:Dog類中,我們定義了3個方法,分別是__init__(),sit()以及roll_over()。因為是類中的函數,所以我們在這里稱其為方法。“__init__()”是一個特殊的方法,每當我們使用Dog類創建新的實例時,Python都會自動運行這個函數,類似于C++里的構造函數。在這個方法的名稱中,它的開頭和結尾都有兩條下劃線,這是一種約定,旨在避免Python默認方法和普通方法發生名稱沖突。我們將方法__init__()定義成了包含三個形參:self、name、age。在這個方法的定義中,形參self必不可少,而且還必須位于其他形參的前面。為何必須在方法的定義中包含形參self呢?因為Python調用這個__init__()方法來創建Dog實例時,將自動傳入實參self。每個與類相關聯的方法調用都自動傳遞實參self,它是一個指向實例本身的引用,讓實例能夠訪問類中的屬性和方法。我們在創建Dog實例時,Python將會調用Dog類的方法__init__()。通過實參向Dog()傳遞名字和年齡;self會自動傳遞(次次如此)。
? ? ? 代碼中的self.name和self.age都是可以通過實例訪問的,像這樣可以通過實例訪問的變量稱為屬性。Dog類中還有兩個方法,這些方法不需要額外的信息,所以它們只有一個形參self。
2、類的實例化
? ? ?類說白了就是一張構造圖,里面有創建某一物體的說明,我們可以根據說明,創建出具體的東西,比如上面的Dog類。接下來我們創建一只狗:
class Dog(): # 在Python中,首字母大寫的名稱指的是類# 類定義的括號是空的,是因為我們要從空白創建這個類"""一次模擬小狗的簡單嘗試""" # 文檔字符串,對類的功能進行描述def __init__(self, name, age):self.name = name self.age =agedef sit(self):"""模擬小狗被命令時蹲下"""print(self.name.title() + " is now sitting.")def roll_over(self):"""模擬小狗被命令時打滾"""print(self.name.title() + " rolled over!")my_dog = Dog('willie',6) # 創建一只名為willie的6歲小狗,Python遇到這行代碼,就會調用__init__()方法,將willie和6傳進去創建一個表示特定小狗的實例
your_dog = Dog('lucy',3)
print("My dog's name is " + my_dog.name.title() + ". ")
print("My dog is " + str(my_dog.age) + " years old. " )
__init__()方法中并未顯式的包含return語句,但Python會自動返回一個表示這條小狗的實例,并存儲在my_dog中。類和實例的命名是要遵循一定的規則的:我們通常可以認為首字母大寫的名稱指的是類,而小寫的名稱指的是根據類創建的實例。
2.1 訪問屬性
要訪問實例的屬性,可以使用句點表示法,例如上述代碼的my_dog.name。原理是:Python先找到實例my_dog,然后再找到與這個實例相關的屬性name。上述代碼的運行結果如下圖:
2.2 調用方法?
根據Dog類創建實例后,就可以使用句點表示法來調用Dog類中定義的任何方法。如果屬性和方法都指定了合適的描述性名稱,即便我們從未見過代碼塊,也知道其功能。代碼如下圖所示:
class Dog(): # 在Python中,首字母大寫的名稱指的是類# 類定義的括號是空的,是因為我們要從空白創建這個類"""一次模擬小狗的簡單嘗試"""def __init__(self, name, age):self.name = nameself.age =agedef sit(self):"""模擬小狗被命令時蹲下"""print(self.name.title() + " is now sitting.")def roll_over(self):"""模擬小狗被命令時打滾"""print(self.name.title() + " rolled over!")my_dog = Dog('willie',6)
your_dog = Dog('lucy',3)
print("My dog's name is " + my_dog.name.title() + ". ")
print("My dog is " + str(my_dog.age) + " years old. " )
my_dog.sit()
my_dog.roll_over()
2.3 創建多個實例
剛才說到,類是一張構造圖。一旦有了類,你想創建多少個實例,都可以。你可以按照需求創建任意數量的實例。例代碼:
class Dog(): # 在Python中,首字母大寫的名稱指的是類# 類定義的括號是空的,是因為我們要從空白創建這個類"""一次模擬小狗的簡單嘗試"""def __init__(self, name, age):self.name = nameself.age =agedef sit(self):"""模擬小狗被命令時蹲下"""print(self.name.title() + " is now sitting.")def roll_over(self):"""模擬小狗被命令時打滾"""print(self.name.title() + " rolled over!")my_dog = Dog('willie',6)
your_dog = Dog('lucy',3)
print("My dog's name is " + my_dog.name.title() + ". ")
print("My dog is " + str(my_dog.age) + " years old. " )
my_dog.sit()
my_dog.roll_over()print("\nYour dog's name is " + your_dog.name.title() + ". ")
print("Your dog is " + str(your_dog.age) + " years old. " )
your_dog.sit()
3、使用類和實例
? ? ? ?學了類之后,我們可以使用類來模擬現實世界的很多情景。類編寫好了之后,更多的時間將會花在使用根據類創建的實例上。需要執行的一個重要任務是修改實例的屬性,可以直接修改,也可以編寫方法以特定的方式修改。接下來創建汽車類,來展示屬性修改的不同方法。
例代碼:
class Car():"""一次模擬汽車的簡單嘗試"""def __init__(self,make,model,year):"""初始化描述汽車的屬性"""self.make = make # 3個屬性,意味著創建新的car時,需要指定制造商、型號和生產年份self.model = modelself.year = year# 類的方法def get_descriptive_name(self):"""返回整潔的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()# 實例化
my_new_car = Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())
3.1 給屬性指定默認值
類中的每個屬性都必須有初始值,0和空字符串都行。在有些情況下,如設置默認值時,在方法__init__()內指定這種初始值是可行的;如果已對某個屬性指定了初始值,就無需包含為它提供初始值的形參。我們為上述代碼增加一個名為odometer_reading的屬性,其初始值總是為0。我們還添加一個名為read_odometer()的方法,用于讀取汽車的里程表:
class Car():"""一次模擬汽車的簡單嘗試"""def __init__(self,make,model,year):"""初始化描述汽車的屬性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0# 類的方法def get_descriptive_name(self):"""返回整潔的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer_reading(self):"""打印一條指出汽車里程的信息"""print("This car has " + str(self.odometer_reading) + " miles on it.")# 實例化
my_new_car = Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer_reading()
3.2 修改屬性的值
可以以三種不同的方式修改屬性的值:A、直接通過實例進行修改。B、通過方法進行設置。C、通過方法進行遞增(增加特定的值)
A、直接通過實例進行修改
使用句點表示法來直接訪問并設置汽車的屬性odometer_reading。下列代碼的第一行,讓Python在實例my_new_car中找到屬性odometer_reading,并將該屬性的值設置為23。
my_new_car.odometer_reading = 23
my_new_car.read_odometer_reading()
B、通過方法修改屬性的值
如果有替你更新屬性的方法,將省事的多。這樣,我們就無需直接訪問屬性。可以將值傳遞給一個方法,由它在內部對屬性的內容進行更新,我們為代碼增添一個名為update_odometer()方法。對代碼所做的唯一修改是添加了方法update_odometer()。這個方法接受一個里程值,并將其存儲到self.odometer_reading中。
class Car():"""一次模擬汽車的簡單嘗試"""def __init__(self,make,model,year):"""初始化描述汽車的屬性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0# 類的方法def get_descriptive_name(self):"""返回整潔的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer_reading(self):"""打印一條指出汽車里程的信息"""print("This car has " + str(self.odometer_reading) + " miles on it.")# 新增加的代碼def update_odometer(self,mileage): """將里程表讀數設置為指定的值"""self.odometer_reading = mileage# 實例化
my_new_car = Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())# 新增加的代碼
my_new_car.update_odometer(23)
my_new_car.read_odometer_reading()
可以對方法update_odometer()進行擴展,使其在修改里程表讀數時做些額外的工作。比如禁止任何人回調里程表的讀數:
def update_odometer(self,mileage):"""將里程表讀數設置為指定的值禁止將里程表讀數往回調"""if mileage >= self.odometer_reading:self.odometer_reading = mileageelse:print("You can't roll back an odometer!")
增添了這段代碼后,update_odometer()在修改屬性前檢查指定的讀數是否合理。合理就修改,不合理將會有警告信息。
C、通過方法對屬性的值進行遞增
假設我們買了一輛二手車,從購買到登記期間增加了100英里的里程,通過下面新定義的方法,相應的增加里程表讀數:
def increment_odometer(self,miles):"""將里程表讀數增加指定的量"""self.odometer_reading +=milesmy_used_car = Car('subaru','outback',2013)
print(my_used_car.get_descriptive_name())my_used_car.update_odometer(23500)
my_used_car.read_odometer_reading()my_used_car.increment_odometer(100)
my_used_car.read_odometer_reading()
當然可以輕松的修改以上的方法,防止增量為負值。
三、繼承
編寫類時,并非總是要從空白開始。如果你要編寫的類是另一個現成類的特殊版本,此時就可以使用繼承。繼承的意思就是我們繼承文化、繼承遺產中繼承的意思,無需多說。當一個類繼承另一個類時,它將自動獲得另一個類的所有屬性和方法;原有的類稱為父類,而新類稱為子類。子類繼承了其父類的所有屬性和方法,同時還可以定義自己的屬性和方法。
1、子類的方法__init__()
創建子類的實例時,Python首先需要完成的任務是給父類的所有屬性賦值。為此,子類的方法__init__()需要父類施以援手。例如,下面我們將會模擬電動汽車。電動汽車是一種特殊的汽車,因此我們可以基于前面創建的Car類來創建ElectricCar,這樣我們就只需為電動車特有的屬性和行為編寫代碼就ok了。例代碼如下:
# 父類
class Car():"""一次模擬汽車的簡單嘗試"""def __init__(self,make,model,year):"""初始化描述汽車的屬性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0# 類的方法def get_descriptive_name(self):"""返回整潔的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer_reading(self):"""打印一條指出汽車里程的信息"""print("This car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self,mileage):"""將里程表讀數設置為指定的值禁止將里程表讀數往回調"""if mileage >= self.odometer_reading:self.odometer_reading = mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):"""將里程表讀數增加指定的量"""self.odometer_reading +=miles
# 子類
class ElectricCar(Car):"""電動汽車的獨特之處"""def __init__(self,make,model,year):# 這一行是初始化父類的屬性(必須要做)"""初始化父類的屬性""" super().__init__(make,model,year) # 調用父類的方法,讓子類在創建實例時,# 包含父類的所有屬性my_tesla = ElectricCar('tesla','model s', 2016)
print(my_tesla.get_descriptive_name())
對上述代碼的分析:映入眼簾的首先是Car類的代碼(父類)。
A、創建ElectricCar(子類)時,Car(父類)必須包含在當前文件中,且位于子類的前面。
B、定義子類時,必須在括號內指定父類的名稱。另外,還得在子類的__init__()方法中接受創建Car實例所需的信息。
C、super()是一個特殊的函數,幫助Python將父類和子類關聯起來。代碼中的這一行代碼“super().__init__(make,model,year)”讓Python調用ElectricCar的父類的方法__init__(),讓ElectricCar實例包含父類的所有屬性。父類也稱為超類,名稱super因此得名。
D、一定要記得在定義子類時,要在子類的__init__()方法中初始化父類的屬性
2、在子類中定義新的屬性和方法
子類中可添加新的屬性和方法,下面我們添加一個電動汽車特有的屬性(電瓶),以及一個描述該屬性的方法。我們將存儲電瓶的容量,并編寫一個打印電瓶描述的方法,全部代碼如下:
# 父類
class Car():"""一次模擬汽車的簡單嘗試"""def __init__(self,make,model,year):"""初始化描述汽車的屬性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0# 類的方法def get_descriptive_name(self):"""返回整潔的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer_reading(self):"""打印一條指出汽車里程的信息"""print("This car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self,mileage):"""將里程表讀數設置為指定的值禁止將里程表讀數往回調"""if mileage >= self.odometer_reading:self.odometer_reading = mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):"""將里程表讀數增加指定的量"""self.odometer_reading +=miles
# 子類
class ElectricCar(Car):"""電動汽車的獨特之處"""def __init__(self,make,model,year):"""初始化父類的屬性在初始化電動汽車特有的屬性""" # 一定要記得初始化父類的屬性super().__init__(make,model,year)self.battery_size = 70def describe_battery(self):"""打印一條描述電瓶容量的消息"""print("This car has a " + str(self.battery_size) + "-kwh battery.")my_tesla = ElectricCar('tesla','model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()
在上述代碼中,我們在子類中添加了新屬性self.battery_size,并設置其初始值70。根據ElectricCar類創建的所有實例都將會包含這個屬性,但所有的Car實例都不包含它。如果一個屬性或方法是任何汽車都有的,而不是電動汽車特有的,就應該將其加入到Car類而不是ElectricCar類中。如此一來,使用Car類的人將獲得相應的功能,而ElectricCar類只包含處理電動汽車特有的屬性和行為的代碼。
3、重寫父類的方法
對于父類的方法,只要它不符合子類模擬的實物的行為,都可對其進行重寫。為此,可在子類中定義一個這樣的方法,即它與重要的父類方法同名。這樣,Python編譯器將不會考慮這個父類方法,而只關注你在子類中定義的相應的方法。下面我們在Car類中增添油箱屬性和對應的方法,并在子類ElectricCar中重寫Car類的對應的方法,以驗證“Python只關注你在子類中定義的相應的方法”這一觀點。
# 父類
class Car():"""一次模擬汽車的簡單嘗試"""def __init__(self,make,model,year):"""初始化描述汽車的屬性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0self.gas = 30 # 新定義的屬性# 類的方法def get_descriptive_name(self):"""返回整潔的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer_reading(self):"""打印一條指出汽車里程的信息"""print("This car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self,mileage):"""將里程表讀數設置為指定的值禁止將里程表讀數往回調"""if mileage >= self.odometer_reading:self.odometer_reading = mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):"""將里程表讀數增加指定的量"""self.odometer_reading +=miles# 新定義一個加油方法,并在加油結束后顯示油量def fill_gas_tank(self):print("gas' num is : " + str(self.gas))
# 子類
class ElectricCar(Car):"""電動汽車的獨特之處"""def __init__(self,make,model,year):"""初始化父類的屬性在初始化電動汽車特有的屬性""" # 一定要記得初始化父類的屬性super().__init__(make,model,year)self.battery_size = 70def describe_battery(self):"""打印一條描述電瓶容量的消息"""print("This car has a " + str(self.battery_size) + "-kwh battery.")def fill_gas_tank(self):"""電動車沒有油箱"""print("This car doesn't need a gas tank!")my_tesla = ElectricCar('tesla','model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()
my_tesla.fill_gas_tank()
以上代碼是在子類中重寫父類方法的代碼,對應的輸出結果如下所示:
如果不在子類中重寫父類的代碼,那么輸出結果將會是這樣的:
由此看來,驗證的觀點是正確的,以后在修改父類方法時,就按照這個方法。所以在使用繼承時,可以讓子類保留父類那里繼承而來的精華,并提出不需要的糟粕。
4、將實例用作屬性
使用代碼模擬實物時,你可能會發現自己給類添加的細節越來越多:屬性和方法清單以及文件都越來越長。在這種情況下,可能需要將類的一部分作為一個獨立的類提取出來,你可以將大型類拆分成多個協同工作的小類。舉個例子,我們不斷給ElectricCar類添加細節時,可能會發現其中包含很多專門針對汽車電瓶的屬性和方法。在這種情況下,我們可將這些屬性和方法提取出來,放到另一個名為Battery的類中,并將一個Battery實例用作ElectricCar類的一個屬性(這是將實例用作屬性的解釋)例代碼如下:
# 父類
class Car():"""一次模擬汽車的簡單嘗試"""def __init__(self,make,model,year):"""初始化描述汽車的屬性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0self.gas = 30 # 新定義的屬性# 類的方法def get_descriptive_name(self):"""返回整潔的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer_reading(self):"""打印一條指出汽車里程的信息"""print("This car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self,mileage):"""將里程表讀數設置為指定的值禁止將里程表讀數往回調"""if mileage >= self.odometer_reading:self.odometer_reading = mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):"""將里程表讀數增加指定的量"""self.odometer_reading +=miles# 新定義一個加油方法,并在加油結束后顯示油量def fill_gas_tank(self):print("gas' num is : " + str(self.gas))# 電池類
class Battery():"""一次模擬電動車電瓶的簡單嘗試"""def __init__(self,battery_size = 70):"""初始化電瓶的屬性"""self.battery_size = battery_sizedef describe_battery(self):"""打印一條描述電瓶容量的消息"""print("This car has a " + str(self.battery_size) + '-kwh battery.')# 電動車子類
class ElectricCar(Car):"""電動汽車的獨特之處"""def __init__(self,make,model,year):"""初始化父類的屬性在初始化電動汽車特有的屬性""" # 一定要記得初始化父類的屬性super().__init__(make,model,year)self.battery = Battery()def describe_battery(self):"""打印一條描述電瓶容量的消息"""print("This car has a " + str(self.battery_size) + "-kwh battery.")def fill_gas_tank(self):"""電動車沒有油箱"""print("This car doesn't need a gas tank!")my_tesla = ElectricCar('tesla','model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
上面的代碼增加了電池類Battery,它沒有繼承任何類,方法describ_battery()也被移到了這個類中。除此之外,我們還在ElectricCar中增添了電池類的實例化代碼,將電池類的實例化用作屬性,并存儲在self.battery中。由于沒有指定尺寸,所以默認為70。每當方法__init__()被調用時,都將執行該操作;因此現在每個ElectricCar實例都包含一個自動創建的Battery實例。此時,如果要描述電瓶時,則需要使用句點表示法,如“my_tesla.battery.describe_battery()"。這行代碼讓Python在實例my_tesla中查找屬性battery,并對存儲在該屬性中的Battery實例調用方法describe_battery()。結果輸出如下:
這個結果和上面的一樣,并且做了很多工作,有點費力不討好的意思。但是,這樣做是有很大優點的:我們現在想多詳細的描述電瓶都可以,且不會導致ElectricCar類混亂不堪。下面再給Battery類增添一個方法,使汽車根據電瓶容量報告汽車的續航里程:
# 父類
class Car():"""一次模擬汽車的簡單嘗試"""def __init__(self,make,model,year):"""初始化描述汽車的屬性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0self.gas = 30 # 新定義的屬性# 類的方法def get_descriptive_name(self):"""返回整潔的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer_reading(self):"""打印一條指出汽車里程的信息"""print("This car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self,mileage):"""將里程表讀數設置為指定的值禁止將里程表讀數往回調"""if mileage >= self.odometer_reading:self.odometer_reading = mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):"""將里程表讀數增加指定的量"""self.odometer_reading +=miles# 新定義一個加油方法,并在加油結束后顯示油量def fill_gas_tank(self):print("gas' num is : " + str(self.gas))# 電池類
class Battery():"""一次模擬電動車電瓶的簡單嘗試"""def __init__(self,battery_size = 70):"""初始化電瓶的屬性"""self.battery_size = battery_sizedef describe_battery(self):"""打印一條描述電瓶容量的消息"""print("This car has a " + str(self.battery_size) + '-kwh battery.')def get_range(self):"""打印一條信息,指出電瓶的續航里程"""if self.battery_size == 70:range = 240elif self.battery_size == 85:range = 270message = "This car can go approximately " + str(range)message += " miles on a full charge."print(message)# 電動車子類
class ElectricCar(Car):"""電動汽車的獨特之處"""def __init__(self,make,model,year):"""初始化父類的屬性在初始化電動汽車特有的屬性""" # 一定要記得初始化父類的屬性super().__init__(make,model,year)self.battery = Battery()def describe_battery(self):"""打印一條描述電瓶容量的消息"""print("This car has a " + str(self.battery_size) + "-kwh battery.")def fill_gas_tank(self):"""電動車沒有油箱"""print("This car doesn't need a gas tank!")my_tesla = ElectricCar('tesla','model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
新增的get_range()做了一些簡單的分析:如果電瓶的容量為70kwh,它就將續航里程設置為240英里;若是85kwh,那就將續航里程設置為270英里,然后報告這個值。輸出結果如下:
5、模擬實物
? ? ? 如果我們需要模擬較為復雜的物件時,需要解決很多問題。例如,續航里程是電瓶的屬性還是汽車的屬性呢?如果我們只需描述一輛汽車,那么將方法get_range()放在Battery類中也許是合適的;但如果要描述一家汽車制造商的整個產品線,也許將方法get_range()移到ElectricCar類中。在這種情況下,get_range()將根據電瓶容量和汽車型號報告續航里程。這已經涉及到類的設計(軟件工程)了,是從較高的邏輯層考慮問題。前面的筆記中也說過,只要代碼能完成所需要的功能就可以了,慢慢的優化,直至寫出高效、準確的代碼。
6、導入類
隨著你不斷地給類添加功能,文件可能變得很長,即便你妥善地使用了繼承,也是會出現這個問題。為了讓代碼總體看起來更整潔,Python允許我們將類存儲在模塊中,然后在主程序中導入所需的模塊。(代碼模塊化)
6.1 導入單個類
下面我們將創建一個只包含Car類的模塊。單獨創建一個py文件,命名為Car.py,把如下代碼存進去:(開頭第一行是一個模塊級文檔字符串,對該模塊的內容做了簡要的描述。我們應該為自己創建的每個模塊都編寫文檔字符串)
"""一個可用于表示汽車的類"""
class Car():"""一次模擬汽車的簡單嘗試"""def __init__(self,make,model,year):"""初始化描述汽車的屬性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0self.gas = 30 # 新定義的屬性# 類的方法def get_descriptive_name(self):"""返回整潔的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer_reading(self):"""打印一條指出汽車里程的信息"""print("This car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self,mileage):"""將里程表讀數設置為指定的值禁止將里程表讀數往回調"""if mileage >= self.odometer_reading:self.odometer_reading = mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):"""將里程表讀數增加指定的量"""self.odometer_reading += miles# 新定義一個加油方法,并在加油結束后顯示油量
隨后,我們創建一個新的py文件,名為my_car.py,里面寫入如下代碼:
from car import Carmy_new_car = Car('audi','a4','2016')
print(my_new_car.get_descriptive_name())my_new_car.odometer_reading = 23
my_new_car.read_odometer_reading()
導入類是一種有效編程,通過導入類這一方式。主程序變得簡潔易讀,而自身也可以專注于主程序的高級邏輯了。
6.2 在一個模塊中存儲多個類
雖然同一個模塊中的類之間應存在某種相關性,但可根據需要在一個模塊中存儲任意數量的類。例代碼將會展示把Car類、Battery類、ElectricCar類三個類放入car.py文件中,并新建一個名為:my_electric_car.py的文件。里面的代碼依次為:
"""一組用于表示燃油汽車和電動汽車的類"""
class Car():"""一次模擬汽車的簡單嘗試"""def __init__(self,make,model,year):"""初始化描述汽車的屬性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0self.gas = 30 # 新定義的屬性# 類的方法def get_descriptive_name(self):"""返回整潔的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer_reading(self):"""打印一條指出汽車里程的信息"""print("This car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self,mileage):"""將里程表讀數設置為指定的值禁止將里程表讀數往回調"""if mileage >= self.odometer_reading:self.odometer_reading = mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):"""將里程表讀數增加指定的量"""self.odometer_reading +=miles# 新定義一個加油方法,并在加油結束后顯示油量class Battery():"""一次模擬電動車電瓶的簡單嘗試"""def __init__(self,battery_size = 70):"""初始化電瓶的屬性"""self.battery_size = battery_sizedef describe_battery(self):"""打印一條描述電瓶容量的消息"""print("This car has a " + str(self.battery_size) + '-kwh battery.')def get_range(self):"""打印一條信息,指出電瓶的續航里程"""if self.battery_size == 70:range = 240elif self.battery_size == 85:range = 270message = "This car can go approximately " + str(range)message += " miles on a full charge."print(message)class ElectricCar(Car):"""電動汽車的獨特之處"""def __init__(self,make,model,year):"""初始化父類的屬性在初始化電動汽車特有的屬性""" # 一定要記得初始化父類的屬性super().__init__(make,model,year)self.battery = Battery()
from car import ElectricCarmy_tesla = ElectricCar('tesla', 'model s','2016')
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
輸出為:
6.3 從一個模塊中導入多個類
可根據需要在程序文件中導入任意數量的類。如果我們要在同一個程序中創建普通汽車和電動汽車,就需要將Car和ElectricCar類都導入,代碼為:
from car import Car,ElectricCarmy_beetle = Car('volkswagen','beetle',2016)
print(my_beetle.get_descriptive_name())my_tesla = ElectricCar('tesla','roadster',2016)
print(my_tesla.get_descriptive_name())
輸出為:
6.4 導入整個模塊
可以直接在程序開頭導入整個模塊,再使用句點表示法訪問需要的類。這種導入方法很簡單,代碼也易于閱讀。
import carmy_beetle = car.Car('volkswagen','beetle',2016)
print(my_beetle.get_descriptive_name())my_tesla = car.ElectricCar('tesla','roadster',2016)
print(my_tesla.get_descriptive_name())
輸出為:
6.5?導入模塊中的所有類
要導入模塊中的每個類,使用的語法是:
from module_name importr *
這種導入模塊的方法是不推薦的,有兩個原因:
A、首先,如果只要看一下文件開頭的import語句,就能清楚地知道程序使用了哪些類,將大有脾益;但這種導入方式沒有明確地指出你使用了模塊中的哪些類。
B、這種導入方式還可能引發名稱方面的困惑。如果你不小心導入了一個與程序文件其他東西同名的類,將引發難以診斷的錯誤。(不推薦但依舊介紹的原因是:有可能在閱讀代碼的過程中遇到這類的用法)
需要從一個模塊中導入很多類時,最好導入整個模塊,并使用module_name.class_name的語法。這是因為雖然文件開頭并沒有列出所用到的所有類,但你清楚的知道在程序的哪些地方使用了導入的模塊;除此以外,還避免了導入模塊的每個類可能引發的名稱沖突。
6.6?在一個模塊中導入另一個模塊
有時候需要將類分散到多個模塊中,以免模塊太大,或在同一個模塊中存儲不相關的類。將類存儲在多個模塊中時,你可能會發現一個模塊中的類依賴另一個模塊中的類。在這種情況下,可在前一個模塊中導入必要的類。例如,將上述寫好的幾個類進行如下組織:
將Car類存儲在一個模塊中,并將ElectricCar和Battery類存儲在另一個模塊中,我們將后者的模塊命名為“electric_car.py”,并將對應的代碼存儲在這個模塊中,electric_car.py文件中的代碼如下:(ElectricCar類需要訪問其父類Car,所以在代碼開頭,直接將Car了類導入到該模塊中)
from car import Carclass Battery():"""一次模擬電動汽車電瓶的簡單嘗試"""def __init__(self,battery_size=70):"""初始化電瓶的屬性"""self.battery_size = battery_sizedef describe_battery(self):"""打印一條描述電瓶容量的消息"""print("This car has a " + str(self.battery_size) + "-kwh battery.")def get_range(self):"""打印一條信息,指出電瓶的續航里程"""if self.battery_size == 70:range = 240elif self.battery_size == 85:range = 270message = "This cae can go approximately " + str(range)message += " miles on a full charge."print(message)class ElectricCar(Car):"""電動汽車的獨特之處"""def __init__(self,make,model,year):"""電動汽車的獨特之處""""""初始化父親的屬性,再初始化電動車特有的屬性"""super().__init__(make,model,year)self.battery = Battery()
car.py中的代碼如下所示:
"""一組用于表示燃油汽車和電動汽車的類"""
class Car():"""一次模擬汽車的簡單嘗試"""def __init__(self,make,model,year):"""初始化描述汽車的屬性"""self.make = makeself.model = modelself.year = yearself.odometer_reading = 0self.gas = 30 # 新定義的屬性# 類的方法def get_descriptive_name(self):"""返回整潔的描述信息"""long_name = str(self.year) + ' ' + self.make + ' ' + self.modelreturn long_name.title()def read_odometer_reading(self):"""打印一條指出汽車里程的信息"""print("This car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self,mileage):"""將里程表讀數設置為指定的值禁止將里程表讀數往回調"""if mileage >= self.odometer_reading:self.odometer_reading = mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):"""將里程表讀數增加指定的量"""self.odometer_reading +=miles# 新定義一個加油方法,并在加油結束后顯示油量
my_cars.py中的代碼如下所示:(現在可以分別從每個模塊中導入類,以根據需要創建任何類型的汽車了)
from car import Car
from electric_car import ElectricCarmy_beetle = Car('volkswagen','beetle',2016)
print(my_beetle.get_descriptive_name())my_tesla = ElectricCar('tesla','roadster',2016)
print(my_tesla.get_descriptive_name())
輸出為:
7、Python標準庫的些許知識
我們學會了如何導入模塊,那么接下來我們嘗試一下:(導入Python標準庫,記錄被調查者喜歡的編程語言)
from collections import OrderedDict # 導入標準庫里的OrderedDictfavorite_languages = OrderedDict() # 使用OrderedDict()創建一個實例(空的有序字典),并存儲在favorite_languages中favorite_languages['jen'] = 'python' # 將四個人的名字及其喜歡的編程語言寫到favorite_languages字典中
favorite_languages['sarch'] = 'c'
favorite_languages['edward'] = 'ruby'
favorite_languages['phil'] = 'python'
# 利用循環將字典中存儲的信息按照順序輸出
# items()以及title()這兩個函數在之前的筆記中有
for name,language in favorite_languages.items():print(name.title() + "'s favorite language is " + language.title() + ". ")
8、寫在最后
8.1 類編碼風格
A、類名應采用駝峰命名法,即將類名中的每個單詞的首字母大寫,而不使用下劃線。實例名和模塊名都采用小寫格式,并在單詞之間加上下劃線。
B、對于每一個類,都應在類定義后面緊跟著一個文檔字符串。這個文檔字符串應簡要的描述類的功能和用途,并遵循編寫函數的文檔字符串時采用的格式約定。
C、可使用空行來組織代碼,但不要濫用。在類中,可使用一個空行來分隔方法;而在模塊中,可使用兩個空行來分割類。
D、需要同時導入標準庫中的模塊和自編寫庫的模塊時,先導入標準庫的,后倒入自編寫庫的。可以讓人知道這些模塊來源于哪。
8.2 總結
我們在進行大型項目的開發時,一開始要讓代碼結構盡可能簡單:現在一個文件中完成所有的工作,確定一切都能正確運行后,在將類移到獨立的模塊中。先成功,后成“精”。當然,如果是小組合作,完成一個大的項目,那就按照軟件工程那一套來,先設計,后實現。設計類是怎么設計的,就怎么實現。
附錄 一? ?類的相關概念的理解?
因為有的書籍把類實例化后的產物也叫對象?,所以,我在這里提出父對象和子對象的概念,便于大家理解,實際上并無這一術語,望周知。特別注意紅色字。
對象:在我看來,對象是集合。并且對象實際上有兩種,一種是父對象,一種是子對象。
父對象:人類實際生活中的萬事萬物。對父對象進行研究可產生某一類。
子對象:基于對應的類,進行實例化操作,產生了子對象。
接下來的內容主要以游戲開發過程為背景,英雄類為例子,為大家故事式的解釋什么是類?類是怎么產生的?類和對象的關系是什么?類的實例化是什么?
? ? ? ?假設,觀看此博文的各位已經被我以高價簽約,共同開發《王者榮耀pro》5v5大型手機游戲。我們全部從零開始,沒有可利用的代碼,一切代碼全憑自己的雙手敲出。我是老板兼項目經理,召開了一次大會,提出了一個以“古今中外的神話人物、歷史名人等”為主要角色的游戲開發項目,讓公司的營業額再上層樓,并你們施以金錢誘惑。大家因為金錢的鼓舞,熱血沸騰。小明率先提出:“我覺得游戲中應該有這么一類暫且稱它為A類,就拿中國歷史名人來說,如孫尚香、白起等,他們有如下特點:能移動(反映到游戲中就是得受用戶控制);同時他們各自在實際歷史中有自己的本事(反映到游戲中是得有一些技能);最后還得有一些屬性(反應到游戲中是得有一些具體的參數)......”我拍手叫好,并任命小明為類A開發組組長,開發對應的代碼。大家伙一看有官可取,紛紛提出了游戲中還應該存在的類B、C、D、E、F、G......與小明的經歷相同,大家都升官發財了,這個游戲的開發就此拉開了帷幕。(此段中,小明提出A類所依據的中國歷史人物即是上文提到的父對象,也即現實生活中的萬事萬物;“對應到游戲中的操作”即為游戲的設計想法)
? ? ? ?期間,我們抽空又開了一次會,對正在開發的類的名稱、屬性名、方法名等進行了討論。其他的類自行命名,合理即可。小明負責的類被我命名為“英雄類”。英雄類根據類的定義又分為類的屬性和類的方法,具體內容如下圖所示。小明帶領團隊加班加點,終于率先完成了“英雄類”的開發。(把父對象與游戲設計想法進行融合,隨后再提煉、抽象、豐富即可得到英雄類。在軟件工程中,這叫類的設計。如果像小明團隊這樣,這就叫做類的實現。父對象是類的前驅,類是父對象的后驅。)
? ? ? ?因為小明的團隊效率高,我又單獨找到他,委以重任:“光有了英雄類可不行,還得有具體的英雄。反正類已經有了,簡單幾行代碼,對英雄類實例化一下,構造幾個用來內測的英雄吧!到時候游戲上線了,再提拔你一下。”小明答應了,并根據我國的神話傳說及歷史典故,在基于英雄類的基礎上,構造了3-4個英雄:孫尚香、白起、后裔等。(此時,小明團隊根據“英雄類”構造出來的英雄如孫尚香、白起、后裔等均是對象(我看的那本書這樣稱呼),所有的對象的總和即是我為了大家便于理解而稱呼的“子對象”(也有的書稱呼為實例)。由類創建子對象的過被稱為類的實例化)
? ? ? ?最終,在我們團隊的不懈努力下,游戲成功上線,公司盈利額蒸蒸日上。日后,我們只需要使用英雄類的實例化代碼,即可構造出多個不同的英雄,以及皮膚,這簡直是一勞永逸啊!(面向對象的編程思想提高了代碼的復用率,省時省力——面向對象的部分優點)
兩種對象的對比(父對象、子對象):
? ? ? ?項目需求中提到的游戲背景、角色來源、游戲模式等可能就已經對父對象進行了限制,無論怎么限制,父對象均是實際生活中的萬事萬物(父對象中的孫尚香指的就是實際歷史中的孫尚香),但經過游戲設計理念的洗禮,父對象中的實際歷史人物很可能和子對象中的游戲角色在性別、著裝等方面完全不一樣(想必大家也深有體會)。但也有相同之處,比如都可以走、跳、釋放技能等。在規模上,因為游戲金錢成本或時間原因,父對象的數量規模很有可能比子對象的規模大的多。就如歷史上的英雄豪杰千千萬,游戲里的角色總共加起來也沒有1000。之所以稱他們為父對象和子對象是因為:他們之間有像父子之間的聯系:父對象是子對象的來源,子對象相似于父對象,但又不完全是父對象。
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?