基於字典的字符串格式化
'%(n)d %(x)s' % {'n':1,'x':'spam'}
>>>'1 spam '
文件字符集編碼聲明
在腳本的第一行或第二行聲明以下注釋可以指明想要的編碼,從而將默認編碼修改爲支持任意的字符集
# -*- coding: latin-1 -*-
字符串類型
python 2.x
- str表示8位文本和二進制數據
- unicode用來表示寬字符Unicode文本
python 3
- str表示Unicode文本(8位的和更寬的)
- bytes表示二進制數據
- bytearray,是一種可變的bytes類型
轉換(python 3)
- str.encode()和bytes(s, encoding)把一個字符串轉換爲其raw bytes形式
bytes.decode()和str(b, encoding)把raw bytes轉換爲其字符串形式。
s='eggs' s.encode() >>>b'eggs' bytes(s, encoding='ascii') >>>b'eggs' b=b'spam' b.decode() >>>'spam' str(b, encoding='ascii') >>>'spam'
如果str函數省略了編碼參數,返回的是打印字符串而不是轉換後的字符串,e.g.
b=b'spam' str(b) >>>"b'spam'" len(str(b)) >>>7
字節常量要求字符要麼是ascii字符,如果它們的值大於127就進行轉義(只能以十六進制轉義)。
字符串常量可以包含字符集中的任何字符(十六進制或Unicode轉義)。
文本文件和二進制文件(python3)
- 以文本模式打開(e.g. w,r,w+,r+),讀取數據會自動將其內容解碼(默認編碼或提供一個編碼名稱),並且將其返回爲一個str,寫入會接受一個str。
- 以二進制模型打開(e.g. wb, rb),讀取其數據不會以任何方式編碼它,直接返回其內容raw並且未經修改作爲一個bytes對象,寫入也類似地接受一個bytes對象。
作用域
LEGB法則
- 本地作用域(L)
- 上一層的def或lambda的本地作用域(E)
- 全局作用域(G)
- 內置作用域(B)
在函數內引用全局變量不需要global聲明,賦值則需要。
nonlocal的作用與global類似,不過是用於嵌套的函數作用域。
在某個函數內部調用一個以後才定義的函數是可行的,只要第二個函數定義的運行是在第一個函數調用前就行。
nonlocal使得對變量的查找從嵌套的函數作用域中開始,而不是從聲明函數的本地作用域開始,並且不會繼續到全局或內置作用域中。
nonlocal聲明的變量必須在一個嵌套的函數中提前定義過,否則會產生一個錯誤。
encode和decode
unicode是python字符串的內部編碼方式,因此encode和decode都是圍繞着unicode編碼來進行編碼轉化的。decode是將其他編碼的字符串解碼爲unicode編碼,而encode則是將unicode編碼的字符串編碼爲另一種編碼。
例如:str.decode(‘utf8’),這個語句將已知是utf8編碼的字符串str解碼爲unicode,而str.encode(‘utf8’)則是將unicode編碼的字符串str編碼爲utf-8。
一般情況下,字符串的編碼跟代碼文件的編碼保持一致,通過在字符串面量值前面加上字母u(例如:str = u’test’)可以強制指定爲unicode編碼而忽略文件的編碼。
nonlocal (python3)
- nonlocal使得對名稱的查找從嵌套的def的作用域中開始,而不是從聲明的函數的本地作用域開始
- nonlocal使得可以對變量進行修改而不只是引用
- nonlocal中列出的名稱必須在一個嵌套的def中提前定義過,否則會產生錯誤,global則不需要
- nonlocal限制作用域查找僅爲嵌套的def,不會在模塊的全局作用域或內置作用域中查找
函數參數
參數順序:
- 函數調用:位置參數,關鍵字參數(name=value)和*sequence形式的組合,**dict形式
- 函數聲明:一般參數(name),默認參數(name=value),*name形式(如果有),keyword-only參數(name或name=value)(python3纔可以),**name形式
參數匹配順序:
- 通過位置分配非關鍵字參數
- 通過匹配變量名分配關鍵字參數
- 其他額外的非關鍵字參數分配到*name元組中
- 其他額外的關鍵字參數分配到**name字典中
- 用默認值分配給未得到分配的參數
注意:
- 在函數調用和聲明中*name和**name的形式都只能出現一次,並且**name形式只能出現在最後
位置參數的匹配優先於關鍵字參數,e.g.
def f(a,b,c): print a,b,c f(a=1,*(1,2))
這樣的調用會出錯,參數a會被匹配兩次,因爲位置參數先匹配,所以參數a會賦值爲1,參數b會賦值爲
2,接着再進行關鍵字參數的匹配,這時再次對參數a進行匹配,所以就出錯了 再看個例子:def f(a,b,c,d): print a,b,c,d f(1,c=3,*(2,),d=4)
位置參數會先匹配,所以參數b被賦值爲2,在* sequence形式的後面只能是關鍵字參數或* * dict形式,如果是這樣調用則會出錯:f(1,c=3,*(2,),4)
在函數調用時,*sequence形式接受任意可迭代對象,e.g.
def f(a,b,c,d): print a,b,c,d l = [1,2,3,4] it = iter(l) f(*it) f(*open('XXX'))
python3支持keyword-only參數,在函數聲明中,keyword-only參數只能出現*arg或單獨的*字符(不需要可變數量位置參數)後面,在函數調用時,這些參數只能使用關鍵字語法來傳遞。e.g.
def f(a,*b,c): print a,b,c f(1,2,3) #出錯,c只能以關鍵字形式傳遞 def f(a,*,b,c): print a,b,c f(1,2,3) #出錯,b,c都只能以關鍵字形式傳遞 keyword-only參數也支持默認參數
函數註解(python3)
def f(a:'test',b:1,c:(1,2)=100)->'hello':
print(a,b,c)
>>> f.__annotations__
{'return': 'hello', 'c': (1, 2), 'a': 'test', 'b': 1}
函數註解只在def語句中有效,在lambda表達式中無效
生成器函數之send
send函數傳入的參數會作爲yield表達式的返回值,e.g.
def f():
for i in range(10):
x = yield i
print 'send ' + str(x)
>>> g = f()
>>> next(g)
0
>>> g.send(10)
send 10
1
生成器表達式
e.g.
g = (x for x in range(10)
>>> next(g)
0
>>> next(g)
1
實際上,列表解析基本等同於在list內置調用中包含一個生成器表達式以迫使其一起生成列表中所有的結果。e.g.
[x ** 2 for x in range(8)]
list(x ** 2 for x in range(8))
如果生成器表達式作爲函數調用中的唯一參數,括號是可以省略的,其他情況則不行。e.g.
sum(x for x in range(10))
sorted(x for x in range(10))
sorted((x for x in range(10)), reverse=True)
函數內的本地變量是靜態檢測的
x = 99
def f():
print x
x = 1
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in f
UnboundLocalError: local variable 'x' referenced before assignment
在編譯時,python看到對x的賦值語句(也包括import,嵌套def,嵌套類等)決定了x將在函數中的任何地方都將是本地變量名,即使運行時賦值語句是在print語句之後運行的
因爲上述的原因,所以在函數中,不可能同時使用同一變量名的本地變量和全局變量,如果希望在函數內打印全局變量,之後使用同一變量名的本地變量,可以通過以下方式實現:
#test.py
x=99
def f():
import test
print test.x #打印全局變量x的值
x = 88
print x #打印本地變量x的值
if __name__ == '__main__':
f()
print x #打印全局變量x的值
$python test.py
99
88
99
函數默認參數陷阱
默認參數是在def語句運行時評估並保存的,而不是在這個函數被調用的時候,從內部來講,python會爲每個默認參數保存成爲一個對象,附加在這個函數本身。e.g.
>>> def f(x=[]):
... x.append(1)
... print x
...
>>> f()
[1]
>>> f()
[1, 1]
>>> f()
[1, 1, 1]
import
python會把載入的模塊存儲到一個名爲sys.modules的表中,只有在模塊第一次導入時纔會進行加載。
import的三個步驟:
- 找到模塊文件
- 編譯成位碼(需要時)
- 執行模塊的代碼來創建其所定義的對象
只有被導入的(.py)文件纔會生成字節碼文件(.pyc)
模塊搜索路徑
按照搜索的順序排列如下:
- 程序的主目錄,頂層腳本文件的目錄而非當前工作目錄。
- PYTHONPATH目錄(如果已經設置該環境變量)
- (當前工作目錄)
- 標準鏈接庫目錄
- 任何.pth文件的內容(如果存在的話)[路徑文件作爲第三方庫經常使用,它通常在python的site-packages目錄安裝一個路徑文件,從而不需要用戶設置]
其中第一和第三是自動定義的,第二和第四用於拓展路徑
可以通過打印sys.path列表查看搜索路徑,導入模塊時,python會由左至右搜索這個列表中的每個目錄
模塊文件選擇
import b形式的可能會加載:
- 源代碼文件b.py
- 字節碼文件b.pyc
- 目錄b,包導入
- 編譯擴展模塊(通常用c或c++編寫),導入時使用動態連接(例如,linux的b.so以及cygwin和windows的b.dll或b.pyd)
- 用c編寫的編譯好的內置模塊,並通過靜態連接至python
- zip文件組件,導入時會自動解壓縮
- 內存內映像,對於frozen可執行文件
- java類,在jyhon版本的python中
- net組件,在IronPython版本的python中
重載模塊
- 導入(無論是是通過import或from語句)只會在模塊在流程中第一次導入時,加載和執行該模塊的代碼,之後的導入只會只用已加載的模塊對象,而不會加載或重新執行文件的代碼
- reload函數會強制已加載的模塊的代碼重新載入並重新執行。此文件中新的代碼的賦值語句會在適當的地方修改現有的模塊對象
- reload會在模塊當前命名空間內執行模塊文件的新代碼 。重新執行模塊文件的代碼會覆蓋其現有的命名空間,並非進行刪除而進行重建
- 文件中頂層的賦值語句會使得變量名換成新值。例如,重新執行的def語句會因爲重新賦值函數變量名而取代模塊命名空間內該函數之前的版本
- 重載會影響所有使用import讀取了模塊的客戶端。因爲使用import的客戶端需要通過點號運算取出屬性,在重載後,他們會發現模塊對象中變成了新的值
- 重載只會對以後使用from的客戶端造成影響。之前使用from來讀取屬性的客戶端並不會受到重載的影響,那些客戶端引用的依然是重載前所取出的舊對象
包導入
- 包導入語句的路徑中每個目錄內都必須有_ init_.py這個文件,否則導入包會失敗
- python首次導入某個目錄的時候,會自動執行該目錄下_ init_.py文件中的所有程序代碼(可以爲空)
- _ init_.py爲目錄創建了命名空間,該空間包含了在_ init_.py中賦值的所有變量名
- 可以在_ init_.py中使用_ all_列表來定義目錄以from*語句形式導入時,需要導出什麼。如果沒有指定_ all_,from*語句不會自動加載嵌套於該目錄內的子模塊,只會加載該目錄_ init_.py文件中賦值語句定義的變量名,包括該文件中明確導入的任何子模塊
包相對導入
相對導入只適用在包內的import
不帶點號的import,e.g.
import A
from A import B
在python2.6中先相對(包目錄)再絕對(sys.path)的搜索路徑順序
在python3中則是絕對的(sys.path),而不會在包目錄下搜索
帶點號的import,e.g.
from . import A
from .A import B
from .. import A
from ..A import B
在python2.6和python3中都是只會在包目錄下搜索
在模塊中隱藏數據
- 在變量名前面加一個下劃線(e.g. _X)可以防止使用from *語句導入模塊時,把這些變量名複製出去
- 在模塊頂層把變量名的字符串列表賦值給變量_ all_,from *語句只會把這些變量名複製出去
- python會先尋找模塊內的_ all_列表,如果沒有定義,from *就會複製出開頭沒有單下劃線的所有變量名
使用字符串導入模塊
>>> sys=__import__('sys')
>>> sys
<module 'sys' (built-in)>
類屬性與實例屬性
- 在方法內對self屬性做賦值運算會產生每個實例自己的屬性
- 在class語句內的頂層的賦值語句(不是在def之內)會產生類對象中屬性,該屬性被所有實例共享
- 對實例屬性的搜索順序:先在實例對象中尋找,然後是創建實例的類,之後是所有較高的超類,由對象樹底端到頂端,並且從左到右
- 繼承樹的搜索只發生在屬性引用時,而不是屬性的賦值運算時
調用類方法的兩種方法
- instance.method(args…)
- class.method(instance, args…)
class Child(Base):
def method(self,arg):
Base.method(self,arg)
運算符重載(python3)
- _ init_ 構造函數
- _ del_ 析構函數
- _ add_,_ radd_,_ iadd_ 運算符+,+=
- _ or_ 運算符|(位OR)
_ repr_,_ str_ 打印,轉換
- _ str_用於str內置函數和print,它通常返回一個用戶友好的顯示
- _ repr_用於所有其他環境中:用於交互模式下提示迴應和repr函數
- 交互模式下,只使用_ repr_,並不嘗試_ str_
- _ repr_和_ str_都必須返回字符串,其他的結果類型不會轉換並會引發錯誤
_ str_只對打印操作頂層纔有用,嵌套到其他對象中的對象用_ repr_或默認方式打印
class Printer: def __init__(self, val): self.val = val def __str__(self): return str(self.val) >>> objs = [Printer(2), Printer(3)] >>> for x in objs: print(x) 2 3 >>> print(objs) [<__main__.Printer object at 0x025D06f0>, <__main__.Printer object at ...
爲了確保一個定製顯示在所有的環境下都顯示而不管容器是什麼,請編寫_ repr_,而不是_ str_
class Printer: def __init__(self, val): self.val = val def __repr__(self): return str(self.val) >>> objs = [Printer(2), Printer(3)] >>> for x in objs: print(x) 2 3 >>> print(objs) [2, 3]
_ call_ 函數調用
_ getattr_ 點號運算 X.undefined
當通過對未定義(不存在)屬性名稱和實例進行點號運算時,就會用屬性名稱作爲字符串調用這個方法,如果通過其繼承樹搜索流程找到這個屬性,該方法就不會被調用。class X: def __init__(self): self.attr1 = 1 def __getattr__(self, attrname): print attrname >>> x = X() >>> x.attr1 1 >>> x.attr2 'attr2'
_ setattr_ 屬性賦值語句 e.g. X.any = value
該方法會攔截所有屬性的賦值語句,如果在該函數中對任何self屬性做賦值,就會再調用該函數,導致了無窮遞歸循環,所以得通過對屬性字典做索引運算來賦值任何實力屬性。- _ delattr_ 刪除屬性 e.g. del X.any
- _ getattribute_ 屬性獲取 X.any,訪問任何屬性(數據和函數)都會調用該函數,_ getattribute_的默認實現會在在屬性不存在時會拋出AttributeError,導致_ getattr_被調用
_ getitem_ 索引或分片運算,e.g. X[key],X[i:j]
class X: def __getitem__(self, index): print x >>> x = X() >>> x[2] 2 >>> x[0:10:2] slice(0, 10, 2)
_ setitem_ 索引或分片賦值語句,e.g. X[key]=value,X[i:j]=value
class X: def __setitem__(self, index, value): print index, value >>> x = X() >>> x[0] = 1 0 1
- _ delitem_ 索引和分片刪除,e.g. del X[key],del X[i:j]
- _ len_ 長度,e.g. len(X)
- _ bool_(_ nonzero_) 布爾測試,e.g. bool(X)
- _ lt_,_ gt_ ,_ le_,_ ge_,_ eq_,_ ne_比較,e.g. X< Y,X>Y
- _ radd_ 右側加法,e.g. other+X
- _ iadd_ 原地加法,e.g. X+=Y
- _ iter_,_ next_ 迭代,e.g. I=iter(X),next(I)
_ getitem_和_ iter_都支持迭代,在所有迭代環境中,Python首先嚐試使用_ iter_(它返回支持迭代協議的一個對象,該對象帶有一個_ next_方法,直到發生StopIteration異常),如果在繼承搜索中找不到_ iter_,Python就會使用_ getitem_索引方法,直到發生IndexError異常。 - _ contains_ 成員關係測試,e.g. item in X
_ index_ 整數值,e.g. hex(X),bin(X),oct(X)
class X: def __index__(self): return 255 >>> x = X() >>> hex(x) '0xff' >>> bin(x) '0b11111111' >>> oct(x) '0o377' >>> ('c'*256)[x] 'c'
_ enter_,_ exit_ 環境管理器,e.g. with obj as var:
- _ get_,_ set_ 描述符屬性
- _ delete_
- _ new_ 在_ init_之前創建對象
抽象超類
python 3.0
from abc import ABCMeta, abstractmethod
class Super(metaclass=ABCMeta):
@abstractmethod
def method(self, ...):
pass
python 2.6
class Super:
__metaclass__ = ABCMeta
@abstractmethod
def method(self, ...):
pass
新式類
- 在python3中所有類都是新式類,所有類都繼承自object,不管它們是否顯示地繼承object
- 在python2.6中,類必須繼承object(或者其他的內置類型)纔是新式類
新式類的類模式變化
#對於經典類來說,對用戶自定義類型和內置類型的處理是不一樣的
Class C:pass
>>> I = C()
>>> type(I)
<type 'instance'>
>>> I.__class__
<class __main__.C at 0x025085A0>
>>> type(C)
<type 'classobj'>
>>> C.__class__
AttributeError: class C has no attribute '__class__'
>> type([1,2,3])
<type 'list'>
>>> type(list)
<type 'type'>
>>> list.__class__
<type 'type'>
對新式類來說,用戶自定義類型和內置類型是一樣的
class C(object): pass
>>> I = C()
>>> type(I)
<class '__main__.C'>
>>> I.__class__
<class '__main__.C'>
>>> type(C)
<type 'type'>
>>> C.__class__
<type 'type'>
>> type([1,2,3])
<type 'list'>
>>> type(list)
<type 'type'>
>>> list.__class__
<type 'type'>
類的僞私有屬性
class語句內開頭有兩個下劃線,但結尾沒有兩個下劃線的變量名(無論是類還是實例屬性,無論是方法還是數據),會自動擴張,從而包含了所在類的名稱:原始的變量名會在頭部加入一個下劃線,然後是所在類名稱
class X:
def __init(self):
self.__a = 1
>>> x = X()
>>> x.__dict__
{'_X__a':1}
新式類的繼承搜索順序
屬性搜索處理沿着樹層級,以更加廣度優先的方式進行
slots(新式類)
在class語句頂層內將字符串名稱順序賦值給變量_ slots_:只有_ slots_列表內的這些變量名可以賦值爲實例屬性,使用了slots,實例通常沒有一個屬性字典。通過在_ slots_中包含_ dict_仍然可以容納額外的屬性
class X(object):
__slots__ = ['a','b','__dict__']
c = 1
def __init__(self):
self.a = 1
self.d = 1
>>> x = X()
>>> x.__slots__
['a','b','__dict__']
>>> x.__dict__
{'d':1}
類特性(新式類)
(新式類的)特性是一種可以替代_ getattr_和_ setattr_的方法
class X(object):
def getage(self):
return 40
def setage(self, value):
print 'set age:', value
self._age = value
age = property(getage, setage, None, None)
>>> x = X()
>>> x.age
40
>>> x.age = 42
set age: 42
>>> x._age
42
>>> x.job = 'trainer'
>>> x.job
'trainer'
無self“靜態”方法
- python2.6類方法總是要求傳入一個實例,不管是通過一個實例還是類都無法調用無self方法
- python3只能通過類調用而無法通過實例調用
靜態方法和類方法
class X:
def sf():
print 'static method'
def cf(cls):
print 'class method', cls
sf = staticmethod(sf)
cf = classmethod(cf)
>>> x = X()
>>> x.sf()
'static method'
>>> x.cf()
'class method' <class '__main__.X'>
>>> X.sf()
'static method'
>>> X.cf()
'class method' <class '__main__.X'>
函數裝飾器
函數裝飾器把一個函數當作參數並且返回一個可調用對象
class tracer:
def __init__(self, func):
self.func = func
def __call__(self, *args):
print 'call tracer'
self.func(*args)
@tracer
def f(a,b,c):
print a,b,c
>>> f(1,2,3)
'call tracer'
1 2 3
類裝飾器
def addcount(cls):
cls.count = 0
return cls
@addcount
class A:pass
@addcount
class B:pass
>>> A.count
0
>>> B.count
0
元類
class Meta(type):
def __new__(meta, classname, supers, classdict):...
class C(metaclass=Meta):...
元類通過重新定義type類的_new_或_init_方法,以實現對一個新的類對象的創建和初始化的控制。
管理屬性
- _getattr_和_setattr_方法,把未定義和屬性獲取和所有的屬性賦值指向通用的處理器方法。
- _getattribute_方法,把所有屬性獲取都指向新式類的該泛型處理器方法
property內置函數,把特定屬性訪問定位到get和set處理器函數,也叫做特性
常規寫法
attribute = property(fget, fset, fdel, doc)
裝飾器寫法
class Person: def __init__(self, name): self._name=name @property def name(self): "name property docs" print('fetch...') return self._name @name.setter def name(self, value): print('change...') self._name=value @name.deleter def name(self): print('remove...') del self._name
描述符協議,把特定屬性訪問定位到具有任意get和set處理器方法的類的實例
class Descriptor: "docstring goes here" def __get__(self, instanc, ower): ... def __set__(self, instance, value): ... def __delete(self, instance): ... class Subject(): attr=Descriptor() e.g. class Descriptor(object): def ____get____(self, instance, owner): print(self, instance, owner, sep='\n') class Subject: attr=Descriptor() x=Subject() x.attr >>><____main____.Descriptor object at xxx> >>><____main____.Subject object at xxx> >>><class '____main____'.Subject> Subject.attr >>><____main____.Descriptor object at xxx> >>>None >>><class '____main____.Subject'>
裝飾器
- 函數裝飾器
def decorator(F):
#process function F
return F
@decorator
def func():...
def decorator(F):
#save or use function F
return G
@decorator
def func():...
class decorator:
def __init__(self, func):
self.func = func
def __call__(self, *args):
#use self.func and args
@decorator
def func(x, y):...
基於類的裝飾器無法用於類方法
e.g.
class decorator:
def __init__(self, func):
self.func = func
def __call__(self, *args):
#self.func(*args) fails! C instance not in args!
class C:
@decorator
def method(self, x, y): ...
- 類裝飾器
異常
try語句分句形式
- except: 捕獲所有異常
- except name: 只捕獲特定的異常
- except name,value: 捕獲所列的異常和其額外的數據(或實例)
except name as value: 在python3中改成這種寫法 - except (name1, name2): 捕獲任何列出的異常
- except (name1, name2), value: 捕獲任何列出的異常,並取得其額外的數據
- else: 如果沒有引發異常,就運行
- finally: 總是會運行此代碼塊
- 即使except和else塊內引發了新的異常,finally塊的代碼仍然會執行
- 如果出現一個else的話,必須有至少一個except
- raise
- rasie < instance >
- raise < class >
- raise 重新拋出最近的異常
- raise exception from otherexception(python3)第二個異常會附加到第一個異常的_ cause_屬性
- 在except內部引發一個新異常的時候,前一個異常附加新異常的_ context_屬性
- assert < test >, < data >,如果使用-O命令行標誌位就會關閉assert
環境管理器
- 必須有_ enter_和_ exit_方法
- 如果as子句存在,其_ enter_返回值會賦值給as子句的變量,否則,直接丟棄
- 如果with代碼塊引發異常,_ exit_(type, value, traceback)方法會被調用,如果此方法返回值爲假,則異常會重新引發,否則異常會終止
- 如果with代碼塊沒有引發異常,_ exit_方法仍然會被調用,其type,value和traceback都會以None傳遞
參考自《Python學習手冊》