第9章 類
面向?qū)ο缶幊淌亲钣行У能浖帉?xiě)方法之一。在面向?qū)ο缶幊讨?,你編?xiě)表示現(xiàn)實(shí)世界中的事物和情景的類,并基于這些類來(lái)創(chuàng)建對(duì)象。編寫(xiě)類時(shí),你定義一大類對(duì)象都有的通用行為?;陬悇?chuàng)建對(duì)象時(shí),每個(gè)對(duì)象都自動(dòng)具備這種通用行為,然后可根據(jù)需要賦予每個(gè)對(duì)象獨(dú)特的個(gè)性。
根據(jù)類來(lái)創(chuàng)建對(duì)象被稱為實(shí)例化。
9.1 創(chuàng)建和使用類
使用類幾乎可以模擬任何東西。
9.1.1 創(chuàng)建Dog類

根據(jù)約定,在python中,首字母大寫(xiě)的名稱指的是類。在這個(gè)類定義中的括號(hào)是空的,因?yàn)槲覀円獜目瞻讋?chuàng)建這個(gè)類。
1.方法_ init_()
類中的函數(shù)稱為方法。
方法_ init_()是一個(gè)特殊的方法,每當(dāng)你根據(jù)Dog類創(chuàng)建新實(shí)例時(shí),python都會(huì)自動(dòng)運(yùn)行它。在這個(gè)方法中,開(kāi)頭和末尾各有兩個(gè)下劃線,這是一種約定,旨在避免python默認(rèn)方法與普通方法發(fā)生名稱沖突。
將方法_ init_()定義成包含三個(gè)形參:self、name和age,在這個(gè)方法的定義中,self必不可少,還必須位于其他形參前面。因?yàn)閜ython調(diào)用這個(gè)_ init_()方法來(lái)創(chuàng)建Dog實(shí)例時(shí),將自動(dòng)傳入實(shí)參self。每個(gè)與類相關(guān)聯(lián)的方法調(diào)用都自動(dòng)傳遞實(shí)參self,它是一個(gè)指向?qū)嵗旧淼囊?,讓?shí)例能夠訪問(wèn)類中的屬性和方法。我們創(chuàng)建Dog實(shí)例時(shí),python將調(diào)用Dog類的方法_ init_()。我們將通過(guò)實(shí)參向Dog()傳遞名字和年齡,self會(huì)自動(dòng)傳遞,因此我們不需要傳遞它。每當(dāng)我們根據(jù)Dog類創(chuàng)建實(shí)例時(shí),都只需給最后兩個(gè)形參提供值。
以self為前綴的變量都可供類中的所有方法使用,我們還可以通過(guò)類的任何實(shí)例來(lái)訪問(wèn)這些變量。self.name = name獲取存儲(chǔ)在形參name中的值,并將其存儲(chǔ)到變量name中,然后該變量被關(guān)聯(lián)到當(dāng)前創(chuàng)建的實(shí)例。像這樣可通過(guò)實(shí)例訪問(wèn)的變量稱為屬性。
2.在python2.7中創(chuàng)建類
class ClassName(object):
9.1.2 根據(jù)類創(chuàng)建實(shí)例
可將類視為有關(guān)如何創(chuàng)建實(shí)例的說(shuō)明。Dog類是一系列說(shuō)明,讓python知道如何創(chuàng)建表示特定小狗的實(shí)例。
# -*- coding:gbk -*-
class Dog():
"""一次模擬小狗的簡(jiǎn)單嘗試"""
def __init__(self,name,age):
"""初始化屬性name和age"""
self.name = name
self.age = age
def sit(self):
"""模擬小狗被命令時(shí)蹲下"""
print(self.name.title() + " is now siitting.")
def roll_over(self):
"""模擬小狗被命令時(shí)打滾"""
print(self.name.title() + " rolled over!")
my_dog = Dog('willie',6)
print("My dog's name is " + my_dog.name.title() + ".")
print("My dog is " + str(my_dog.age) + " years old.")
python使用實(shí)參‘willie’和6調(diào)用Dog類的方法_ init_(),方法_ init_()創(chuàng)建一個(gè)表示特定小狗的實(shí)例,并使用我們提供的值來(lái)設(shè)置屬性name和age.方法_ init_()并未顯式地包含return語(yǔ)句,但python自動(dòng)返回一個(gè)表示這條小狗的實(shí)例。將實(shí)例存儲(chǔ)在變量my_dog中。
命名約定:我們通??梢哉J(rèn)為首字母大寫(xiě)的名稱(如Dog)指的是類,而小寫(xiě)的名稱(如my_dog)指的是根據(jù)類創(chuàng)建的實(shí)例。
1.訪問(wèn)屬性
要訪問(wèn)實(shí)例的屬性,可使用句點(diǎn)表示法。
訪問(wèn)my_dog的屬性name的值.python先找到實(shí)例my_dog,再查找與這個(gè)實(shí)例相關(guān)聯(lián)的屬性name。在Dog類中引用這個(gè)屬性時(shí),使用的是self.name
my_dog.name
2.調(diào)用方法
根據(jù)Dog類創(chuàng)建實(shí)例后,就可以使用句點(diǎn)表示法來(lái)調(diào)用Dog類中定義的任何方法。
class Dog():
--snip--
my_dog = Dog('willie',6)
my_dog.sit()
my_dog.roll_over()
要調(diào)用方法,可指定實(shí)例的名稱和要調(diào)用的方法,并用句點(diǎn)分隔它們。
3.創(chuàng)建多個(gè)實(shí)例
class Dog():
--snip--
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()
print("\nYour dog's name is " + your_dog.name.title() + ".")
print("Your dog is " + str(your_dog.age) + " years old.")
your_dog.sit()
課后習(xí)題9-1
# -*- coding:gbk -*-
class Restaurant():
"""餐廳"""
def __init__(self,restaurant_name,cuisine_type):
"""初始化屬性"""
self.restaurant_name = restaurant_name
self.cuisine_type = cuisine_type
def describe_restaurant(self):
print("The name of the restaurant is " + self.restaurant_name + ".")
print("The cuisine_type is " + self.cuisine_type + ".")
def open_restaurant(self):
print("The restaurant is oppen.")
restaurant = Restaurant('kfc','fast food')
print(restaurant.restaurant_name)
print(restaurant.cuisine_type)
restaurant.describe_restaurant()
restaurant.open_restaurant()
課后習(xí)題9-3
# -*- coding:gbk -*-
class User():
"""用戶"""
def __init__(self,first_name,last_name,location):
"""初始化屬性"""
self.first_name = first_name
self.last_name = last_name
self.location = location
def describe_user(self):
print("First_name: " + self.first_name)
print("Last_name: " + self.last_name)
print("Location: " + self.location)
def greet_user(self):
full_name = self.first_name + " " + self.last_name
print("Hello, " + full_name.title() + "." )
user = User('zhao','sheng','chongqing')
user.describe_user()
user.greet_user()
9.2 使用類和實(shí)例
你可以使用來(lái)模擬現(xiàn)實(shí)世界中的很多情景。類編寫(xiě)好后,你的大部分時(shí)間都將花在使用根據(jù)類創(chuàng)建的實(shí)例上。你需要執(zhí)行的一個(gè)重要任務(wù)是修改實(shí)例的屬性。你可以直接修改實(shí)例的屬性,也可以編寫(xiě)方法以特定的方式進(jìn)行修改。
9.2.1 Car類
# -*- coding:gbk -*-
class Car():
"""一次模擬汽車的簡(jiǎn)單嘗試"""
def __init__(self,make,model,year):
"""初始化描述汽車的屬性"""
self.make = make
self.model = model
self.year = year
def get_descriptive_name(self):
"""返回整潔的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
my_new_car = Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())
9.2.2 給屬性指定默認(rèn)值
類中的每個(gè)屬性都必須由初始值,哪怕這個(gè)值是0或空字符串。在有些情況下,如設(shè)置默認(rèn)值時(shí),在方法_ init_()內(nèi)指定這種初始值是可行的;如果你對(duì)某個(gè)屬性這樣做了,就無(wú)需包含為它提供初始值的形參。
# -*- coding:gbk -*-
class Car():
"""一次模擬汽車的簡(jiǎn)單嘗試"""
def __init__(self,make,model,year):
"""初始化描述汽車的屬性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""返回整潔的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
def rea_odometer(self):
"""打印一條指出汽車?yán)锍痰南?""
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()
9.2.3 修改屬性的值
可以以三種不同的方式修改屬性的值:直接通過(guò)實(shí)例進(jìn)行修改;通過(guò)方法進(jìn)行設(shè)置;通過(guò)方法進(jìn)行遞增(增加特定的值)。
1.直接修改屬性的值
class Car():
--snip--
my_new_car = Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
更新屬性的方法
class Car():
--snip--
def update_odometer(self,mileage):
"""將里程表讀數(shù)設(shè)置為指定的值"""
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()
禁止把里程表讀數(shù)回調(diào)
class Car():
--snip--
def update_odometer(self,mileage):
"""
將里程表讀數(shù)設(shè)置為指定的值
禁止將里程表讀數(shù)往回調(diào)
"""
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
3.通過(guò)方法對(duì)屬性的值進(jìn)行遞增
class Car():
--snip--
def update_odometer(self,mileage):
--snip--
def increment_odometer(self,miles):
"""將里程表讀數(shù)增加指定的量"""
self.odometer_reading += miles
my_used_car = Car('subaru','outback',2013)
print(my_used_car.get_descriptive_name())
my_used_car.update_odometer(23500)
my_used_car.read_odometer()
my_used_car.increment_odometer(100)
my_used_car.read_odometer()
9.3 繼承
編寫(xiě)類時(shí),并非總是要從空白開(kāi)始。如果你要編寫(xiě)的類是另一個(gè)現(xiàn)成類的特殊版本,可使用繼承。一個(gè)類繼承另一個(gè)類時(shí),它將自動(dòng)獲得另一個(gè)類的所有屬性和方法;原有的類稱為父類,而新類稱為子類。子類繼承了其父類的所有屬性和方法,同時(shí)還可以定義自己的屬性和方法。
9.3.1 子類的方法_ init_()
創(chuàng)建子類的實(shí)例時(shí),python首先需要的任務(wù)是給父類的所有屬性賦值。為此,子類的方法_ init_()需要父類施以援手。
- END - 2019/3/31
# -*- coding:gbk -*-
class Car():
"""一次模擬汽車的簡(jiǎn)單嘗試"""
def __init__(self,make,model,year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
def read_odometer(self):
print("This car has " + str(self.odometer_reading) + " miles on it.")
def update_odometer(self,mileage):
if mileage >= odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self,miles):
self.odometer_reading += miles
class ElectricCar(Car):
"""電動(dòng)汽車的獨(dú)特之處"""
def __init__(self,make,model,year):
"""初始化父類的屬性"""
super().__init__(make,model,year)
my_tesla = ElectricCar('tesla','model s',2016)
print(my_tesla.get_descriptive_name())
創(chuàng)建子類時(shí),父類必須包含在當(dāng)前文件中,且位于子類前面。
定義子類時(shí),必須在括號(hào)內(nèi)指定父類的名稱。
super()的一個(gè)特殊函數(shù),幫助python將父類和子類關(guān)聯(lián)起來(lái),讓python調(diào)用ElectricCar的父類的方法_ init_(),讓ElectricCar實(shí)例包含父類的所有屬性。父類也成為超類(superclass),名稱super因此而得名。
9.3.2 python2.7中的繼承
class Car(object):
def __init__(self,make,model,year):
--snip--
class ElectricCar(Car):
def __init__(self,make,model,year):
super(ElectricCar,self).__init__(make,model,year)
--snip--
函數(shù)super()需要兩個(gè)實(shí)參:子類名和對(duì)象self。
9.3.3 給子類定義屬性和方法
讓一類繼承另一個(gè)類后,可添加區(qū)分子類和父類所需的新屬性和方法。
# -*- coding:gbk -*-
class Car():
--sinp--
class ElectricCar(Car):
"""電動(dòng)汽車的獨(dú)特之處"""
def __init__(self,make,model,year):
"""
電動(dòng)汽車的獨(dú)特之處
初始化父類的屬性,再初始化電動(dòng)汽車特有的屬性
"""
super().__init__(make,model,year)
self.battery_size = 70
def 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()
9.3.4 重寫(xiě)父類的方法
對(duì)于父類的方法,只要它不符合子類模擬的實(shí)物的行為,都可對(duì)其進(jìn)行重寫(xiě)。為此,可在子類中定義一個(gè)這樣的方法,即它與要重寫(xiě)的父類方法同名。這樣python將不會(huì)考慮這個(gè)父類方法,而只關(guān)注你在子類中定義的相應(yīng)方法。
class ElectricCar(Car):
--snip--
def fill_gas_tank(self):
"""電動(dòng)汽車沒(méi)有油箱"""
print("This car doesn't need a gas tank!")
使用繼承時(shí),可讓子類保留從父類那里繼承而來(lái)的精華,并剔除不需要的糟粕。
9.3.5 將實(shí)例用作屬性
使用代碼模擬實(shí)物時(shí),你可能會(huì)發(fā)現(xiàn)自己給類添加的細(xì)節(jié)越來(lái)越多:屬性和方法清單以及文件都越來(lái)越長(zhǎng)。在這種情況下,可能需要將類的一部分作為一個(gè)獨(dú)立的類提取出來(lái)。你可以將大型類拆分成多個(gè)協(xié)同工作的小類。

②處的方法_ init_()除self外,還有另一個(gè)形參battery_size,這個(gè)形參時(shí)可選的:如果沒(méi)有給它提供值,電瓶容量將被設(shè)置為70.
④處代碼讓python創(chuàng)建一個(gè)新的Battery實(shí)例(由于沒(méi)有指定尺寸,因此為默認(rèn)值70),并將該實(shí)例存儲(chǔ)在屬性self.battery中。
my_tesla.battery.describe_battery()
這行代碼讓python在實(shí)例my_tesla中查找屬性battery,并對(duì)存儲(chǔ)在該屬性中的Battery實(shí)例調(diào)用方法describe_battery()。
# -*- coding:gbk -*-
class Car():
"""一次模擬汽車的簡(jiǎn)單嘗試"""
def __init__(self,make,model,year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
def read_odometer(self):
print("This car has " + str(self.odometer_reading) + " miles on it.")
def update_odometer(self,mileage):
if mileage >= odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self,miles):
self.odometer_reading += miles
class Battery():
"""一次模擬電動(dòng)汽車電瓶的簡(jiǎn)單嘗試"""
def __init__(self,battery_size=70):
"""初始化電瓶的屬性"""
self.battery_size = battery_size
def describe_battery(self):
"""打印一條描述電瓶容量的消息"""
print("This car has a " + str(self.battery_size) + "-kwh battery.")
def get_range(self):
"""打印一條消息,指出電瓶的續(xù)航里程"""
if self.battery_size ==70:
range = 240
elif self.battery_size ==85:
range = 270
message = "This car can go approximately " + str(range)
message += " miles on a full charge."
print(message)
class ElectricCar(Car):
"""電動(dòng)汽車的獨(dú)特之處"""
def __init__(self,make,model,year):
"""
初始化父類的屬性,再初始化電動(dòng)車汽車特有的屬性
"""
super().__init__(make,model,year)
self.battery = Battery()
my_tesla = ElectricCar('tesla','model s',2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
9.3.6 模擬實(shí)物
邏輯層面 語(yǔ)法層面
9.4 導(dǎo)入類
將類存儲(chǔ)在模塊中,然后在主程序中導(dǎo)入所需的模塊。
9.4.1 導(dǎo)入單個(gè)類
# -*- coding:gbk -*-
"""一個(gè)可用于表示汽車的類"""
class Car():
"""一次模擬汽車的簡(jiǎn)單嘗試"""
def __init__(self,make,model,year):
"""初始化描述汽車的屬性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""返回整潔的描述性名稱"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
def read_odometer(self):
"""打印一條消息,指出汽車的里程"""
print("This car has " + str(self.odometer_reading) + " miles on it.")
def update_odometer(self,mileage):
"""
將里程表讀數(shù)設(shè)置為指定的值
拒絕將里程表往回?fù)? """
if mileage >= odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self,miles):
"""將里程表讀數(shù)增加指定的量"""
self.odometer_reading += miles
包含一個(gè)模塊級(jí)文檔字符串,對(duì)該模塊的內(nèi)容做了簡(jiǎn)要的秒速。應(yīng)該為自己創(chuàng)建的每個(gè)模塊都編寫(xiě)文檔字符串。
from car9 import Car
my_new_car = Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
import語(yǔ)句讓python打開(kāi)模塊car9,并導(dǎo)入其中的Car類。
專注于主程序的高級(jí)邏輯
9.4.2 在一個(gè)模塊中存儲(chǔ)多個(gè)類
雖然同一個(gè)模塊中的類之間應(yīng)存在某種關(guān)聯(lián)性,但可根據(jù)需要在一個(gè)模塊中存儲(chǔ)任意數(shù)量的類。

9.4.3 從一個(gè)模塊中導(dǎo)入多個(gè)類
可根據(jù)需要在程序文件中導(dǎo)入任意數(shù)量的類。

從一個(gè)模塊中導(dǎo)入多個(gè)類時(shí),用逗號(hào)分隔了各個(gè)類。
9.4.4 導(dǎo)入整個(gè)模塊

使用語(yǔ)法module_name.class_name訪問(wèn)需要的類
9.4.5 導(dǎo)入模塊中的所有類
要導(dǎo)入模塊中的每個(gè)類,可使用語(yǔ)法:
from module_name import *
不推薦使用這種導(dǎo)入方式,原因:如果只要看一下文件開(kāi)頭的import語(yǔ)句,就能清楚的知道程序使用了哪些類,將大有裨益;但這種導(dǎo)入方式?jīng)]有明確地指出你使用了模塊中的哪些類。這種導(dǎo)入方式還可能引發(fā)名稱方面的困惑。如果不小心導(dǎo)入了一個(gè)與程序文件中其他東西同名的類,將引發(fā)難易診斷的錯(cuò)誤。
需要從一個(gè)模塊中導(dǎo)入很多類時(shí),最好導(dǎo)入整個(gè)模塊,用module_name.class_name語(yǔ)法來(lái)訪問(wèn)。
9.4.6 在一個(gè)模塊中導(dǎo)入另一個(gè)模塊



9.4.7 自定義工作流程
9.5 python標(biāo)準(zhǔn)庫(kù)
python標(biāo)準(zhǔn)庫(kù)是一組模塊,安裝的python都包含它。
字典讓你能夠?qū)⑿畔㈥P(guān)聯(lián)起來(lái),但它們不記錄你添加鍵-值對(duì)的順序。要?jiǎng)?chuàng)建字典并記錄其中的鍵-值對(duì)添加順序,可使用模塊collections中的OrderedDict類。
from collections import OrderedDict
favorite_languages = OrderedDict()
favorite_languages['jen'] = 'python'
favorite_languages['sarah'] = 'c'
favorite_languages['edward'] = 'ruby'
favorite_languages['phil'] = 'python'
for name,language in favorite_languages.items():
print(name.title() + "'s favorite language is " +
language.title() + ".")
9.6 類編碼風(fēng)格
- 類名應(yīng)采用駝峰命名法,即將類名中的每個(gè)單詞的首字母都大寫(xiě),而不適用下劃線。實(shí)例名和模塊名都采用小寫(xiě)格式,并在單詞之間加上下劃線。
- 對(duì)于每個(gè)類,都應(yīng)緊跟在類定義后面包含一個(gè)文檔字符串。這種文檔字符串簡(jiǎn)要地描述類的功能,并遵循編寫(xiě)函數(shù)的文檔字符串時(shí)采用的格式約定。每個(gè)模塊也都應(yīng)包含一個(gè)文檔字符串,對(duì)其中的類可用于做什么進(jìn)行描述。
- 可使用空行來(lái)組織代碼,但不要濫用。在類中,可使用一個(gè)空行來(lái)分隔方法;在模塊中,可使用兩個(gè)空行來(lái)分隔類。
- 需要同時(shí)導(dǎo)入標(biāo)準(zhǔn)庫(kù)中的模塊和你編寫(xiě)的模塊時(shí),先編寫(xiě)導(dǎo)入標(biāo)準(zhǔn)庫(kù)模塊的import語(yǔ)句,再添加一個(gè)空行,然后編寫(xiě)導(dǎo)入你自己編寫(xiě)的模塊的import語(yǔ)句。在包含多條import語(yǔ)句的程序中,這種做法讓人更容易明白程序使用的各個(gè)模塊都來(lái)自何方。
第10章 文件和異常
10.1 從文件中讀取數(shù)據(jù)
要讀取文本文件中的信息,首先需要將信息讀取到內(nèi)存中??梢淮涡宰x取文件的全部?jī)?nèi)容,也可以每次一行的方式逐步讀取。
10.1.1 讀取整個(gè)文件
pi_digits.txt
3.1415926525
8979323846
2643383279
file_reader.py
with open('pi_digits.txt') as file_object:
contents = file_object.read()
print(contents)
函數(shù)open()接受一個(gè)參數(shù):要打開(kāi)的文件的名稱,并返回一個(gè)表示文件的對(duì)象。(python在當(dāng)前執(zhí)行的文件所在的目錄中查找指定的文件。)python將這個(gè)對(duì)象存儲(chǔ)在后面使用的變量中。
關(guān)鍵字with在不再需要訪問(wèn)文件后將其關(guān)閉。
方法read()讀取這個(gè)文件的全部?jī)?nèi)容。
結(jié)果多出空行,因?yàn)閞ead()到達(dá)文件末尾時(shí)返回一個(gè)空字符串,而將這個(gè)空字符串顯示出來(lái)時(shí)就說(shuō)一個(gè)空行。要?jiǎng)h除末尾的空行,可在print語(yǔ)句中使用rstrip()。
print(contents.rstrip())
10.1.2 文件路徑
將文件名傳遞給函數(shù)open()時(shí),python將在當(dāng)前執(zhí)行的文件(即.py程序文件)所在的目錄中查找文件。
要讓python打開(kāi)不與程序文件位于同一個(gè)目錄中的文件,需要提供文件路徑,它讓python到系統(tǒng)的特定位置去查找。
由于文件夾txt_files位于文件夾python_work中,因此可使用相對(duì)文件路徑來(lái)打開(kāi)這個(gè)文件夾中的文件。
在windows系統(tǒng)中,在文件路徑中使用反斜杠(\)而不是斜杠(/)。
with open('txt_files\filename.txt') as file_object:
絕對(duì)文件路徑通常比相對(duì)文件路徑更長(zhǎng),因此將其存儲(chǔ)在一個(gè)變量中,再將該變量傳遞給open()會(huì)有所幫助。
file_path = 'C:\Users\ehmatthes\other_files\txt_files\filename.txt'
with open(file_path) as file_object:
10.1.3 逐行讀取
要以每次一行的方式檢查文件,可對(duì)文件對(duì)象使用for循環(huán)。
filename = 'pi_digits.txt'
with open(filename) as file_object:
for line in file_object:
print(line.rstrip())
將要讀取的文件的名稱存儲(chǔ)在filename中,變量filename表示的并非實(shí)際文件——只是一個(gè)讓python知道到哪里去查找文件的字符串。
10.1.4 創(chuàng)建一個(gè)包含文件各行內(nèi)容的列表
使用關(guān)鍵字with時(shí),open()返回的文件對(duì)象只在with代碼塊內(nèi)可用。如果要在with代碼塊外訪問(wèn)文件的內(nèi)容,可在with代碼塊內(nèi)將文件的各行存儲(chǔ)在一個(gè)列表中,并在with代碼塊外使用該列表。
filename = 'pi_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
for line in lines:
print(line.rstrip())
方法readlines()
10.1.5 使用文件的內(nèi)容
將文件讀取到內(nèi)存中后,就可以以任何方式使用這些數(shù)據(jù)了。
filename = 'pi_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.rstrip()
print(pi_string)
print(len(pi_string))

pi_string += line.strip()

注意:讀取文本文件時(shí),python將其中的所有文本都解讀為字符串。如果你讀取的是數(shù)字,并要將其作為數(shù)值使用,就必須使用函數(shù)int()將其轉(zhuǎn)換為整數(shù),或使用函數(shù)float()將其轉(zhuǎn)換為浮點(diǎn)數(shù)。
10.1.6 包含一百萬(wàn)位的大型文件

10.1.7 圓周率值中包含你的生日嗎
將生日表示為一個(gè)由數(shù)字組成的字符串,再檢查這個(gè)字符串是否包含在pi_string中。
filename = 'pi_million_digits'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
birthday = input("Enter your birthday,in the form mmddyy:")
if birthday in pi_string:
print("Your birthday appears in the first million digits of pi!")
else:
print("Your birthday does not appear in the first million digits of pi!")
10.2 寫(xiě)入文件
保存數(shù)據(jù)的最簡(jiǎn)單的方式之一是將其寫(xiě)入到文件中。通過(guò)將輸出寫(xiě)入文件,即便關(guān)閉包含程序輸出的終端窗口,這些輸出依然存在:你可以在程序結(jié)束運(yùn)行后查看這些輸出,可與別人分享輸出文件,還可編寫(xiě)程序來(lái)將這些輸出讀取到內(nèi)存中并進(jìn)行處理。
10.2.1 寫(xiě)入空文件
要將文本寫(xiě)入文件,在調(diào)用open()時(shí)需要提供另一個(gè)實(shí)參,告訴python你要寫(xiě)入打開(kāi)的文件。
filename = 'programming.txt'
with open(filename,'w') as file_object:
file_object.write("I love programming.")
調(diào)用open()時(shí)提供了兩個(gè)實(shí)參:1)要打開(kāi)的文件的名稱;2)實(shí)參‘w’,告訴python,我們要以寫(xiě)入模式打開(kāi)這個(gè)文件。打開(kāi)文件時(shí),可指定讀取模式(‘r’)、寫(xiě)入模式('w')、附加模式('a')或讓你能夠讀取和寫(xiě)入文件的模式('r+')。如果省略了模式實(shí)參,python將以默認(rèn)的只讀模式打開(kāi)文件。
如果要寫(xiě)入的文件不存在,函數(shù)open()將自動(dòng)創(chuàng)建它。以寫(xiě)入(‘w’)模式打開(kāi)文件時(shí)要小心,因?yàn)?em>如果指定的文件已經(jīng)存在,python將在返回文件對(duì)象前清空該文件。
這個(gè)程序沒(méi)有終端輸出,但如果打開(kāi)文件programming.txt將看到內(nèi)容。
注意:python只能講字符串寫(xiě)入文本文件,要將數(shù)值數(shù)據(jù)存儲(chǔ)到文本文件中,必須先使用函數(shù)str()將其轉(zhuǎn)換為字符串格式。
10.2.2 寫(xiě)入多行
函數(shù)write()不會(huì)在寫(xiě)入的文本末尾添加換行符,需要自己添加換行符。
filename = 'programming.txt'
with open(filename,'w') as file_object:
file_object.write("I love programming.\n")
file_object.write("I love creating new games.\n")
10.2.3 附加到文件
如果要給文件添加內(nèi)容,而不是覆蓋原有的內(nèi)容,可以附加模式打開(kāi)文件。以附加模式打開(kāi)文件時(shí),python不會(huì)在返回文件對(duì)象前清空文件,而你寫(xiě)入到文件的行都將添加到文件末尾。如果指定的文件不存在,python將為你創(chuàng)建一個(gè)空文件。
filename = 'programming.txt'
with open(filename,'a') as file_object:
file_object.write("I also love finding meaning in large datasets.\n")
file_object.write("I love creating apps that can run in browser.\n")
10.3 異常
python使用被稱為異常的特殊對(duì)象來(lái)管理程序執(zhí)行期間發(fā)生的錯(cuò)誤。每當(dāng)發(fā)生讓python不知所措的錯(cuò)誤時(shí),它都會(huì)創(chuàng)建一個(gè)異常對(duì)象。如果你編寫(xiě)了處理該異常的代碼,程序?qū)⒗^續(xù)運(yùn)行;如果你未對(duì)異常進(jìn)行處理,程序?qū)⑼V?,并顯示一個(gè)traceback,其中包含有關(guān)異常的報(bào)告。
異常是使用try-except代碼塊處理的。try-except代碼塊讓python執(zhí)行指定的操作,同時(shí)告訴python發(fā)生異常時(shí)怎么辦。使用try-except代碼塊時(shí),即便出現(xiàn)異常,程序也將繼續(xù)運(yùn)行:顯示你編寫(xiě)的友好的錯(cuò)誤消息,而不是令用戶迷糊的traceback.
10.3.1 處理ZeroDivisionError異常

10.3.2 使用try-except代碼塊
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
10.3.3 使用異常避免崩潰
print("Give me two numbers,and I'll divide them.")
print("Enter 'q' to quit.")
while True:
first_number = input("\nFirst number: ")
if first_number == 'q':
break
second_number = input("Second number: ")
if second_number == 'q':
break
answer = int(first_number) / int(second_number)
print(answer)
這個(gè)程序沒(méi)有采取任何處理錯(cuò)誤的措施,因此讓它執(zhí)行除數(shù)為0時(shí)的除法運(yùn)算,它將崩潰。

如果用戶懷有惡意,他將通過(guò)traceback獲悉你不希望他知道的信息。例如,他將知道你的程序的名稱,還將看到部分不能正確運(yùn)行的代碼。有時(shí)候,訓(xùn)練有素的攻擊者可根據(jù)這些信息判斷出可對(duì)你的代碼發(fā)起什么樣的攻擊。
10.3.4 else代碼塊
通過(guò)將可能引發(fā)錯(cuò)誤的代碼放在try-except代碼塊中,可提高贈(zèng)程序抵御錯(cuò)誤的能力。依賴于try代碼塊成功執(zhí)行的代碼都應(yīng)放在else代碼塊中。
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")
while True:
first_number = input("\nFirst number: ")
if first_number == 'q':
break
second_number = input("Second number: ")
try:
answer = int(first_number) / int(second_number)
except ZeroDivisionError:
print("You can't divide by 0!")
else:
print(answer)
try-except-else代碼塊的工作原理:python嘗試執(zhí)行try代碼塊中的代碼;只有可能引發(fā)異常的代碼才需要放在try語(yǔ)句中。有時(shí)候,有一些僅在try代碼塊成功執(zhí)行時(shí)才需要運(yùn)行的代碼應(yīng)放在else代碼塊中。except代碼塊告訴python,如果它嘗試運(yùn)行try代碼塊中的代碼時(shí)引發(fā)了指定的異常,該怎么辦。
通過(guò)預(yù)測(cè)可能發(fā)生錯(cuò)誤的代碼,可編寫(xiě)健壯的程序,他們即便面臨無(wú)效數(shù)據(jù)或缺少資源,也能繼續(xù)運(yùn)行,從而能夠抵御無(wú)意的用戶錯(cuò)誤和惡意的攻擊。
10.3.5 處理FileNotFoundError異常
使用文件時(shí),一種常見(jiàn)的問(wèn)題是找不到文件:你要查找的文件可能在其他地方、文件名可能不正確或者這個(gè)文件根本就不存在。
filename = 'alice.txt'
with open(filename) as f_ojt:
contents = f_ojt.read()

filename = 'alice.txt'
try:
with open(filename) as f_ojt:
contens = f_ojt.read()
except FileNotFoundError:
msg = "Sorry, the file " + filename + " does not exist."
print(msg)
10.3.6 分析文本
方法split(),它根據(jù)一個(gè)字符串創(chuàng)建一個(gè)單詞列表。
方法split()以空格為分隔符將字符串分拆成多個(gè)部分,并將這些部分都存儲(chǔ)到一個(gè)列表中,結(jié)果是一個(gè)包含字符串中所有單詞的列表。

# -*- coding:gbk -*-
filename = 'programming.txt'
try:
with open(filename) as f_ojt:
contents = f_ojt.read()
except FileNotFoundError:
msg = "Sorry, the file " + filename + " does not exist."
print(msg)
else:
# 計(jì)算文件大致包含多少個(gè)單詞
words = contents.split()
num_words = len(words)
print("The file " + filename + " has about " + str(num_words) +
"words.")
10.3.7 使用多個(gè)文件
# -*- coding:gbk -*-
def count_words(filename):
"""計(jì)算一個(gè)文件大致包含多少個(gè)單詞"""
try:
with open(filename) as f_ojt:
contents = f_ojt.read()
except FileNotFoundError:
msg = "Sorry, the file " + filename + " does not exist."
print(msg)
else:
# 計(jì)算文件大致包含多少個(gè)單詞
words = contents.split()
num_words = len(words)
print("The file " + filename + " has about " + str(num_words) +
"words.")
filename = 'programming.txt'
count_words(filename)
多個(gè)
def count_words(filename):
--snip--
filenames = ['alice.txt','siddhartha.txt','moby_dick.txt','little_women.txt']
for filename in filenames:
count_words(filename)
10.3.8 失敗時(shí)一聲不吭
python中有一個(gè)pass語(yǔ)句,可在代碼塊中使用它來(lái)讓python什么都不要做。
# -*- coding:gbk -*-
def count_words(filename):
"""計(jì)算一個(gè)文件大致包含多少個(gè)單詞"""
try:
--snip--
except FileNotFoundError:
pass
else:
--snip--
filenames = ['alice.txt','siddhartha.txt','moby_dick.txt','little_women.txt']
for filename in filenames:
count_words(filename)
10.3.9 決定報(bào)告哪些錯(cuò)誤
python的錯(cuò)誤處理結(jié)構(gòu)讓你能過(guò)細(xì)致地控制與用戶分享錯(cuò)誤信息的程度,要分享多少信息由你決定。
編寫(xiě)得很好且經(jīng)過(guò)詳盡測(cè)試的代碼不容易出現(xiàn)內(nèi)部錯(cuò)誤,如語(yǔ)法或邏輯錯(cuò)誤,但只要程序依賴于外部因素,如用戶輸入、存在指定的文件、有網(wǎng)絡(luò)鏈接,就由可能出現(xiàn)異常。憑借經(jīng)驗(yàn)判斷該在程序的什么地方包含異常處理塊,以及出現(xiàn)錯(cuò)誤時(shí)該向用戶提供多少相關(guān)的信息。
10.4 存儲(chǔ)數(shù)據(jù)
使用模塊json來(lái)存儲(chǔ)數(shù)據(jù)。
模塊json讓你能夠?qū)⒑?jiǎn)單的python數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)儲(chǔ)到文件中,并在程序再次運(yùn)行時(shí)加載該文件中的數(shù)據(jù)。你還可以使用json在python程序之間分享數(shù)據(jù)。更重要的是,JSON數(shù)據(jù)格式并非python專業(yè)的,這讓你能夠?qū)⒁訨SON格式存儲(chǔ)的數(shù)據(jù)與使用其他編程語(yǔ)言的人分享。
注意:JSON(JavaScriptObjectNotation)格式最初是為JavaScript開(kāi)發(fā)的,但隨后成了一種常見(jiàn)格式,被包括python在內(nèi)的眾多語(yǔ)言采用。
10.4.1 使用json.dump()和json.load()
編寫(xiě)一個(gè)存儲(chǔ)數(shù)字的簡(jiǎn)短程序,函數(shù)json.dump()
編寫(xiě)一個(gè)將這些數(shù)字讀取到內(nèi)存中的程序,函數(shù)json.load()
函數(shù)json.dump()接受兩個(gè)實(shí)參:要存儲(chǔ)的數(shù)據(jù)以及可用于存儲(chǔ)數(shù)據(jù)的文件對(duì)象。
import json
numbers = [2,3,5,7,11,13]
filename = 'number.json'
with open(filename,'w') as f_ojt:
json.dump(numbers,f_ojt)
導(dǎo)入模塊json
通常使用文件拓展名.json來(lái)指出文件存儲(chǔ)的數(shù)據(jù)為JSON格式。
這個(gè)程序沒(méi)有輸出,但可打開(kāi)文件numbers.json,看內(nèi)容。
import json
filename = 'numbers.json'
with open(filename) as f_ojt:
numbers = json.load(f_ojt)
print(numbers)
使用函數(shù)json.load()加載存儲(chǔ)在numbers.json中的信息,并將其存儲(chǔ)在變量numbers中。
這是一種在程序之間共享數(shù)據(jù)的簡(jiǎn)單方式。
10.4.2 保存和讀取用戶生成的數(shù)據(jù)
對(duì)于用戶生成的數(shù)據(jù),使用json保存它們大有裨益,因?yàn)槿绻灰阅撤N方式進(jìn)行存儲(chǔ),等程序停止運(yùn)行時(shí)用戶的信息將丟失。
remember_me.py
import json
username = input("What is your name?")
filename = 'username.json'
with open(filename,'w') as f_ojt:
json.dump(username,f_ojt)
print("We'll remember you when you come back " + username + "!")
greet_user.py
import json
filename = 'username.json'
with open(filename) as f_ojt:
username = json.load(f_ojt)
print("Welcome back, " + username + "!")
合并程序
# -*- coding:gbk -*-
import json
# 如果以前存儲(chǔ)了用戶名,就加載它
# 否則,就提示用戶輸入用戶名并存儲(chǔ)它
filename = 'username.json'
try:
with open(filename) as f_ojt:
username = json.load(f_ojt)
except FileNotFoundError:
username = input("What is your name?")
with open(filename,'w') as f_ojt:
json.dump(username,f_ojt)
print("We'll remember you when you come back, " + username + "!")
else:
print("Welcome back, " + username + "!")
10.4.3 重構(gòu)
代碼能夠正確運(yùn)行,但可做進(jìn)一步的改進(jìn)——將代碼劃分為一系列完成具體工作的函數(shù)。這樣的過(guò)程被稱為重構(gòu)。
重構(gòu)讓代碼更清晰、更易于理解、更容易擴(kuò)展。
# -*- coding:gbk -*-
import json
def greet_user():
"""問(wèn)候用戶,并指出其名字"""
filename = 'username.json'
try:
with open(filename) as f_ojt:
username = json.load(f_ojt)
except FileNotFoundError:
username = input("What is your name?")
with open(filename,'w') as f_ojt:
json.dump(username,f_ojt)
print("We'll remember you when you come back, " + username + "!")
else:
print("Welcome back, " + username + "!")
greet_user()
考慮到現(xiàn)在使用了函數(shù),我們刪除了注釋,轉(zhuǎn)而使用一個(gè)文檔字符串來(lái)指出程序是做什么的。
# -*- coding:gbk -*-
import json
def get_stored_username():
"""如果存儲(chǔ)了用戶名,就獲取它"""
filename = 'username.json'
try:
with open(filename) as f_obj:
username = json.load(f_obj)
except FileNotFoundError:
return None
else:
return username
def greet_user():
"""問(wèn)候用戶,并指出其名字"""
username = get_stored_username()
if username:
print("Welcome back " + username + "!")
else:
username = input("What is your name?")
filename = 'username.json'
with open(filename,'w') as f_obj:
json.dump(username,f_obj)
print("We'll remember you when you come back, " +
username + "!")
greet_user()

第11章 測(cè)試代碼
11.1 測(cè)試函數(shù)
name_function.py
# -*- coding:gbk -*-
def get_formatted_name(first,last):
"""生成整潔的姓名"""
full_name = first + ' ' + last
return full_name.title()
names.py
from name_function import get_formatted_name
print("Enter 'q' ant any time to quit.")
while True:
first = input("\nPlease give me a first name: ")
if first == 'q':
break
last = input("Please give me a last name: ")
if last == 'q':
break
formatted_name = get_formatted_name(first,last)
print("\tNeatly formatted name: " + formatted_name + ".")
11.1.1 單元測(cè)試和測(cè)試用例
python標(biāo)準(zhǔn)庫(kù)中的模塊unittest提供了代碼測(cè)試工具。單元測(cè)試用于核實(shí)函數(shù)的某個(gè)方面沒(méi)有問(wèn)題;測(cè)試用例是一組單元測(cè)試,這些單元測(cè)試一起核實(shí)函數(shù)在各種情形下的行為都符合要求。良好的測(cè)試用例考慮到了函數(shù)可能受到的各種輸入,包含針對(duì)所有這些情形的測(cè)試。全覆蓋式測(cè)試用例包含一整套單元測(cè)試,涵蓋了各種可能的函數(shù)使用方式。對(duì)于大型項(xiàng)目,要實(shí)現(xiàn)全覆蓋可能很難。通常,最初只要針對(duì)代碼的重要行為編寫(xiě)測(cè)試即可,等項(xiàng)目被廣泛使用時(shí)再考慮全覆蓋。
11.1.2 可通過(guò)的測(cè)試
要為函數(shù)編寫(xiě)測(cè)試用例,可先導(dǎo)入模塊unittest以及要測(cè)試的函數(shù),再創(chuàng)建一個(gè)繼承unittest.TestCase的類,并編寫(xiě)一系列方法對(duì)函數(shù)行為的不同方面進(jìn)行測(cè)試。
# -*- coding:gbk -*-
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
"""測(cè)試name_function.py"""
def test_firt_last_name(self):
"""能夠正確地處理像Janis Joplin這樣的姓名嗎?"""
formatted_name = get_formatted_name('janis','joplin')
self.assertEqual(formatted_name,'Janis Joplin')
unittest.main()
創(chuàng)建一個(gè)名為NamesTestCase的類,用于包含一系列針對(duì)get_formatted_name()的單元測(cè)試??呻S便給這個(gè)類命名,但最好讓它看起來(lái)與要測(cè)試的函數(shù)有關(guān),并包含字樣Test。這個(gè)類必須繼承unittest.TestCase類,這樣python才知道如何運(yùn)行你編寫(xiě)的測(cè)試。
斷言方法用來(lái)核實(shí)得到的結(jié)果是否與期望的結(jié)果一致。
方法self.assertEqual()

11.1.3 不能通過(guò)的測(cè)試


11.1.4 測(cè)試未通過(guò)時(shí)怎么辦
如果你檢查的條件沒(méi)錯(cuò),測(cè)試通過(guò)了意味著函數(shù)的行為是對(duì)的,而測(cè)試未通過(guò)意味著你編寫(xiě)的新代碼有錯(cuò)。因此,測(cè)試未通過(guò)時(shí),不要修改測(cè)試,而應(yīng)修復(fù)導(dǎo)致測(cè)試不能通過(guò)的代碼:檢查剛對(duì)函數(shù)所做的修改,找出導(dǎo)致函數(shù)行為不符合預(yù)期的修改。

11.1.5 添加新測(cè)試
# -*- coding:gbk -*-
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
"""測(cè)試name_function.py"""
def test_first_last_name(self):
"""能夠正確地處理像Janis Joplin這樣的姓名嗎?"""
formatted_name = get_formatted_name('janis','joplin')
self.assertEqual(formatted_name,'Janis Joplin')
def test_first_last_middle_name(self):
"""能夠正確地處理像Wolfgang Amadeus Mozart這樣的姓名嗎?"""
formatted_name = get_formatted_name(
'wolfgang','mozart','amadeus')
self.assertEqual(formatted_name,'Wolfgang Amadeus Mozart')
unittest.main()

11.2 測(cè)試類
11.2.1 各種斷言方法
python在unittest.TestCase類中提供了很多斷言方法。斷言方法檢查你認(rèn)為應(yīng)該滿足的條件是否確實(shí)滿足。
只能在繼承unittest.TestCase的類中使用這些方法。

11.2.2 一個(gè)要測(cè)試的類
類的測(cè)試和函數(shù)的測(cè)試相似——你所做的大部分工作都是測(cè)試類中方法的行為。
- END - 2019/3/31 22:14