python學習第五天
一、遞歸調用
遞歸調用是函數嵌套調用的一種特殊形式,函數在調用時,直接或間接調用了自身,就是遞歸調用;遞歸調用分為遞歸和回溯兩個階段
#示例: #場景:已知A的工資比B的工資多200;B的工資比C的工資多200;C的工資比D的工資多200;D的工資比E的工資多200;E的工資為8000;求A的工資 def salary(n): if n==1: return 8000 else: return salary(n-1)+200 salary_A=salary(5) print(salary_A)
#註意事項:
python中的遞歸效率低,需要在進入下一次遞歸時保留當前的狀態,在其他語言中可以有解決方法:尾遞歸優化,即在函數的最後一步(而非最後一行)調用自己,尾遞歸優化;
但是python又沒有尾遞歸,且對遞歸層級做了限制
#總結遞歸的使用:
1. 必須有一個明確的結束條件
2. 每次進入更深一層遞歸時,問題規模相比上次遞歸都應有所減少
3. 遞歸效率不高,遞歸層次過多會導致棧溢出(在計算機中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出)
#修改遞歸的最大深度
import sys sys.getrecursionlimit() sys.setrecursionlimit(2000) n=1 def test(): global n print(n) n+=1 test() test() #雖然可以設置,但是因為不是尾遞歸,仍然要保存棧,內存大小一定,不可能無限遞歸
二、二分法
#場景: #要求查看某一列表中是否有有某個值,該列表中數值由小到大的順序排列(即實現類似與in的功能) num_list=[1,2,3,10,30,45,66,80,200,300,500,770] def get_num(num,num_list): mid=len(num_list)//2 if num_list: if num_list[mid] > num: num_list=num_list[:mid]elif num_list[mid] < num: num_list=num_list[mid+1:] else: print(‘find it‘) return get_num(num,num_list) else: print(‘not find‘) return get_num(210,num_list)
三、匿名函數
沒有名字的函數;只能使用一次
lambda x,y,z:x+y+z #作用同下列有名函數: def func(x,y,z): return x+y+z
#有名函數與匿名函數的對比
有名函數:循環使用,保存了名字,通過名字就可以重復引用函數功能
匿名函數:一次性使用,隨時隨時定義
應用:max,min,sorted,map,reduce,filter
#員工工資信息如下列字典,求最大工資# ,最小工資,和排序 salaries={ ‘egon‘:3000, ‘alex‘:100000000, ‘wupeiqi‘:10000, ‘yuanhao‘:2000 } # 用匿名函數 print(max(salaries,key=lambda k:salaries[k])) print(min(salaries,key=lambda k:salaries[k])) print(sorted(salaries,key=lambda k:salaries[k])) # 用zip拉鏈 salaries_and_name=zip(salaries.values(),salaries.keys())#叠代器,只能訪問一次 print(max(salaries_and_name)) salaries_and_name=zip(salaries.values(),salaries.keys()) print(min(salaries_and_name)) salaries_and_name=zip(salaries.values(),salaries.keys()) print(sorted(salaries_and_name))
四、內置函數
#優先掌握 #max、#min、#sorted--->示例參見上文匿名函數中的應用示例內容 #map #map()是 Python 內置的高階函數,它接收一個函數 f 和一個 list,並通過把函數 f 依次作用在 list 的每個元素上,得到一個新的 list 並返回。 #例如,對於list [1, 2, 3, 4, 5, 6, 7, 8, 9] ;如果希望把list的每個元素都作平方,就可以用map()函數;map返回的是一個生成器,可以用list轉為列表 num_list=[i for i in range(1,10)] new=map(lambda i:i*i,num_list) print(list(new),type(new)) # reduce # reduce()函數也是Python內置的一個高階函數。 # reduce()函數接收的參數和 map()類似,一個函數 f,一個list,但行為和 map()不同,reduce()傳入的函數 f 必須接收兩個參數, # reduce()對list的每個元素反復調用函數f,並返回最終結果值。 # 例如,編寫一個f函數,接收x和y,返回x和y的和: # 求和,依次相加如[0,1,2,3,4,5,6,7,8,9] # 0+1=1 # 1+2=3 # 3+3=6 # 6+4=10 # ...... from _functools import reduce num_list=[i for i in range(1,10)] num=reduce(lambda x,y:x+y,num_list) num2=reduce(lambda x,y:x+y,num_list,100)#第三個參數作為計算的初始值 print(num,num2) # filter #filter()函數是 Python 內置的另一個有用的高階函數,filter()函數接收一個函數 f 和一個list, # 這個函數 f 的作用是對每個元素進行判斷,返回 True或 False,filter()根據判斷結果自動過濾掉不符合條件的元素, # 返回由符合條件元素組成的新list。 #要從一個list [1, 4, 6, 7, 9, 12, 17]中刪除偶數,保留奇數,首先,要編寫一個判斷奇數的函數: num_list=[1,4,6,7,9,12,17] new=filter(lambda i:i%2,num_list) print(list(new),type(new)) # sum print(sum([i for i in range(1,11)])) # bool print(bool([]),bool(‘‘),bool(None),bool(0)) # chr #將ascii碼轉換為對應的字符 print(chr(65)) #ord #將字符轉為對應的ascii碼 print(ord(‘A‘)) # divmod #取商和余數 print(divmod(7,2)) # enumerate #enumerate() 函數用於將一個可遍歷的數據對象(如列表、元組或字符串)組合為一個索引序列, # 同時列出數據和數據下標,一般用在 for 循環當中。 seasons = [‘Spring‘, ‘Summer‘, ‘Fall‘, ‘Winter‘] print(list(enumerate(seasons)),type(enumerate(seasons))) print(list(enumerate(seasons,start=2)))#start指定起始以序號值 # id #內置函數id()可以返回一個對象的身份,返回值為整數。這個整數通常對應與該對象在內存中的位置, #但這與python的具體實現有關,不應該作為對身份的定義,即不夠精準,最精準的還是以內存地址為準。 #is運算符用於比較兩個對象的身份,等號比較兩個對象的值,內置函數type()則返回一個對象的類型 # input # print #isinstance #判斷對象類型,返回bool值 l=[1,2,‘q‘] print(isinstance(l,list)) # iter #將叠代器轉換為生成器 l=[1,2,3] new_l=iter(l) print(next(new_l)) print(next(new_l)) # len # open # pow print(pow(10,2,3)) #10**2%3 # type # zip #使用zip()函數來可以把列表合並,並創建一個元組對的列表。 l1=[1,2,3,4] l2=[‘q‘,‘a‘] new=zip(l1,l2) print(list(new),type(new))
五、模塊與包
5.1模塊
5.1.1模塊的定義
一個模塊就是一個包含了python定義和聲明的文件(文件名就是模塊名字加上.py的後綴),模塊可以被導入使用。
import加載的模塊分為四個通用類別:
1 使用python編寫的.py文件
2 已被編譯為共享庫或DLL的C或C++擴展
3 把一系列模塊組織到一起的文件夾(註:文件夾下有一個__init__.py文件,該文件夾稱之為包)
4 使用C編寫並鏈接到python解釋器的內置模塊
5.1.2模塊的使用
#模塊可以包含可執行的語句和函數的定義,這些語句的目的是初始化模塊,它們只在模塊名第 #一次遇到導入import語句時才執行(import語句是可以在程序中的任意位置使用的,且針對同 #一個模塊很import多次,為了防止你重復導入,python的優化手段是:第一次導入後就將模塊 #名加載到內存了,後續的import語句僅是對已經加載大內存中的模塊對象增加了一次引用, #不會重新執行模塊內的語句),如下 #test.py import spam #只在第一次導入時才執行spam.py內代碼,此處的顯式效果是只打印一次‘from the spam.py‘,當然其他的頂級代碼也都被執行了,只不過沒有顯示效果. import spam import spam import spam ‘‘‘ 執行結果: from the spam.py ‘‘‘ #ps:我們可以從sys.module中找到當前已經加載的模塊,sys.module是一個字典,內部包含模塊名與模塊對象的映射,該字典決定了導入模塊時是否需要重新導入。 #首次import時做了三件事 #1.為源文件(spam模塊)創建新的名稱空間,在spam中定義的函數和方法若是使用到了global時訪問的就是這個名稱空間。 #2.在新創建的命名空間中執行模塊中包含的代碼,見初始導入import spam 提示:導入模塊時到底執行了什麽? In fact function definitions are also ‘statements’ that are ‘executed’; the execution of a module-level function definition enters the function name in the module’s global symbol table. 事實上函數定義也是“被執行”的語句,模塊級別函數定義的執行將函數名放 入模塊全局名稱空間表,用globals()可以查看 #3.創建名字spam來引用該命名空間 這個名字和變量名沒什麽區別,都是‘第一類的’,且使用spam.名字的方式 可以訪問spam.py文件中定義的名字,spam.名字與test.py中的名字來自 兩個完全不同的地方。 #每個模塊都是一個獨立的名稱空間,定義在這個模塊中的函數,把這個模塊的名稱空間當 #做全局名稱空間,這樣我們在編寫自己的模塊時,就不用擔心我們定義在自己模塊中全局 #變量會在被導入時,與使用者的全局變量沖突 #4、為模塊名起別名 #為已經導入的模塊起別名的方式對編寫可擴展的代碼很有用 import spam as sm print(sm.money) #5、在一行導入多個模塊 import sys,os,re #6、from...import 與import的對比 #唯一的區別就是:使用from...import...則是將spam中的名字直接導入到當前的名稱空間中,所以在當前名稱空間中,直接使用名字就可以了、無需加前綴:spam. #6from...import...的方式有好處也有壞處 好處:使用起來方便了 壞處:容易與當前執行文件中的名字沖突 #也支持as from spam import read1 as read #也支持導入多行 from spam import (read1, read2, money) #from...import * #from spam import * 把spam中所有的不是以下劃線(_)開頭的名字都導入到當前位置 #大部分情況下我們的python程序不應該使用這種導入方式,因為*你不知道你導入什麽名字,很有可能會覆蓋掉你之前已經定義的名字。而且可讀性極其的差,在交互式環境中導入時沒有問題。 #可以使用__all__來控制*(用來發布新版本),在spam.py中新增一行 __all__=[‘money‘,‘read1‘] #這樣在另外一個文件中用from spam import *就這能導入列表中規定的兩個名字
5.1.3 模塊重載
python 重載模塊必須重啟程序,python不支持重新加載或卸載之前導入的模塊;如果只是你想交互測試的一個模塊,使用 importlib.reload(), e.g. import importlib; importlib.reload(modulename),這只能用於測試環境。
5.1.4 模塊的搜索路徑
1)查看內存中是否已經加載相應模塊,
2)1中沒有則,查找同名的內建模塊,
3)2中沒有則在sys.path給出的目錄列表中依次尋找相應文件
ps:#在初始化後,python程序可以修改sys.path,路徑放到前面的優先於標準庫被加載。
>>> import sys
2
>>> sys.path.append(‘/a/b/c/d‘)
>>> sys.path.insert(0,‘/x/y/z‘)
註意:搜索時按照sys.path中從左到右的順序查找,位於前的優先被查找,sys.path中還可能包含.zip歸檔文件和.egg文件,python會把.zip歸檔文件當成一個目錄去處理,
#首先制作歸檔文件:zip module.zip foo.py bar.py
import sys
sys.path.append(‘module.zip‘)
import foo,bar
#也可以使用zip中目錄結構的具體位置
sys.path.append(‘module.zip/lib/python‘)
#windows下的路徑不加r開頭,會語法錯誤
sys.path.insert(0,r‘C:\Users\Administrator\PycharmProjects\a‘)
#至於.egg文件是由setuptools創建的包,這是按照第三方python庫和擴展時使用的一種常見格式,.egg文件實際上只是添 加了額外元數據(如版本號,依賴項等)的.zip文件。
#需要強調的一點是:只能從.zip文件中導入.py,.pyc等文件。使用C編寫的共享庫和擴展塊無法直接從.zip文件中加載(此時setuptools等打包系統有時能提供一種規避方法),且從.zip中加載文件不會創建.pyc或者.pyo文件,因此一定要事先創建他們,來避免加載模塊是性能下降。
ps:python中.py文件的兩種應用
#編寫好的一個python文件可以有兩種用途: 一:腳本,一個文件就是整個程序,用來被執行 二:模塊,文件中存放著一堆功能,用來被導入使用 #python為我們內置了全局變量__name__, 當文件被當做腳本執行時:__name__ 等於‘__main__‘ 當文件被當做模塊導入時:__name__等於模塊名 #作用:用來控制.py文件在不同的應用場景下執行不同的邏輯 if __name__ == ‘__main__‘: #fib.py def fib(n): # write Fibonacci series up to n a, b = 0, 1 while b < n: print(b, end=‘ ‘) a, b = b, a+b print() def fib2(n): # return Fibonacci series up to n result = [] a, b = 0, 1 while b < n: result.append(b) a, b = b, a+b return result if __name__ == "__main__": import sys fib(int(sys.argv[1])) #執行:python fib.py <arguments> python fib.py 50 #在命令行
5.2 包
5.2.1包的定義
包就是一個包含有__init__.py文件的文件夾,所以其實我們創建包的目的就是為了用文件夾將文件/模塊組織起來
5.2.2包的使用
1) 在python3中,即使包下沒有__init__.py文件,import 包仍然不會報錯,而在python2中,包下一定要有該文件,否則import 包報錯
2) 創建包的目的不是為了運行,而是被導入使用,記住,包只是模塊的一種形式而已,包的本質就是一種模塊\
3)關於包相關的導入語句也分為import和from ... import ...兩種,但是無論哪種,無論在什麽位置,在導入時都必須遵循一個原則:凡是在導入時帶點的,點的左邊都必須是一個包,否則非法。可以帶有一連串的點,如item.subitem.subsubitem,但都必須遵循這個原則。但對於導入後,在使用時就沒有這種限制了,點的左邊可以是包,模塊,函數,類(它們都可以用點的方式調用自己的屬性)。
4)import導入文件時,產生名稱空間中的名字來源於文件,import 包,產生的名稱空間的名字同樣來源於文件,即包下的__init__.py,導入包本質就是在導入該文件
5)包A和包B下有同名模塊也不會沖突,如A.a與B.a來自倆個命名空間
#文件內容 #policy.py def get(): print(‘from policy.py‘) #versions.py def create_resource(conf): print(‘from version.py: ‘,conf) #manage.py def main(): print(‘from manage.py‘) #models.py def register_models(engine): print(‘from models.py: ‘,engine)
#import import glance.db.models glance.db.models.register_models(‘mysql‘) #單獨導入包名稱時不會導入包中所有包含的所有子模塊,如 #在與glance同級的test.py中 import glance glance.cmd.manage.main() ‘‘‘ 執行結果: AttributeError: module ‘glance‘ has no attribute ‘cmd‘ ‘‘‘ #解決方法: #glance/__init__.py#在此文件中寫入如下內容 from . import cmd #glance/cmd/__init__.py from . import manage ##from ... import ... #需要註意的是from後import導入的模塊,必須是明確的一個不能帶點,否則會有語法錯誤,如:from a import b.c是錯誤語法 from glance.db import models models.register_models(‘mysql‘) from glance.db.models import register_models register_models(‘mysql‘) ##from glance.api import * 在講模塊時,我們已經討論過了從一個模塊內導入所有*,此處我們研究從一個包導入所有*。 此處是想從包api中導入所有,實際上該語句只會導入包api下__init__.py文件中定義的名字,我們可以在這個文件中定義__all___: #在__init__.py中定義 x=10 def func(): print(‘from api.__init.py‘) __all__=[‘x‘,‘func‘,‘policy‘] 此時我們在於glance同級的文件中執行from glance.api import *就導入__all__中的內容(versions仍然不能導入)。 ##絕對導入和相對導入 我們的最頂級包glance是寫給別人用的,然後在glance包內部也會有彼此之間互相導入的需求,這時候就有絕對導入和相對導入兩種方式: 絕對導入:以glance作為起始 相對導入:用.或者..的方式最為起始(只能在一個包中使用,不能用於不同目錄內) 例如:我們在glance/api/version.py中想要導入glance/cmd/manage.py 在glance/api/version.py #絕對導入 from glance.cmd import manage manage.main() #相對導入 from ..cmd import manage manage.main() ###註意### 包以及包所包含的模塊都是用來被導入的,而不是被直接執行的。而環境變量都是以執行文件為準的 比如我們想在glance/api/versions.py中導入glance/api/policy.py,有的同學一抽這倆模塊是在同一個目錄下,十分開心的就去做了,它直接這麽做 #在version.py中 import policy policy.get() 沒錯,我們單獨運行version.py是一點問題沒有的,運行version.py的路徑搜索就是從當前路徑開始的,於是在導入policy時能在當前目錄下找到 但是你想啊,你子包中的模塊version.py極有可能是被一個glance包同一級別的其他文件導入,比如我們在於glance同級下的一個test.py文件中導入version.py,如下 from glance.api import versions ‘‘‘ 執行結果: ImportError: No module named ‘policy‘ ‘‘‘ ‘‘‘ #分析: 此時我們導入versions在versions.py中執行 import policy需要找從sys.path也就是從當前目錄找policy.py, 這必然是找不到的 ‘‘‘
python學習第五天