2.相等運算符
3.is:同一性運算符
#避免將is運算符用于比較類似數值和字符串這類不可變值,由于Python內部操作這些對象的方式的原因,使用is運算符的結果是不可預測的。
4.in: 成員資格運算符
【代碼】
name = input('what
is your name?')
if 's' in name:
print('Your name contains the letter "s".')
else:
print ('Your name does not contain the letter
"s".')
【結果】
what is your name?nihao
Your name does not contain the letter "s".
5.字符串和序列比較
【代碼】
print("alpha"< "beta")
print(
'FnOrD'.lower())
print(
'FnOrD'.upper())
print([
1,2] < [2,1]) #參與比較的不是字符而是元素的其他類型print([2,[1,4]] <[2,[1,5]]) #如果一個序列中包含其他序列元素,比較規則同樣適用于序列元素
【結果】
True
fnord
FNORD
True
True
6.布爾運算符
【代碼】
#有時想要檢查一個以上的條件,可以這樣做:number = int(input('Enter
a number between 1 and 10:'))
if number <= 10:
if number
>=1:
print('Great!')
else:
print('Wrong!')
else:
print('Wrong')
#下面使用布爾思想進行優化if number <= 10 and number >= 1:
print('Great')
else:
print('Wrong')
#and運算符就是所謂的布爾運算符。它連接兩個布爾值,并且在兩者都為真時返回真,否則返回假。與它同類的還有兩個運算符,or和not。
#
布爾運算符有個有趣的特性:只有在需要求值時才進行求值。例如,表達式x and y需要兩個變量都為真時才為真,如果x為假,表達之會立刻返回false,不管y的值。這類短路邏輯可以用來實現C和Java中所謂的三元運算符。
【結果】
Enter a number between 1 and 10:111
Wrong
Wrong
5.4.7 斷言
【代碼】
#if語句有個非常有用的“近親”,它的工作方式像下面:
#if not condition
#?? crash program
#
就是因為其他程序在晚些時候崩潰,比如在錯誤條件出現時直接讓它崩潰。語句中使用的關鍵字是assertage = 10
assert 0 <
age < 100
age = -1
assert 0 <
age < 100
【結果】
Traceback (most recent call last):
? File"D:/python_file/20180113/test.py", line 8, in
??? assert 0 < age < 100
AssertionError
【代碼】
#如果需要確保程序中的某個條件一定為真才讓程序正常工作的話,assert語句就有用了,它可以在程序中置入檢查點,條件后添加字符串,用于解釋斷言age = -1
assert 0 <
age < 100 ,'The
age must be realistic'
【結果】
Traceback (most recent call last):
? File"D:/python_file/20180113/test.py", line 3, in
??? assert 0 < age < 100,'The age must be realistic'
AssertionError: The age must be realistic
5.5 循環
但是怎樣才能重復執行多次呢?比如,寫一個每月提醒付房租的程序,但是不使用循環,需要這樣寫:
發郵件
等一個月
發郵件
等一個月
(繼續下去……)
但是如果想讓程序繼續執行,直到認為停止它呢?
5.5.1 while循環
【代碼】
x = 1
while x <= 100:
print(x)
??? x+=
1
【結果】
不寫了吧
【代碼】
name = ''
while not name.strip(): #這回,連空格也逃脫不掉了
??? name = input('Please
enter your name:')
print('Hello,%s!'% name)
【結果】
不寫了吧
5.5.2 for循環
【代碼】
#while語句非常靈活,它可以用來在任何條件為真的情況下重復執行一個代碼塊。但是,有時還得量體裁衣。比如要為一個集合(序列或其他可迭代對象)的每個元素都執行一個代碼塊words = ['this','is','an','ex','parrot']
for word in words:
print(word)
#range函數的工作方式類似于分片,它包含下限,但不包含上限。
#range()
函數返回的是一個可迭代對象(類型是對象),而不是列表類型, 所以打印的時候不會打印列表。
# #list()
函數是對象迭代器,把對象轉為一個列表。返回的變量類型為列表。a = range(0,10) #下限為0,上限為9a = list(a)
print(a)
#xrange函數的循環行為類似于range函數,區別在于range函數一次創建整個序列而xrange一次只創建一個數。當需要迭代一個巨大的序列時,xrange會更高效,一般情況下不需要關注它。
【結果】
this
is
an
ex
parrot
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
5.5.3 循環遍歷字典元素
【代碼】
#一個簡單的for語句就能循環字典的所有鍵,就像處理序列一樣d = {'x':1,'y':2,'z':3}
for key in d:
print(key,'correspends to',d[key])
#for循環的一大好處就是可以使用序列解包
#
字典嚴肅的順序通常是沒有意義的。迭代的時候,字典中的鍵和值都能保證被處理,但是處理順序不確定。如果處理順序很重要的話,可以把鍵保存在列表中。
【結果】
y correspends to 2
z correspends to 3
x correspends to 1
5.5.4 一些迭代工具
在Python中迭代序列時,一些函數非常有用。有些函數位于itertools模塊中,還有一些內建函數也十分有用。
[if !supportLists]1.? [endif]并行迭代
【代碼】
#程序可以同時迭代兩個序列。比如:names = ['anne','beth','george','damon']
ages = [
12,45,32,102]
#如果想要打印名字和對應的年齡,可以像下面這樣做:for i in range(len(names)):
print(names[i],'is',ages[i],'years
old')
print('---------------------------------------------')
z =
zip(names,ages) #python3中,使用zip后,得到的是一個可迭代對象print(z)
for i in z:
print(i) #可以看出,打印出來的是一個元組print('---------------------------------------------')
#注:要重新獲得迭代對象,不能再使用上面一個z了,以為上面的z已經迭代到末尾了z = zip(names,ages) #python3中,使用zip后,得到的是一個可迭代對象for i in z: #這里不嫩解包了?
??? print(i[0],'is',i[1],'years old')
【結果】
anne is 12 years old
beth is 45 years old
george is 32 years old
damon is 102 years old
---------------------------------------------
('anne', 12)
('beth', 45)
('george', 32)
('damon', 102)
---------------------------------------------
anne is 12 years old
beth is 45 years old
george is 32 years old
damon is 102 years old
【代碼】
#zip函數也可以用于任意多的序列。關于它很重要的一點就是zip可以應付不等長序列:當最短的序列用完時停止z = zip(range(5),range(100000))
for i in z:
print(i)
#print(i[0],i[1])
【結果】
(0, 0)
(1, 1)
(2, 2)
(3, 3)
(4, 4)
[if !supportLists]2.? [endif]編號迭代
【代碼】
#有時候想要迭代序列中的對象,還想要獲取當前對象的索引。例如,在一個字符串列表中替換所有包含'xxx'的子字符串。strings = ['hello,world!,xxx,yyy,zzz','dkdskldsklkl']
#strings 村莊
#string?
人家
#xxx????
要找的人for string in strings:#大框
??? if 'xxx' instring: #小框
??????? index = strings.index(string) #index相當于門牌號
??????? strings[index] = '[censored]'
print(strings)
【結果】
['[censored]', 'dkdskldsklkl']
【同上,代碼】
#沒問題,但是在替換之前搜索給定的字符串似乎沒必要。如果不替換的話,搜索還會返回錯誤的索引,較好的版本如下index = 0
for string in strings:
if 'xxx' in string:
??????? strings[index] =
'[censored]'
??? index += 1
print(strings)
【結果】
['[censored]', 'dkdskldsklkl']
【同上,代碼】
#方法有點笨,不過可以接受。另一種方法是使用內建的enumerate函數:for index,string in enumerate(strings): #這樣的話,一戶人家和一戶人家的門牌號都有了
??? if 'xxx' instring:
??????? strings[index] =
'[censored]'
print(strings)
【結果】
['[censored]', 'dkdskldsklkl']
[if !supportLists]3.? [endif]翻轉和排序迭代
【代碼】
s = sorted([4,3,6,8,3])
print(s)
s2 =
sorted('Hello,
world!')
print(s2)
s3 =
list(reversed('Hello,
world!'))
print(s3)
#reversed函數和sorted函數作用相反,但是reversed函數還需要使用list函數把得到的對象解包,sorted函數自帶解包功能。
【結果】
[3, 3, 4, 6, 8]
[' ', '!', ',', 'H', 'd', 'e', 'l', 'l', 'l', 'o', 'o', 'r', 'w']
['!', 'd', 'l', 'r', 'o', 'w', ' ', ',', 'o', 'l', 'l', 'e', 'H']
5.5.5 跳出循環
一般來說,循環會一直執行直到條件為假,或者到序列元素用完時。但是有些時候可能會提前中斷一個循環,進行新的迭代。
[if !supportLists]1.? [endif]break
【代碼】
#結束(跳出)循環可以使用break語句。假設需要尋找100以內的最大平方數,那么程序可以從100往下迭代到0,當找到一個平方數時就不需要繼續循環了。from math import sqrt
for n in range(99,0,-1): #range增加了第三個參數:步長
??? root = sqrt(n)
if root
== int(root):
print(n)
break
【結果】
81
[if !supportLists]2.? [endif]continue
for x in seq:
??? if condition1:continue
??? if condition2:continue
??? if condition3:continue
??? do_something()
??? do_something_else()
??? do_another_thing()
??? etc()
很多時候,只要使用if語句就可以了;
for x in seq:
??? if not (condition1 orcondition2 or condition3):
?????? do_something()
?????? do_something_else()
?????? do_another_thing()
?????? etc()
盡管continue語句非常有用,它卻不是最本質的。應該習慣使用break語句,因為在while True語句中會經常用到它。
[if !supportLists]3.? [endif]while True/break習語
【代碼1】
#Python中while和for循環非常靈活,但一旦使用while語句就會遇到一個需要更多功能的問題。word = 'dummy' #代碼有些丑,在進入循環體之前需要給word賦一個啞值。使用啞值就是工作沒有盡善盡美的標志while word:
??? word =
input('please enter a word:')
#處理word
??? print('The word was',word)
【代碼2】
#解決啞值問題while True:
??? word =
input('please enter a word:')
if not word: break
??? #處理word
??? print('The word was',word)
#while True部分實現了一個永遠不會自己停止的循環。但在循環內部的if語句中加入條件是可以的,在條件滿足時調用break語句,這樣就可以在循環體內部的任何地方結束循環,而不僅僅在凱循環的開頭部分。if/break語句自然地將循環分為兩部分:第1部分負責初始化,第2部分則在循環條件為真的情況下使用第1部分內部初始化好的數據。
5.5.6 循環中的else子句
broke_out = False
for x in seq:
??? do_something(x)
??? if condition(x):
?????? broke_out = True
?????? break
??? do_something_else(x)
if not broke_out:
??? print(“I didn’t break out!”)
更簡單的方式是在循環中增加一個else子句—它僅在沒有調用break時執行。
【代碼】
from math import sqrt
for n in range(99,81,-1):
??? root = sqrt(n)
if root
== int(root):
print(n)
break
else: #for循環中的所有語句都執行過的時候(沒有跳出循環),從這個地方開始執行
??? print("Didn't find it!")
#跳出for循環的話,從這個地方開始執行
#
注:for和while循環中都可以使用continue、break語句和else子句
【結果】
Didn't find it!
5.6 列表推導式----輕量級循環
【代碼】
#列表推導式是利用其他列表創建新列表(類似于數學術語中的集合推導式)的一種方法。它的工作方式類似于for循環x = [x*x for x in range(10)]
x2 = [x*x
for x in range(10) if x % 3 == 0]
xy = [(x,y)
for x in range(3) for y in range(3)] #兩個for之間不需要加and
#
作為對比,下面的代碼使用兩個for語句創建了相同的列表:result = []
for x in range(3):
for y in range(3):
??????? result.append((x,y))
print(x)
print(x2)
print(xy)
print(result)
#也可以和if子句聯合使用,像以前一樣:girls = ['alice','bernice','clarice']
boys = [
'chris','arnold','bob']
print([b + '+' + g for g in girls for b in boys if b[0] == g[0]])
#注:使用普通的圓括號而不是方括號不會得到“元組推導式”。,在最近的版本中,則會得到一個生成器。
【結果】
2
[0, 9, 36, 81]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
['arnold+alice', 'bob+bernice', 'chris+clarice']
使用字典進行效率更高的優化,只使用一層for循環:
【代碼】
girls = ['alice','bernice','clarice']
boys = [
'chris','arnold','bob']
letteGirls = {}
for girl in girls:
??? letteGirls.setdefault(girl[
0],[]).append(girl)
a = [b +
'+' + g for b in boys for g in letteGirls[b[0]]] #列表推導式不能使用元組"()",而應使用列表"[]"print(a)
【結果】
['chris+clarice', 'arnold+alice', 'bob+bernice']
5.7 三人行
5.7.1 什么都沒發生
有時,程序什么事情都不做,這種情況,應讓pass出馬
【代碼】
#Python中空代碼是非法的,解決的方法就是在語句塊中加上一個pass語句:name = 'Bill Gates'
if name == 'Ralph Auldus Melish':
print('Welcome')
elif name == 'Enid':
#還沒完。。。
??? pass
elifname == 'Bill Gates':
print('Access Denied')
#注釋pass語句聯合的替代方案時插入字符串
【結果】
Access Denied
5.7.2 使用del刪除
【代碼】
#一般來說,Python會刪除那些不再使用的對象scoundrel = {'age':42,'first
name':'Robin','last
name':'of Locksley'}
robin = scoundrel
print(scoundrel)
print(robin)
scoundrel =
None
print(robin)
#首先,robin和scoundrel都被綁定到同一個字典上。所以當設置scoundrel為None的時候,字典通過robin還是可用的。但是當把robin也設置為None的時候,字典就“漂”在內存里面了,沒有任何名字綁定到它上面。所以python解釋器直接刪除了那個字典(這種行為被稱為垃圾收集)。也可以使用None之外的其他值,字典同樣會“消失不見”
【結果】
{'age': 42, 'last name': 'of Locksley', 'first name': 'Robin'}
{'age': 42, 'last name': 'of Locksley', 'first name': 'Robin'}
{'age': 42, 'last name': 'of Locksley', 'first name': 'Robin'}
【代碼】
#使用del語句,不僅會移動一個對象的引用,也會移除那個名字本身
# x = 1
# del x
# print(x)
x = ['Hello','world']
y = x
y[
1] = 'Pythoin'
print(x)
del x
print(y)
#原因是刪除的只是名稱,而不是列表本身。事實上,在Python中是沒有辦法刪除值的(也不需要過多考慮刪除值的問題,因為在某個值不再使用的時候,Python解釋器會負責內存的回收)
【結果】
['Hello', 'Pythoin']
['Hello', 'Pythoin']
5.7.3 使用exec和eval執行和求值字符串
有時可能會需要動態創造Python代碼,然后將其作為語句執行或作為表達式計算,---在此之前,一定要謹慎
[if !supportLists]1.? [endif]exec
【代碼】
#在Python3中,exec是一個函數而不是語句exec('print("hello,world")') #exec函數的形參是一個字符串,所以兩邊加引號from math import sqrt
exec("sqrt
= 1")
print(sqrt(4))
#為什么一開始要這樣做?exec函數最有用的地方在于可以動態創建代碼字符串。為了安全起見,可以增加一個字典,起到命名空間的作用。命名空間又稱為作用域,是個非常重要的只知識。
【結果】
hello,world
Traceback (most recent call last):
? File"D:/python_file/20180113/test.py", line 6, in
??? print(sqrt(4))
TypeError: 'int' object is not callable
【示例】
#可以通過增加in 來實現,其中的就是起到放置代碼字符串命名空間作用的字典。from math import sqrt
scope = {}
exec('sqrt =
1',scope) #python3中使用這種形式限定作用域print(sqrt)
print(len(scope))
print(scope['sqrt']) #可以看出,sqrt=1 中,sqrt是鍵,1是值print(scope.keys()) #因為內建的__builtins__字典自動包含所有的內建函數和值
【結果】
2
1
dict_keys(['sqrt', '__builtins__'])
[if !supportLists]2.? [endif]eval
【示例】
#eval(用于“求值”)是類似于exec的內建函數。exec語句會執行一些列Python語句,而eval會計算Python表達式(以字符串形式書寫),并且返回結果值,(exec語句并不返回任何對象,因為它本身就是語句)。e = eval(input("Enter
an arithmetic expression:"))
print(e)
#注:Python2中,表達式eval(raw_input(...))事實上等同于input(...).在Python3.0中,raw_input被重命名為input
#
可以給eval語句提供兩個命名空間,一個全局的一個局部的。全局的必須是字典,局部的可以是任何形式的映射。
#
目前Python內沒有任何執行不可信任代碼的安全方式,一個可選的方案是使用Pythono的實現,比如Jython,以及使用一些本地機制,比如Java的sandbox
【結果】
Enter an arithmetic expression:6 + 18 * 2
42
【示例】
#初探作用域
#
給exec或者eval語句提供命名空間時,還可以在真正使用命名空間前放置一些值進去scope = {}
scope[
'x'] = 2
scope['y'] = 3
print(eval('x*y',scope))
#同理,exec或者eval調用的作用域也能在另外一個上面使用:scope = {}
exec('x = 2',scope)
print(eval('x*x',scope))
【結果】
6
4
第六章 抽 象
這里,將會學習語句組織成函數,這樣,可以告訴計算機如何做事。有了函數之后,不必反反復復項計算機傳遞同樣的具體指令了。本章還會詳細介紹參數和作用域的概念。以及遞歸概念及其在程序中的用途。
6.1 懶惰即美德
【代碼】
fibs = [0,1]
for i in range(8):
??? fibs.append(fibs[-
2] +fibs[-1])
print(fibs)
#如果想要一次計算前10個數的話,沒有問題。甚至可以將用戶輸入的數字作為動態范圍的長度使用,從而改變for語句循環的次數fibs = [0,1]
num =
int(input('How
many Fibonacci number do you want?')) #使用for i in range(num-2):
print('i=',i,)
??? fibs.append(fibs[-
2] +fibs[-1]) #主要在于循環幾次(總共需要5個,原始序列中有兩個)print(fibs)
【結果】
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
How many Fibonacci number do you want?5
i= 0
i= 1
i= 2
[0, 1, 1, 2, 3]
#真正的程序員會讓自己的程序抽象一些,上面的程序可以改寫為較抽象的版本:
#num = int(input('How many numbers do you want?'))
#print(fibs(num))
如果函數要用很多次的話,這么做會節省很多精力
6.2 抽象和結構
抽象可以節省很多工作,實際上它的作用還要更大,它是計算機程序可以讓人讀懂的關鍵(這也是最基本的要求)。計算機非常樂于處理精確和具體的指令,但是人可就不同了。
計算機會:向前走10米,左轉90度,再走5步,右轉45度,走123步
人只需要知道:一直沿著街道走,過橋,電影院就在左手邊,這樣就明白了吧!關鍵大家都知道怎樣走路和過橋,不需要指令來知道這些事。
組織計算機程序也是類似的。程序應該是非常抽象的。事實上,我們把這段描述翻譯為Python程序。
page = download_page()
freqs = compute_frequencies(page)
for word,freq in freqs:
??? print word,freq
6.3 創建函數
函數可以調用(可能包含參數),它執行某種行為并且返回一個值。一般來說,內建的callable函數可以用來判斷函數是否可調用:
【示例】
import math
x =
1
y = math.sqrt
print(callable(x))
print(callable(y))
【結果】
False
True
【示例】
#那么,怎樣定義函數呢def hello(name):
return 'Hello,'+name+'!'
print(hello('world'))
print(hello('Gumby'))
【結果】
Hello,world!
Hello,Gumby!
【示例】
def fibs(num):
??? result = [
0,1]
for i in range(num-2):
??????? result.append(result[-
2]+result[-1])
return result
print(fibs(10))
print(fibs(15))
#注:result語句是用來從函數中返回值的
【結果】
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
6.3.1 記錄函數
如果想要給函數寫文檔,讓后面使用該函數人能理解的haul,可以加入注釋(以#開頭)。另外一個方式就是直接寫上字符串。這類字符串在其他地方可能會非常有用。如果在函數的開頭寫下字符串,它就會作為函數的一部分進行存儲,這稱為文檔字符串。
【示例】
def square(x):
'Calculates the square of the number x.'
??? return x*x
print(square(2))
【結果】
4
【示例】
def square(x):
'Calculates the square of the number x.'
??? return x*x
print(square(2))
print(square.__doc__) #這樣對文檔字符串進行訪問,__doc__是函數屬性。第7章會介紹更多關于屬性的知識,雙下劃線表示它是個特殊屬性。這類特殊和"魔法"屬性在第9章中介紹
#
內建的help函數是非常有用的。在交互式解釋器中使用它,可以得到關于函數,包括它的文檔字符串的信息:print(help(square)) #第10章對help函數進行討論
【結果】
4
Calculates the square of the number x.
Help on function square in module __main__:
square(x)
??? Calculates the square of thenumber x.
None
6.3.3 并非真正函數的函數
數學意義上的函數,總在計算其參數后返回點什么。Python的有些函數并不返回任何東西。但是Python的函數就是函數,即便它從學術上講并不是函數。沒有return語句,或者return后沒有跟任何值的函數不返回值
【示例】
def test():
print('This is printed')
return
??? print('This is not')
x =test()
#可以看到,第2個return語句被跳過了(類似于循環中的break語句)
#
但是如果test不返回任何值,那么x又引用什么呢?print(x)
#好熟悉的None,所以所有的函數的確都返回了東西:當不需要它們返回值的時候,它們就返回None。看來,“有些函數并不是真正的函數”的說法有些不公平了。
#
不要被默認的行為所迷惑,當調用者期待一個序列的時候,就不會意外地返回None。
【結果】
This is printed
None
6.4 參數魔法
函數用起來很簡單,創建起來也不復雜。但函數參數的用法就有時有些不可思議了。
6.4.1 值從哪里來
寫在def語句中函數名后面的變量通常叫做函數的形式參數,而調用函數的時候提供的值是實際參數(值)。
6.4.2 能改變參數么
【示例】
#函數通過它的參數獲得一系列值。那參數只是變量而已,所以它們的行為和預想的一樣。在函數內為參數賦予新值不會改變外部任何變量的值:def try_to_change(n):
n = 'Mr. Gumby'
name = 'Mrs. Entity'
try_to_change(name)
print(name) #可以看出,該函數沒有改變變量name的值
#
在try_to_change函數的內部,參數n獲得了新值,但是它沒有影響到name變量。n實際上是個完全不同的變量,具體的工作方式類似于下面:name = "Mrs. Entity"
n = name #這等于傳遞參數n = 'Mr. Gumby'
print(name)
#結果是顯而易見的,當變量n改變的時候,變量name不變。同樣,當在函數內部把參數重綁定(賦值)的時候,函數外部的變量不會受到影響。
#
注:參數存儲在局部作用域內
【結果】
Mrs. Entity
Mrs. Entity
【示例】
#字符串(以及數字和元組)是不可變的,即無法修改(也就是說只能用新的值覆蓋)。但是:如果將可變的數據結構如列表用作參數的時候會發生什么:def change(n): #關鍵看實參是什么,實參是字符串,則不能改變,實參是列表,則能改變
??? n[0] = 'Mr. Gumby'
names = ['Mrs. Entity','Mrs.
Thing']
change(names)
print(names)
#本例中,參數被改變了。這就是本例和前面例子中至關重要的區別。前面的例子中,局部變量被賦予了新值,但是這個例子中變量names所綁定的列表的確改變了。有些奇怪了吧?
#
下面進行模仿names = ['Mrs. Entity','Mrs.
Thing']
n = names
#再來一次,模擬傳參行為n[0] = 'Mr. Gumby' #改變列表names
【結果】
['Mr. Gumby', 'Mrs. Thing']
【示例】
#這種情況在前面已經出現很多次了。當兩個變量同時引用一個列表的時候,它們的確是同時引用一個列表。如果想避免出現這種情況,可以復制一個列表的副本。在序列中做切片的時候,返回的切片總是一個副本。names =['Mrs. Entity','Mrs.
Thing']
n = names[:]
#使用切片進行復制(這個相當于字典中的深復制)
#
如果現在改變n,則不會影響到namesn[0] = 'Mr. Gumby'
print(n)
print(names)
【結果】
['Mr. Gumby', 'Mrs. Thing']
['Mrs. Entity', 'Mrs. Thing']
【示例】
#這種情況在前面已經出現很多次了。當兩個變量同時引用一個列表的時候,它們的確是同時引用一個列表。如果想避免出現這種情況,可以復制一個列表的副本。在序列中做切片的時候,返回的切片總是一個副本。names =['Mrs. Entity','Mrs.
Thing']
n = names[:]
#使用切片進行復制(這個相當于字典中的深復制)
#
如果現在改變n,則不會影響到namesn[0] = 'Mr. Gumby'
print(n) #n變了print(names) #names沒變
#
再用change試一下def change(n): #關鍵看實參是什么,實參是字符串,則不能改變,實參是列表,則能改變
??? n[0] = 'Mr. Gumby'
change(names[:]) #使用這種方式,傳遞進去的是一個串,如果形參是names,則傳遞進去的是一個列表print(names)
【結果】
['Mr. Gumby', 'Mrs. Thing']
['Mrs. Entity', 'Mrs. Thing']
['Mrs. Entity', 'Mrs. Thing']
[if !supportLists]1.? [endif]為什么我們要修改參數
使用函數改變數據結構(比如列表或字典)是將程序抽象化的好方法。假設需要編寫一個存儲名字并且能用名字、中間名或姓查找聯系人的程序,可以使用下面的數據結構:
【示例】
storage= {}
storage[
'first'] = {}
storage[
'middle'] = {}
storage[
'last'] = {}
#storage這個數據結構的存儲方式是帶有3個鍵“first”、“middle”和“last”的字典。每個鍵的下面都又存儲一個字典。子字典中,可以使用名字(名字、中間名或姓)作為鍵,插入聯系人列表作為值。me = 'Magnus Lie Hetland'
storage['first']['Magnus'] = [me] #Magnus是key,me是value(說明value是一個列表,可以使用append方法進行追加元素)storage['middle']['Lie'] =[me]
storage[
'last']['Hetland'] = [me]
#每個鍵下面都存儲了一個以人名組成的列表。本例表中,人名只有“我”。如果想得到所有注冊的中間名為Lie的人,可以像下面這樣做:print(storage['middle']['Lie'])
#將人名加入到列表中的步驟有些枯燥乏味,尤其是要加入很多姓名相同的人時,因為要擴展已經存儲了哪些名字的列表。例如,下面加入我姐姐的名字,而且假設不知道數據庫中已經存儲了什么:my_sister = 'Anne Lie Hetland'
storage['first'].setdefault('Anne',[]).append(my_sister)#Anne是key,my_sister是valuestorage['middle'].setdefault('Lie',[]).append(my_sister)
storage[
'last'].setdefault('Hetland',[]).append(my_sister)
print(storage['middle']['Lie'])
【結果】
['Magnus Lie Hetland']
['Magnus Lie Hetland', 'Anne Lie Hetland']
【代碼】
#如果要寫個大程序來這樣更新列表,那么很顯然程序很快就會變得臃腫且笨拙不堪了。抽象的要點就是隱藏更新時的繁瑣的細節,這個過程可以使用函數來實現。def init(data): #data本身是個字典
??? data['first'] = {}
??? data[
'middle'] = {}
??? data[
'last'] = {}
#使用的方法如下:storage = {}
init(storage)
#print(storage)
#可以看到,函數包辦了初始化工作,讓代碼更易讀
#
在編寫存儲名字的函數前,先寫個獲得名字的函數def lookup(data,label,name):
return data[label].get(name)
#標簽(比如“middle")以及名字(比如“Lie”)可以作為參數提供給lookup函數使用,這樣會獲得包含全名的列表
#
注:返回的列表和存儲在數據結構中的列表是相同的,所以如果列表修改了,那么也會影響數據結構def store(data,full_name):
??? names = full_name.split()
#得到的是一個列表['','','',...]
??? if len(names)
== 2:
??????? names.insert(
1,'') #在names列表的下標為1的位置處插入
??? labels = 'first','middle','last'#是一個元組
??? for label,name inzip(labels,names):
??????? people = lookup(data,label,name)
if people:
??????????? people.append(full_name)
else:
??????????? data[label][name] =[full_name]
#如果原先沒有值,則賦值
#
(1)通過參數data和full_name進入函數,這兩個參數被設置為函數在外部獲得的一些值。
#
(2)通過拆分full_name,得到一個叫做names的列表
#
(3)如果names的長度為2(只有首名和末名),那么插入一個空字符串作為中間名
#
(4)將字符串“first”、“middle”和“last”作為元組存儲在labels中(也可以使用列表,這里只是為了方便而去掉括號)
#
(5)使用zip函數聯合標簽和名字,對于每一個(label,name)對,進行以下處理:
??? #
①獲得屬于給定標簽和名字的列表
??? #
②將full_name添加到列表中,或者插入一個需要更新的新列表MyNames = {}
init(MyNames)
store(MyNames,
'Magnus Lie
Hetland')
print(lookup(MyNames,'middle','Lie'))
#好像可以工作,再試試store(MyNames,'Robin Hood')
store(MyNames,
'Robin Locksley')
print(lookup(MyNames,'first','Robin'))
store(MyNames,
'Mr. Gumby')
print(lookup(MyNames,'middle',''))
#可看到,如果某些人的名字、中間名或姓相同,那么結果中會包含所有這些人的信息。
#
這類程序很適合進行面向對象程序設計
【結果】
['Magnus Lie Hetland']
['Robin Hood', 'Robin Locksley']
['Robin Hood', 'Robin Locksley', 'Mr. Gumby']
[if !supportLists]2.? [endif]如果我的參數不可變呢
在某些語言(如C++,Pascal和Ada)中,重綁定參數并且使這些改變到函數外的變量是很平常的事情。但在Python中,這是不可能的:函數只能修改參數對象本身。但是如果你的參數不可變—比如是數字—又該怎么辦呢?
不好意思,沒有辦法。這個時候應該從函數中返回所有需要的值(如果值多余一個的話就以元組的形式返回)。
??1???N??