標籤:
一、函數
函數的作用:可以計算出一個傳回值,最大化代碼重用,最小化代碼冗餘,流程的分解。
1、函數相關的語句和運算式
語句 例子
Calls myfunc(‘diege‘,‘eggs‘,meat=‘lit’) #使用函數
def,return,yield def adder(a,b=1,*c):
return a+b+c[0]
global changer():
global x;x=‘new‘
lambda Funcs=[lambad x:x**2,lambad x:x*3]
2、編寫函數
def建立了一個對象並將其賦值給某一個變數名。
return將一個結果對象發送給調用者。
函數是通過賦值(對象引用)傳遞的。
參數通過賦值傳遞給函數。
global聲明了一個模組層級的變數並被賦值。
參數,傳回值以及變數並不是聲明
def <name>(arg1,age2,...,agrN):
<statements>
return <value
函數主體往往都包含了一個return語句(不是必須的),如果它沒有出現,那麼函數將會在控制流程執行完函數主體時結束,技術上,沒有傳回值的函數自動返回了none對象。
return可以在函數主體中的任何地方出現。它表示函數調用的結束,並將結果返回至函數調用。
不要嘗試判斷傳入參數的物件類型,這樣實質上破壞了函數的靈活性,把函數限制在特定的類型上
3、範圍
範圍:變數定義以及尋找的地方。
本地變數:在函數內定義的變數為本地變數。
全域變數:在檔案內[模組內]函數外定義的變數為全域變數
Python建立,改變或者尋找變數名都是在所謂的命名空間(一個儲存變數名的地方,模組中為__main__)中進行。範圍這個術語指的就是命名空間。
也就說,在代碼中變數名被賦值的位置決定了這個變數名能被訪問到的範圍
一個函數所有變數名都與函數的命名空間相關聯。
*def內定義變數名def內使用,是本地變數
*def之中的變數名與def之外的變數名不發生衝突,使用別處相同的變數名也沒問題。
函數定義了本地範圍,而模組定義了全域範圍。兩範圍關係。
*內嵌的模組是全域範圍:對於外部的全域變數就成為了一個模組對象的屬性
*全域範圍的作用範圍僅限單個檔案:不要被全域迷惑,這裡的全域是一個檔案的頂層的變數名,僅對這個檔案內部的代碼而言是全域。
Python中,沒有一個無所不包的情景檔案範圍。替代的方法是,變數名由模組檔案隔開,必須精準地匯入一個模組檔案才能夠使用這檔案中
定義的變數名,
*每次對函數的調用都建立了一個新的本地範圍。
*賦值的變數名廢除聲明為全域變數,否則均為本地變數。
*所用的變數名都可以歸納為本地,全域,或者內建。(內建:ptyhon預定義的__builtin__模組提供的)
變數名解析:LEGB原則
對一個def語句
*變數名引用分為三個範圍進行尋找:首先尋找本地,然後是函數內(如果有),之後全域,最後內建。
*預設情況下,變數名賦值會建立或改變本地變數
*全域聲明將賦值變數名映射到模組檔案內部的範圍。
global
global語句包含關鍵字global
*全域變數是位於模組檔案內部頂層的變數名
*全域變數如果是在函數內部被賦值的話,並需經過聲明
*全域變數名在函數的內部不經過聲明也可以被引用
4、傳遞參數
參數傳遞:傳遞給函數作為其輸入對象的方式
*參數的傳遞是通過自動將對象賦值給本地變數來實現的。
*在函數內部的參數名的賦值不會影響調用者。
*改變函數的可變對象參數的值也許會對調用者有影響。
換句話說,因為參數是簡單的通過賦值進行對象的傳遞,函數能夠改變傳入的可變對象,因此其結果會影響調用者。
*不可變參數是“通過值”進行傳遞。
像整數和字串這樣的對象是通過對象引用而不是拷貝進行傳遞的,但是因為你無論如何都不可能在原處改變不可變對象,實際的效果就是很像建立了一份拷貝。
可變對象是通過“指標”進行傳遞的。
避免可變參數的修改
在Python中,預設通過引用(也就是指標)進行函數的參數傳遞。如果不想在函數內部在原處的修改影響傳遞給它的對象。那麼,能夠簡單的建立一個可變對象的拷貝。
我們總是能夠在調用時對列表進行拷貝
L=[1,2]
changer(X,L[:])
如果不想改變傳入的對象,無論函數是如何調用的,我們可以在函數內部進行拷貝,避免可變參數的修改
>>> def changer(a,b):
... a=2
... b=b[:]
... b[0]=‘diege‘
特定參數匹配模型
參數在ptyhon中總是通過賦值進行傳遞,傳入的對象賦值給了在def頭部的變數名。儘管這樣,在模型的上層,python提供了額外的工具,該工具改變了調用過中
賦值時參數對象匹配在頭部的參數名的優先順序。這些工具是可選的。
總結與特定模式有關的文法:
文法 位置 解釋
func(value) 調用者 常規參數,通過位置進行匹配,從左至右進行匹配
func(name=value) 調用者 關鍵字參數,通過變數名匹配
func(*name) 調用者 以name傳遞所有的對象,並作為獨立的基於位置的參數
func(**name) 調用者 以name成對的傳遞所有的關鍵字/值,並作為獨立的關鍵字的參數
def func(name) 函數 常規參數:通過位置或變數名進行匹配
def func(name=value) 函數 預設參數值:如果沒有在調用中傳遞的話,就是用預設值
def func(*name) 函數 匹配並收集(在元組中)所有包含位置的參數
def func(**name) 函數 匹配並收集(在字典中)所有包含位置的參數。
5、匿名函數:lamdba
lambad 建立了一個之後能夠被調用的函數,它返回了一個函數而不是將這個函數賦值給一個變數名。
lambda運算式
lanbda arg1,arg2,...,argN:expression using arguments
lambda 是一個運算式,而不是一個語句
lambda的主體是一個單個的運算式,而不是代碼塊
func(x,y,z):return x+y+z
預設參數也能夠再lambda參數中使用,就像在def中使用一樣。
lambda a=‘free‘,b=‘file‘,c=‘feel‘:a+b+c
6、作參數來應用函數
內建函數apply
當需要變得更加動態話,可以通過將一個函數作為一個參數傳遞給apply來調用一個產生的函數,並且
也將傳給那個函數的參數作為一個元組傳遞給apply函數()
apply(函數,參數1(元組),參數2(元組))
7、在序列中映射函數:map
使用內建工具map,map函數會對一個序列對象中的每一個元素應用被傳入的函數,並且返回一個包含了所有函數調用結果的一個列表。
map(函數,傳入函數的序列對象)
>>> def inc(x):return x+10
>>> L=[1,2,3,4,5]
>>> map(inc,L)
[11, 12, 13, 14, 15]
>>> L=[1,2,3,4,5]
map嵌套lambda
>>> map((lambda x:x+3),L)
[4, 5, 6, 7, 8]
進階功能:提供了多個序列作為參數,它能夠並行返回分別以每個序列的元素作為【函數對應參數】得到的結果的列表
>>> pow(3,4)
81
>>> map(pow,[1,2,3],[2,3,4]) #1**2,2**3,3**4
[1, 8, 81]
8、函數式編程工具:filter和reduce
函數式編程的意思就是對序列應用一些函數的工具。
基於某一測試函數過濾出一些元素-filter
對每對元素都應用函數並運行到最後結果-reduce
>>> filter((lambda x:x>0),range(-5,5))
[1, 2, 3, 4]
這個等效於for range:if語句
reduce稍微複雜一點。這裡兩個reduce調用,計算在一個列表中所有元素加起來和以及乘起來的乘積
>>> reduce((lambda x,y:x+y),[1,2,3,4])
10
>>> reduce((lambda x,y:x*y),[1,2,3,4])
24
依次計算
9、重訪列表解析:映射
1)、列表解析基礎
>>> [x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> map((lambda x:x**2),range(10))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
2)、增加測試和嵌套迴圈
在for之後編寫if分支,用來增加邏輯選擇,if分支相當filter
>>> [x for x in range(5) if x%2==0]
[0, 2, 4]
>>> filter((lambda x:x%2==0),range(5))
[0, 2, 4]
3)filter出來的列表可以作為map的第2個參數
>>> map((lambda x:x**2),filter((lambda x:x%2==0),range(5)))
[0, 4, 16]
列表解析和map的對比
兩者類似,都會收集對序列或其他可迭代對象中每個元素應用元算後的結果(一次一個項目),從而建立新列表。其主要差異在於。
map會對每個元素應用函數,而列表解析則是應用任意的運算式。因此,列表解析更通用一些,可以像map那樣應用函數調用運算式,
但是,map需要一個函數才能應用其他種類的運算式(函數是map的第一參數).列表解析也支援擴充文法,如果,嵌套for迴圈和if
分句從而可以包含內建函數filter的功能。
10、重返迭代器:產生器
編寫的函數能夠返回一個值,並且稍後還可以從它剛才離開的地方仍然傳回值。這樣的函數被認作是產生器,因為它們隨時間產生一個序列的值。
大多數方面產生器函數就像一般函數,在Python它們被自動用作實現迭代協議,因它只能夠再迭代的語境中出現。
產生器和一般的函數之間代碼上最大的不同就是一個產生器yield一個值,而不是return一個值。yield語句將會將函數關起,並向它的調用者返回一個值
但是儲存足夠的狀態資訊為了讓其能夠在函數從它掛起的地方恢複。
包含yield的語句的函數將會特地編譯成為產生器。當調用它時,他們返回一個產生器對象,這個產生器對象支援迭代器對象介面。
>>> def Dtest(N):
... for i in range(N):
... yield i**2
使用
>>> for i in Dtest(5):
... print i,‘:‘,
...
0 : 1 : 4 : 9 : 16 :
查看過程
>>> x=Dtest(4)
>>> x.next()
0
>>> x.next()
11、函數設計概念
*耦合性:對於輸入使用參數,並且對於輸出使用return語句
*耦合性:只有在真正必要的情況下使用全域變數。
*耦合性:不要改變可變類型的參數,除非調用者希望這樣做。
*彙總性:每一個函數都應該有一個單一的,同一的目標
*大小:每一個函數應該相對較小。
*耦合:避免直接改變在另一個模組檔案中的變數。
函數是對象:簡潔調用
二、模組
1、基本
每個檔案都是一個模組,並且模組匯入其他模組之後就可以使用匯入模組定義的變數名。模組可以由兩個語句和一個重要的內建函數進行處理。
import: 使用戶端(匯入者)以一個整體擷取一個模組。
from:容許用戶端從一個模組檔案中擷取特定的變數名。
reload:在不中止Python程式的情況下,提供了一個重新載入模組檔案代碼的方法。
import和from是指派陳述式,是可執行檔語句,可以嵌套到if ,def語句中和def一樣import和from都是隱性指派陳述式
在一個模組檔案的頂層定義的所有變數名都成為了被匯入的模組對象的屬性。
模組至少有三個角色:
代碼重用:模組還是定義變數名的空間,被認作是屬性。可以被多個外部的用戶端應用。
系統命名空間的劃分:
現實共用服務和資料:
2、python程式構架
import如何工作
執行三個步驟
1)、找到模組檔案
2)、編譯成位碼(需要時)
3)、執行模組的代碼來建立其所定義的對象。
在之後匯入相同的模組時候,會跳過這三個步驟,而只提取記憶體中已載入模組對象。
搜尋模組
匯入模組時,不帶模組的尾碼名,比如.py
Python搜尋模組的路徑:
1)、程式的主目錄
2)、PTYHONPATH目錄(如果已經進行了設定)
3)、標準串連庫目錄(一般在/usr/local/lib/python2.X/)
4)、任何的.pth檔案的內容(如果存在的話).新功能,允許使用者把有效果的目錄添加到模組搜尋路徑中去
.pth尾碼的文字檔中一行一行的地列出目錄。
這四個組建組合起來就變成了sys.path了,
>>> import sys
>>> sys.path
匯入時,Python會自動由左到右搜尋這個列表中每個目錄。
第1,第3元素是自動定義的,第2,第4可以用於擴充路徑,從而包括自己的源碼目錄。
3、模組的建立和使用。
建立模組
尾碼.py文字檔,模組頂層指定的所有變數名都會變成其屬性。
定義一個module.py模組
name=‘diege‘
age=18
def printer(x):
print x
使用模組
import全部匯入,import將整個模組對象賦值給一個變數名,模組只匯入一次,因為該操作開銷大
>>> import module
屬性
>>> module.name
‘diege‘
函數
>>> module.printer(‘hi‘)
hi
from語句:from 將擷取(複製)模組特定變數名
from會把變數名賦值到另一個範圍,所以它就可以讓我們直接在指令碼中使用複製後的變數名,而不是通過模組
from 模組名 import 需要複製的屬性 將一個或多個變數名賦值給另一個模組中同名的對象
from 模組名 import 需要複製的屬性 as 新的屬性名稱 將一個或者多個變數名賦值給另一個模組中不同名的對象
from * 語句 from 模組名 import * 取得模組頂層所有賦了值的變數名的拷貝
>>> from module import name
>>> name
‘diege
>>> from module import name as myname
>>> myname
‘diege‘
>>> from module import printer as PR
>>> PR(‘hi python‘)
hi python
>>> from module import name,age 複製多個變數名時要用逗號隔開
>>> name,age
(‘diege‘, 18)
>>> from module import name as myname,age as myage 複製多個變數名並改變需時需要用逗號隔開多個as
>>> from module import *
4、模組命名空間
模組最好理解為變數名的封裝,簡而言之,模組就是命名空間(變數名建立所在的場所),而存在於模組之內的變數名就是模組對象的屬性。
檔案產生命名空間
*模組語句會在首次匯入時執行。
*頂層的指派陳述式會建立模組屬性(檔案頂層不在的def和class之內的,但def和class隱性建立的變數名也屬於模組屬性)。賦值的變數名會儲存在模組的命名空間內。
*模組的命名空間能通過屬性__dict__(module.__dict__)或dir(module)擷取
由於匯入而建立的模組的命名空間是字典,可通過模組對象相關聯的內建__dict__屬性讀取。
dir函數查看,大至與對象的__dict__屬性的鍵排序後的列表相等,但是它還包含了類繼承的變數名。
*模組是一個獨立的範圍。
5、重載模組
模組程式碼預設值對每個過程執行一次,要強制使模組代碼重新載入並重新運算需要使用reload內建函數。
reload是函數,import是語句。兩個文法不一樣。
>>> import module
>>> reload(module)
<module ‘module‘ from ‘module.pyc‘>
>>> reload(test17)
<module ‘test17‘ from ‘/root/test17.py‘>
reload()之前需得import過一次
6、模組包
除模組名以外,匯入也可以指定目錄路徑,Pytyh代碼的目錄就是稱為包。因此這類匯入就稱為包匯入
import dir1.dir2.mod
from dir1.dir2.mod import x
.號路徑相當於機器上目錄層次的路徑。
dir1在容器目錄dir0中,dir0這個目錄可以在Python模組搜尋路徑中找到。
__init__.py包檔案
如果選擇使用包匯入,那就必須遵循一條約束:包匯入語句的路徑的每個目錄內部都必須有__init__.py這個檔案,
否則匯入包會失敗。
dir1和dir2中必須包含__init__.py,容器目錄dir0不需要這類檔案。因為本身沒有列在import語句中
__init__.py檔案可以包含程式碼,也可以是空的。 更通常情況下,__init__.py檔案扮演了包初始化的掛鈎
替目錄產生模組命名空間以及用目錄匯入實現from *行為的角色。
*包初始化:
首次匯入某個目錄時,會自動執行該目錄下__init__.py檔案中所有程式碼。
所以這個檔案就是放置包內檔案所需初始化的代碼的場所。可以使用其初始設定檔案,建立所需的資料檔案,
串連資料庫等。
*模組命名空間的初始化:
*from * 語句的行為:
作為一個進階功能,可以在__init__.py檔案中使用__all__列表來定義目錄以form *語句形式匯入時,需要
匯出什麼。__all__列表是指出當包(目錄—)名稱使用from *的時候,應該匯入的子模組名稱清單。
eg:
/usr/local/lib/python2.7/sqlite3/__init__.py
from dbapi2 import *
/usr/local/lib/python2.7/site-packages/mod_python/__init__.py
__all__ = ["apache", "cgihandler", "psp",
"publisher", "util", "python22"]
version = "3.3.1"
常見的第三方擴充都是以包目錄形式發布給使用者,而不是單純的模組列表。
這樣就可以通過路徑來匯入
7、在模組中隱藏資料
最小化from *的破壞:_X和__all__達到隱藏變數名的目的
有種特定情況,可以把底線放在變數名前(_X),可以防止用戶端使用from * 語句匯入模組名時,把其中的那些變數名賦值出去。這其實是為了把命名空間的破壞最小化而已。底線和__all__不是私人聲明,還可以通過其他匯入形式修改這類變數名。例如import語句、from module import _X
以外,也可以在模組頂層把變數名的字串列表賦值給變數__all__,以達到類似於_X命名慣例的隱藏效果【__all__是不隱藏的】
mod_python.__all__ 可以看到可以用from *語句複製那些變數名
_X和__all__ 對比
_X 隱藏了 無法from *
__all__ 只顯示,from *只能擷取__all__中指定的,其他隱藏。
python中from *會先尋找模組內的__all__列表,有的話複製其中的變數名,如果沒有定義的話,from *就會複製開頭沒有底線的所有命令名。
怎麼覺得__all__列表裡存放的是模組呢???
8、混合用法模式:__name__和__main__
這是一個特殊的與模組相關的技巧,可以把檔案作為模組匯入,並以獨立式程式的形式運行。每個模組都有個名為__name__的內建屬性。Python會自動化佈建該屬性:
*如果檔案是以頂層程式檔案執行,在啟動時,__name__就會被設定為字串__main__
*如果檔案被匯入,__name__就會改設成用戶端所瞭解模組名。
結果就是模組可以檢測自己的__name__,來確定它是執行還是在匯入。
定義一個檔案test15.py
def tester():
print "It‘s python test!"
if __name__==‘__main__‘:
tester()
9、修改模組搜尋路徑
可以通過PYTHONPATH以及可能的.pth路徑檔案進行定製。
Python程式本身是修改sys.path的內建列表。sys.path在程式啟動時就進行初始化,但那之後也可以隨意對其元素進行刪除,附加和重設
>>> import sys
>>> sys.path
# cd /tmp/
# python
>>> sys.path.append(‘/root‘)【增加新的路徑】
>>> sys.path
[‘‘, ‘/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-i386.egg‘, ‘/usr/local/lib/python2.7/site-packages/setuptools-0.6c12dev_r88846-py2.7.egg‘, ‘/usr/local/lib/python2.7/site-packages/Babel-0.9.6-py2.7.egg‘, ‘/usr/local/lib/python2.7/site-packages/Trac-0.12.3-py2.7.egg‘, ‘/usr/local/lib/python2.7/site-packages/Genshi-0.6-py2.7.egg‘, ‘/usr/local/lib/python2.7/site-packages/IniAdmin-0.2_r10454-py2.7.egg‘, ‘/usr/local/lib/python2.7/site-packages/TracAccountManager-0.4dev_r11251-py2.7.egg‘, ‘/usr/local/lib/python2.7/site-packages/SvnAuthzAdminPlugin-0.2-py2.7.egg‘, ‘/usr/local/lib/python27.zip‘, ‘/usr/local/lib/python2.7‘, ‘/usr/local/lib/python2.7/plat-freebsd8‘, ‘/usr/local/lib/python2.7/lib-tk‘, ‘/usr/local/lib/python2.7/lib-old‘, ‘/usr/local/lib/python2.7/lib-dynload‘, ‘/usr/local/lib/python2.7/site-packages‘, ‘/root‘]
匯入/root目錄下test17.py,注意啟動python時在/tmp目錄,所以/root不是程式啟動目錄
>>> import test17
>>> dir(test17)
>>> test17.lessthan(3,4)
True
10、相對匯入文法
from語句現在可以使用點號(.)更傾向於同一個包內的模組(稱為包相對匯入),而不是位於模組匯入搜尋路徑上其他地方的模組(所謂的絕對匯入)
*現在,可以使用點號指出該匯入應該與其所在包相關聯:這類匯入傾向於匯入位於該包內的模組,而不是匯入搜尋路徑sys.path上其他地方的同名模組
from .apache CallBack as CB
同一個包內匯入apache模組CallBack為CB變數
11、模組設計理念
*總是在Python的模組內編寫代碼
*模組耦合要降到最底:全域變數。模組應該儘可能和其他模組的全域變數無關。
*最大化模組的沾合性:統一目標
*模組應該少去修改其他模組的的變數。
模組是對象:元程式
模組通過內建屬性顯示了他們大多數的特性。因此,可很容易的編寫程式來管理其他程式。我們通常稱這類管理程式為元程式,因為他們是在其他系統上工作。這也稱為內省,因為程式能看見和處理對象的內部。內省是進階功能,但是它可以做建立程式工具,取得模組內名為name的屬性,方法包括
(1)可以使用結合點號運算,
(2)或者對模組的屬性字典進行索引運算(在內建__dict__屬性中顯示)。
(3)Python也在sys.modules字典中匯出所有已經載入的模組。
(4)並提供一個內建函數getattrr,讓我們以字串名來取出屬性。(就好像object.attr,而attr是運行時的字串)
>>> test17.name
‘diege‘
>>> test17.__dict__.keys()
>>> test17.__dict__[‘name‘]
‘diege
>>> test17.__dict__[‘lessthan‘]
<function lessthan at 0x28495844>
>>> sys.modules 顯示所有載入的模組
>>> sys.modules[‘test17‘]
<module ‘test17‘ from ‘/root/test17.py‘>
>>> sys.modules[‘test17‘].name
‘diege
>>> getattr(test17,‘lessthan‘)
<function lessthan at 0x28495bc4>
12、模組陷阱
1)頂層代碼的語句次序的重要性
*在匯入時,模組檔案頂層的程式碼(不在函數內)一旦python運行,就會立刻執行。因此,該語句是無法引用檔案後面位置賦值的變數名。
*位於函數主體內的代碼知道函數被調用後才會運行。
作為一條原則,如果需要把立即執行的代碼和def一起混用,就要把def放在檔案前面,把頂層代碼放在後面。這樣的話,你的函數在使用的代碼運行時,可以保證他們都已定義並賦值過了。
2)通過變數名字串匯入模組的方法
import或from語句內的模組名是”寫入程式碼“的變數名。
>>> x=‘string‘
>>> import x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named x
這裡python會試著匯入檔案x.py
為了避免發生這樣的問題,通常的做法就是把import語句構造成python代碼的字串,再傳給exec語句執行:
>>> modname=‘string‘
>>> exec "import "+modname
exec語句以及和他類似eval會編譯一段字串代碼,將其傳給Python解析器來執行。
3)from複製變數名,而不是串連
from語句其實是在匯入者的範圍內對變數名的指派陳述式,也就是變數名拷貝運算,而不是變數名的別名機制。它的實現和python所有賦值運算都一樣,微妙之處在於,共用對象的代碼存在於不同的檔案中。然後,我們使用import獲得了整個模組,然後賦值某個點號運算的變數名,就會修改匯入的模組中的變數名。點號運算把python定向到了模組對象,而不是賦值模組中對象。
4)from*會讓變數語義模糊
5)reload不會影響from匯入
6)不要使用reload、from以及互動模式測試
reload中引用模組得通過import至少將其載入一次:
不要from匯入之後reload
7) reload使用沒有傳遞性
當重載一個模組時,Python只會重載那個模組的檔案,不會自動重載該檔案重載嘶碰巧還要匯入的模組。
8)遞迴形式的from import無法工作
不要在遞迴匯入中使用 from。
Python學習筆記總結(二)函數和模組