python学习笔记_序
說明
這里的python筆記也是之前留下的,看了下時間,大概是今年寒假期間學的,主要是跟著廖大的教程來。也是半途而廢的計劃啊(干了多少半途而廢的事情)。
教程地址:Python教程--廖雪峰
數據類型
還沒學之前就有所耳聞,Python是動態語言,不需要定義類型名的,語法上已經將這一塊封裝好了。
除了整形、浮點型和字符串外,Python也有布爾值(有特殊的運算符and\or),“常量”(Python的常量其實是一個變量)。但最大的不同點是,Python有獨特的“列表”和“字典”類型。
列表:分為list(列表)和tuple(元組),list是可變的,而tuple是不可變的。list有點像一個數組但是內部保存的元素可以是多種且不同的數據類型。tuple也很類似,但是它內部的元素必須確定,即不可改變。
有一個比較容易搞混的例子:t = ('a', 'b', ['A', 'B']),它是正確的。在tuple的指向里,三個元素的位置是確定不變的;在list中,它儲存元素的空間是可以更換不同的元素的。
dict:當看到dict的特性時,我立馬想到數據結構里的哈希表,查了一下確實是如此。那就很好理解了,key值是索引哈希值所在位置的鑰匙。另一個方面,dict的特性就像查字典那樣,時間永遠是常量級的,典型的空間換時間思想。一個很重要的地方時,key必須是一個不可變的數據類型,也就是說list是不行的,某種情況下的tuple也是不行的(上面的例子)。
set:從詞義上理解就很清晰了,就是一個集合。事實上它所儲存的元素是不可重復的。當然,dict的特性也保證了key所對應的value也是不可重復的,要不然查找效率就不是常量級了。
函數
函數定義
函數沒有太大的變化,記錄一下之前沒見過的。
isinstance():檢查參數類型是否合格。
返回多個值:C/C++是不允許返回多個值的,如果實在需要,只能用數組指針。Python之所以可以返回多個值,其實是tuple的作用。
import XX:類似于頭文件
函數參數
Python的參數有點復雜,它不需要定義參數是哪種數據類型(一開始學真的很不習慣,被C虐慣了),但可以根據參數的作用來區分參數類型,一共有5類:必選、默認、可變、關鍵詞、命名關鍵詞。并且參數的先后順序要嚴格按照這個來,否則會產生歧義。如調用fun(a, b = 1, c)傳入(1, 2)時,我們不知道應該是報錯還是按照調用(1, 1, 2)來。
必選:也叫位置參數,調用時必須有一個值占位
默認:提前設置好參數的值,如果調用時沒有值占位,那么就使用默認值
可變:傳入的參數個數是可變的,在參數前加一個*,看起來有點像指針,函數內部接收到的其實是一個tuple,因此代碼不會變化
關鍵字:**kw,允許傳入任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝成一個dict。
命名關鍵字:限制關鍵字參數名字,需要用到特殊分隔符 * , * 后面的參數被視為命名關鍵字;如果函數中有可變參數,就不需要再使用分隔符了
對于任意函數,都可以通過類似func(*args, **kw)的形式調用它,無論它的參數是如何定義的。
大神的小結:
Python的函數具有非常靈活的參數形態,既可以實現簡單的調用,又可以傳入非常復雜的參數。
默認參數一定要用不可變對象,如果是可變對象,程序運行時會有邏輯錯誤!
要注意定義可變參數和關鍵字參數的語法:
args是可變參數,args接收的是一個tuple;
kw是關鍵字參數,kw接收的是一個dict。
以及調用函數時如何傳入可變參數和關鍵字參數的語法:
可變參數既可以直接傳入:func(1, 2, 3),又可以先組裝list或tuple,再通過args傳入:func((1, 2, 3));
關鍵字參數既可以直接傳入:func(a=1, b=2),又可以先組裝dict,再通過 * * kw傳入:func(* * {'a': 1, 'b': 2})。
使用* args和kw是Python的習慣寫法,當然也可以用其他參數名,但最好使用習慣用法。
命名的關鍵字參數是為了限制調用者可以傳入的參數名,同時可以提供默認值。
定義命名的關鍵字參數在沒有可變參數的情況下不要忘了寫分隔符,否則定義的將是位置參數。
函數遞歸
講到一個尾遞歸的東西,可總結為:返回值只能是函數自身而不能是一個表達式
尾遞歸很容易改成循環的形式,但是很多編輯器沒有做到。印象中C好像是做到了?
高級特性
在Python中,代碼不是越多越好,而是越少越好。代碼不是越復雜越好,而是越簡單越好。
切片
用法是[x:x],提取list或tuple里這一部分的數據,十分方便
迭代
語法是 for ... in ...,不僅可以迭代list或tuple,還可以操作dict
dict內部元素儲存是無序的,所以迭代的結果可能會和儲存的順序不一樣。另外,dict迭代默認情況下是key,可用for value in d.value() 迭代value,還可以for k, v in d.item() 來同時迭代key和value。
字符串迭代,要實現同時索引對和元素對的迭代,可以使用內置的enumerate()函數。
判斷是否為可迭代對象,通過collections的Iterable類型來判斷。
列表生成式
格式:[元素或表達式 , 循環式]
循環式還可以用兩層循環。
運用列表生成式,可以寫出非常簡潔的代碼。例如,列出當前目錄下的所有文件和目錄名,可以通過一行代碼實現:
確實不愧為“人生苦短,我用python”,腦海中想著如果要用C的話,先不論要設多少個邊界條件,關鍵是也不知道怎么寫啊……
生成器generator
生成器可以看成是一個集成了算法的列表。
要比較列表生成式和生成器的差別,可以看一段代碼:
因而生成器是要通過一邊循環一遍計算得到所有的結果的。
另外,如果在一個函數里添加了yield X,函數就變成了生成器。而yield的作用是輸出當前生成器計算所得的X的結果。
可迭代對象和迭代器
可迭代對象 Iterable:可以直接作用于for循環的對象,包括集合數據類型和generator
迭代器 Iterator:可以被next()函數調用并不斷下一個值的對象。
可以isinstance()函數來區分可迭代對象和迭代器。
函數式編程
說實話沒看明白,不過上網搜索了一下,函數式和命令式同屬于面向過程的思維。函數式編程式接近數學計算。只能明白這么多了。
高階函數 Higher-order function
總結起來就是大神說的那么幾句話:變量可以指向函數、函數名也是變量、函數可以作為變量傳參。
雖然很多人都說學新語言就要放下固有語言的成見,但是我還是不由自主地對比起C/C++與python在這一點的不同。變量指向函數可以通過函數指針解決,但應該不是一個層次的東西。python的核心是為了實現函數作為參數傳遞的機制,這是高階函數的特征。而C/C++并無意做到這一點,它們無法忍受未知的事物作為參數,而一個函數在未被調用之前,很明顯會被看作未知事物。
幾個高階函數
map: 接受兩個參數,第一個函數對象, 一個Interable。map將的函數作用到序列的每個元素,并把結果作為新的Interable。
reduce: 把一個函數作用在一個序列上,這個函數必須接受兩個參數,reduce把結果繼續和序列的下一個元素累積計算
filter: 用于過濾序列。返回值決定保留還是丟棄該元素
sorted: 排序函數,可以接受一個key函數來實現自定義排序
返回函數
即將函數作為結果值返回。這一部分著實有點難以理解,換個名字我就嚇了一跳,它就是赫赫有名的“閉包”。
舉教程的例子:
def lazy_sum(*args):def sum():ax = 0for n in args:ax = ax + nreturn axreturn sumf = lazy_sum(1, 3, 5, 7, 9)當我們調用lazy_sum() 時,返回的是求和函數。只有調用函數f 時,才能計算真正求和的結果。
需要注意的兩個地方是:
匿名函數
關鍵詞lambda,就是一個表達式,表達式的值就是返回值。可以把匿名函數賦給一個變量,也可以把它作為返回值返回
裝飾器 decorator
假如要增強某一個函數的功能,比如說在函數調用前后打印日志,但又不希望改變原函數的定義,可以使用裝飾器在代碼運行階段動態增強功能。
本質上裝飾器就是一個返回函數的高階函數,如:
def log(func):def wrapper(*args, **kw):print('call %s():' % func.__name__)return func(*args, **kw)return wrapper@log def now():print('2015-3-25')輸出結果: >>> now() call now(): 2015-3-25實質上是實現了now = log(now) 的功能。
偏函數
利用functools.partial 幫助創建一個偏函數,這個可以固定某些參數,返回一個新函數,調用這個新函數會更簡單。
創建偏函數時,實際上可以接受函數對象、* args、**kw這三個參數的傳入。
模塊
在python文件中,一個py文件就是一個模塊。使用模塊可以提高代碼的可維護性,其次方便其它地方的引用。python有內置的模塊和來自第三方的模塊。使用模塊還可以避免函數名和變量名沖突。相同名字的函數和變量完全可以分別存在不同的模塊中。但是我們自己命名的函數盡量不要與內置函數名沖突。
為了避免同樣的模塊名沖突,python引進了按目錄來組織模塊的方法,稱為包。引入了包以后,只要頂層的包名不與別人的沖突,那所有模塊都不會與別人沖突。每一個包目錄下面都會有一個_init_.py的文件,否則python會把這個目錄當成普通目錄。
使用模塊
python的函數和變量可以通過前綴_來改變作用域。正常的變量和函數是公開的,即public,可以直接訪問,;而類似_XXX_的變量是特殊變量也可以直接引用,但是有特殊用途,比如_name_;類似_XXX和__XXX這樣的函數就是非公開的,即private。
雖然如此,但是在python中其實并沒有一種方法可以完全限制住private變量或函數的訪問,只是從編程習慣上我們不應該引用它。
面向對象
類和實例
和C++很像,定義類也是通過class關鍵字。class后緊跟著類名,緊接著是(object),表示這個類是從哪個類繼承下來的。
創建實例是用類+()實現的。可以自由地給一個實例變量綁定一些屬性。
由于類可以起到模板的作用,因此可以通過定義一個特殊的__init__方法,在創建實例的時候,把我們認為必須強制綁定的屬性綁上去。__init__方法的第一個參數是self,表示創建的實例本身。
可以將函數定義在類里,實現數據封裝。
和靜態語言不同(如C++),Python允許對實例變量綁定任何數據,也就是說,對于兩個實例變量,雖然它們都是同一個類的不同實例,但擁有的變量名稱都可能不同。
訪問限制
如果要讓內部屬性不被外部訪問,可以把屬性的名稱前面加上兩個下劃線__。在python中,實例的變量名如果加上了兩個下劃線,就變成了一個私有變量。
其實python的私有變量是沒有得到像C++那樣的機制保護的。事實上,之所以不能直接訪問私有變量是因為解釋器對外會把__XXX改成_Object__XXX,所以仍然可以通過后者來訪問這個私有變量。
繼承和多態
這一小節對面對對象編程來說是重點。剛學C++時,繼承還好理解,多態就有些費解了。正好現在再看看python的繼承和多態,兩相對比來理解。
理解繼承的時候,一定要想象一棵樹(最好是數據結構的那種樹),根結點是所有子孫結點的父輩結點。同樣的,一棵繼承樹的根類是下面所有結點的父類,它們共同繼承了這個父類的全部功能。
理解多態時,我是對比重載和多態的相同和不同之處。和C++不同的是,python是不支持函數名重載的,內部對比無從說起。但是在類的繼承中,相同的函數名子類可以覆蓋父類。在引用子類的實例時,實例的函數是子類所覆蓋的那個函數。
看一個例子來理解會好一點:
因為繼承,所以所有的子類都可以作為animal參數;又因為多態,所以在引用參數的內部函數時其實是引用了覆蓋了的函數。
廖老師的總結:
這就是多態真正的威力:調用方只管調用,不管細節,而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的“開閉”原則:
1.對擴展開放:允許新增Animal子類;
2.對修改封閉:不需要修改依賴Animal類型的run_twice()等函數。
最后是靜態語言和動態語言在繼承上的不同。靜態語言,以C++為例,像上面那個例子,傳入的參數必須只能是animal或它的子類;而動態語言,如python,只要那個實例有run()方法,那就可以傳入,亦被稱為“鴨子類型”。
獲取對象信息
當我們拿到一個對象的參數時,可以使用type() isinstance() dir() 來知道這個對象是什么類型,有哪些方法。
type()
基本類型、指向函數或類的變量都可以用type()判斷,返回的是對應的Class()類型。
如果要判斷一個對象是否為函數,可以使用types模塊中定義的常量。
isinstance()
可用于判斷class的類型,告訴我們一個對象是否是某一個類型。
能用type()判斷的都可以用isinstance()判斷,而后者還可以判斷一個變量是否是某些類型中的一種。如:
dir()
用于獲得一個對象的所有屬性和方法,返回值是一個包含字符串的list。
類似__xxx__的屬性和方法在Python中都是有特殊用途的,比如__len__方法返回長度。在Python中,如果你調用len()函數試圖獲取一個對象的長度,實際上,在len()函數內部,它自動去調用該對象的__len__()方法,所以,下面的代碼是等價的:
我們自己寫的類,如果也想用len(myObj)的話,就自己寫一個__len__()方法:
>>> class MyDog(object): ... def __len__(self): ... return 100 ... >>> dog = MyDog() >>> len(dog) 100僅僅把屬性和方法列出來是不夠的,配合getattr() setattr() hasattr,我們可以直接操作一個對象的狀態。
用這些函數,可以測試對象的屬性或方法。如果試圖獲取不存在的屬性,會拋出AttributeError的錯誤;可以傳入一個默認參數,如果屬性不存在,就返回默認值。
實例屬性和類屬性
看下面這段測試程序:
>>> class Student(object): ... name = 'Student' ... >>> s = Student() # 創建實例s >>> print(s.name) # 打印name屬性,因為實例并沒有name屬性,所以會繼續查找class的name屬性 Student >>> print(Student.name) # 打印類的name屬性 Student >>> s.name = 'Michael' # 給實例綁定name屬性 >>> print(s.name) # 由于實例屬性優先級比類屬性高,因此,它會屏蔽掉類的name屬性 Michael >>> print(Student.name) # 但是類屬性并未消失,用Student.name仍然可以訪問 Student >>> del s.name # 如果刪除實例的name屬性 >>> print(s.name) # 再次調用s.name,由于實例的name屬性沒有找到,類的name屬性就顯示出來了 Student類屬性可以用作一個類所有對象的全局變量,在統計一些數據的時候特別有用。
面向對象高級編程
在C++中,之前那些已經算是基礎語法所學的全部了。在python中,面向對象還有很多高級特性。
使用__slots__
python中,動態綁定允許我們在程序運行的過程中動態給實例或class加上屬性和方法。但是,如果我們想要限制實例的屬性,可以定義一個特殊的變量__slots__,來限制該class實例能添加的屬性。
需要注意的是,__slots__定義的屬性只能對當前類起作用,對繼承的子類是不起作用的。但是,如果子類也定義了__slots__,子類允許定義的屬性就是自身的加上父類的。
使用@propert
在綁定屬性時,為了避免參數值出現不符合常理的情況,我們需要對它進行限制。一種方法是將類的變量類型設置為私有,利用類的方法來訪問;但是對于一些情況來說這種方式顯得有些麻煩。
要想達到既能檢查參數,又可以用類似屬性這樣簡單的方式來訪問類的變量的目的,可以使用python內置的@property裝飾器,將一個方法編程屬性來調用。如:
把一個getter方法變成屬性,只需要加上@property就可以了;此外,@property又創建了另一個裝飾器@score.setter,負責把一個setter方法變成屬性賦值。于是我們就有了一個可控的屬性操作。
>>> s = Student() >>> s.score = 60 # OK,實際轉化為s.set_score(60) >>> s.score # OK,實際轉化為s.get_score() 60 >>> s.score = 9999 Traceback (most recent call last):... ValueError: score must between 0 ~ 100!通過這個裝飾器,我們還可以只定義getter方法,實現只讀屬性。
多重繼承
應用到多重繼承的設計通常被稱為MixIn。MixIn的目的是給一個類增加多個功能,這樣在設計類的時候,我們優先考慮通過多重繼承來組合多個MixIn的功能。這樣一來,我們不需要復雜而龐大的繼承鏈,只要選擇組合不同的類的功能,就可以快速構造出所需的子類。
需要注意的是,只允許單一繼承的語言不能使用MixIn設計。
定制類
python的class還有許多有特殊作用的函數,可以幫助我們定制類。
__ str __
打印一個類的實例時,若是想要按照我們的想法打印,只需要定義__str__方法。如:
>>> class Student(object): ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return 'Student object (name: %s)' % self.name ... >>> print(Student('Michael')) Student object (name: Michael)但是若想直接敲變量不用print,這時候直接顯示變量調用的不是__str__(),而是__repr__()。兩者的區別是前者返回用戶看到的字符串,而后者返回程序開發者看到的字符串,即后者是為調試服務的。
解決的方法就是再定義一個__repr__(),可以直接用賦值語句將__str__()賦給它。
__ iter __
如果一個類想被用于for···in···循環,就必須實現__iter__()方法,該方法返回一個迭代對象,然后python的for循環就會不斷調用該迭代對象的__next__()方法拿到下一個循環值,知道遇到StopInteration錯誤退出循環。如:
class Fib(object):def __init__(self):self.a, self.b = 0, 1 # 初始化兩個計數器a, bdef __iter__(self):return self # 實例本身就是迭代對象,故返回自己def __next__(self):self.a, self.b = self.b, self.a + self.b # 計算下一個值if self.a > 100000: # 退出循環的條件return StopInteration()return self.a # 返回下一個值若將fib實例用于for循環:
>>> for n in Fib(): ··· print(n) ··· 1 1 2 3 5 ··· 46368 75025__ getitem __
Fib實例雖然能作用于for循環,看起來和list有點像,但是把它當作list來使用還是不行的。要表現得像list那樣按照下標取出元素,需實現__getitem__()方法。 ``` class Fib(object): def __getitem__(self, n): a, b = 1, 1 for x in range(n): a, b = b, a + b return a ``` 如果要實現list的切片方法,需要作判斷傳入的參數是一個int還是一個slice對象。如果把對象看成dict,getitem的參數也可能是一個可以作key的object,例如str。 與之對應的是setitem()方法,把對象視作list或dict來對集合賦值。最后還有一個delitem()`方法,用于刪除某個元素。
總之,通過上面的方法,我們自己定義的類表現得和python自帶的list、tuple和dict沒什么區別,這完全歸功于動態語言的“鴨子類型”,不需要強制繼承某個接口。
__ getattar __
正常情況下,當我們調用類的方法或屬性時,如果不存在就會報錯。寫一個__getattar__()方法可以動態返回一個屬性。
只有在沒有找到屬性的情況下,才調用__getattar__。此外如果調用如s.abc都會返回None,這是因為我們定義的__getattar__默認返回就是None。要讓class只響應特定的幾個屬性,我們就要按照約定,拋出AttributeError的錯誤。
這實際上可以把一個類的所有屬性和方法全部動態化處理,不需要任何特殊手段。這種完全動態調用的特性可以針對完全動態的情況作調用。
現在很多網站都搞REST API,比如新浪微博、豆瓣啥的,調用API的URL類似:
http://api.server/user/friends
http://api.server/user/timeline/list
如果要寫SDK,給每個URL對應的API都寫一個方法,那得累死,而且,API一旦改動,SDK也要改。
利用完全動態的__getattar__,可以寫一個鏈式調用。
還有些REST API會把參數放到URL中,比如GitHub的API:
GET /users/:user/repos調用時,需要把:user替換為實際用戶名:
Chain().users('michael').repos__ call __
一個對象實例可以有自己的屬性和方法,當我們調用實例方法時,我們用instance.method()來調用。如果要想在實例本身上調用,只需要定義一個__call__方法。
class Student(object):def __init__(self, name):self.name = namedef __call__(self):print('My name is %s.' % self.name)調用方法如下:
>>> s = Student('Michael') >>> s() # self參數不要傳入 My name is Michael.__call__()還可以定義參數。對實例進行直接調用就好比對一個函數進行調用一樣,所以你完全可以把對象看成一個函數,把函數看成對象。
但是如何判斷一個變量時對象還是函數呢?其實,更多的時候我們需要判斷的是一個對象是否能被調用,能被調用的對象就是一個Callable對象。通過callable()函數,我們就可以判斷一個對象是否是“可調用”對象。
使用枚舉類
當我們需要定義大量常量時,可以為這樣的枚舉類型定義一個class類型,然后,每個常量都是class的一個唯一實例。
from enum import EnumMonth = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))這樣我們就獲得了Month類型的枚舉類,可以直接使用Month.Jan來引用一個常量,或者枚舉它的所有成員。
for name, member in Month.__members__.items():print(name, '=>', member, ',', member.value)value屬性是自動賦給成員的int常量,默認從1開始計數。
如果需要更精確地控制枚舉類型,可以從Enum派生出自定義類:
from enum import Enum, unique@unique class Weekday(Enum):Sun = 0 # Sun的value被設定為0Mon = 1Tue = 2Wed = 3Thu = 4Fri = 5Sat = 6@unique裝飾器可以幫助我們檢查保證沒有重復。訪問這些枚舉類型可以有多種方式,既可以用成員名稱引用枚舉變量,又可以直接根據value的值獲得枚舉類型。
總結:Enum可以把一組相關常量定義在一個class中,且class不可變,而且成員可以直接比較。
使用元類
type()
動態語言和靜態語言最大的不同,就是函數和類的定義,不是編譯時定義的,而是運行時動態創建的。
比方說我們要定義一個Hello的class,就寫一個hello.py的模塊。
class Hello(object):def hello(self, name = 'world'):print('Hello, %s.', % name)當Python解釋器載入hello模塊時,就會依次執行該模塊的所有語句,執行結果就是動態創建除一個Hello的class對象,測試如下:
>>> from hello import Hello >>> h = Hello() >>> h.hello() Hello, world. >>> print(type(Hello)) <class 'type'> >>> print(type(h)) <class 'hello.Hello'>type()函數可以查看一個類型或變量的類型,Hello是一個class,它的類型就是type,而h是一個實例,它的類型就是class Hello。
我們說class的定義是運行時創建的,而創建class的方法就是使用type()函數。
type()函數既可以返回一個對象的類型,又可以創建新的類型,比如,我們可以通過type()函數創建出Hello類,而無需通過class Hello(Object)...的定義:
>>> def fn(self, name = 'world'): #先定義函數 ... print('Hello, %s' % name) ... >>> Hello = type('Hello', (Object), dict(hello=fn)) #創建Hello class >>> h = Hello() >>> h.Hello() Hello, world >>> print(type(Hello)) <class 'type'> >>> print(type(h)) <class '_main_.Hello'>要創建一個class對象,type()函數依次傳入3個參數:
通過type()函數創建的類和直接寫class是完全一樣的,因為Python解釋器遇到class定義時,僅僅是掃描一下class定義的語法,然后調用type()函數創建出class。
正常情況下,我們都用class Xxx...來定義類,但是,type()函數也允許我們動態創建出類來,也就是說,動態語言本身支持運行期動態創建類,這和靜態語言有非常大的不同,要在靜態語言運行期創建類,必須構造源代碼字符串再調用編譯器,或者借助一些工具生成字節碼實現,本質上都是動態編譯,會非常復雜。
metaclass 元類
除了使用type()動態創建類外,要控制類的創建行為,還可以使用metaclass。
當我們定義了類以后,就可以根據這個類創建出實例,所以:先定義類,然后創建實例。
但是如果我們想創建出類呢?那就必須根據metaclass創建出類,所以:先定義metaclass,然后創建類。
連接起來就是:先定義metaclass,就可以創建類,最后創建實例。
所以metaclass允許你創建類或修改類。
后面的……沒看懂,先不記了。
轉載于:https://www.cnblogs.com/ChanWunsam/p/10018246.html
總結
以上是生活随笔為你收集整理的python学习笔记_序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为游戏适配刘海屏
- 下一篇: win10系统同时安装python2和p