1. 程式人生 > >python學習要點(一)

python學習要點(一)

![](https://img.luozhiyun.com/blog0145e15e86ed8aa80120a89500a19f.jpg@1280w_1l_2o_100sh.jpg) 我的個人部落格排版更舒服: https://www.luozhiyun.com/archives/264 ## 列表和元組 * 列表是動態的,長度大小不固定,可以隨意地增加、刪減或者改變元素(mutable)。 * 而元組是靜態的,長度大小固定,無法增加刪減或者改(immutable)。 如果你想對已有的元組做任何"改變”,那就只能重新開闢一塊記憶體,建立新的元組了。 如下: ``` tup = (1, 2, 3, 4) new_tup = tup + (5, ) # 建立新的元組new_tup,並依次填充原元組的值 new _tup (1, 2, 3, 4, 5) l = [1, 2, 3, 4] l.append(5) # 新增元素5到原列表的末尾 l [1, 2, 3, 4, 5] ``` ### 列表和元組儲存方式的差異 由於列表是動態的,所以它需要儲存指標,來指向對應的元素。另外,由於列表可變,所以需要額外儲存已經分配的長度大小,這樣才可以即使擴容。 ```py l = [] l.__sizeof__() // 空列表的儲存空間為40位元組 40 l.append(1) l.__sizeof__() 72 // 加⼊了元素1之後,列表為其分配了可以儲存4個元素的空間 (72 - 40)/8 = 4 l.append(2) l.__sizeof__() 72 // 由於之前分配了空間,所以加⼊元素2,列表空間不變 l.append(3) l.__sizeof__() 72 // 同上 l.append(4) l.__sizeof__() 72 // 同上 l.append(5) l.__sizeof__() 104 // 加⼊元素5之後,列表的空間不⾜,所以⼜額外分配了可以儲存4個元素的空間 ``` 但是對於元組,情況就不同了。元組長度大小固定,元素不可變,所以儲存空間固定。 ### 列表和元組的效能 元組要比列表更加輕量級一些,所以總體上來 說,元組的效能速度要略優於列表。 Python會在後臺,對靜態資料做一些資源快取資源快取(resource caching)。通常來說,因為垃圾回收機制 的存在,如果一些變數不被使用了,Python就會回收它們所佔用的記憶體,返還給作業系統,以便其他變數 或其他應用使用。 但是對於一些靜態變數,比如元組,如果它不被使用並且佔用空間不大時,Python會暫時快取這部分記憶體。 由下面例子元組的初始化速 度,要比列表快5倍。 ``` python3 -m timeit 'x=(1,2,3,4,5,6)' 20000000 loops, best of 5: 9.97 nsec per loop python3 -m timeit 'x=[1,2,3,4,5,6]' 5000000 loops, best of 5: 50.1 nsec per loop ``` ## 字典和集合 集合和字典基本相同,唯一的區別,就是集合沒有鍵和值的配對,是一系列無序的、唯一的元素組合。 如何訪問、使用就不說了,說兩個注意點: 1. Python 中字典和集合,無論是鍵還是值,都可以是混合型別 ``` s = {1, 'hello', 5.0} ``` 2. 字典訪問可以直接索引鍵,如果不存在,就會丟擲異常;也可以使用 get(key, default) 函式來進行索引。如果鍵不存在,呼叫 get() 函式可以返回一個預設值。 ``` d = {'name': 'jason', 'age': 20} d['name'] 'jason' d['location'] Traceback (most recent call last): File "", line 1, in KeyError: 'location' d = {'name': 'jason', 'age': 20} d.get('name') 'jason' d.get('location', 'null') 'null ``` ### 字典和集合的工作原理 字典和集合的內部結構都是一張雜湊表。 * 對於字典而言,這張表儲存了雜湊值(hash)、鍵和值這 3 個元素。 * 而對集合來說,區別就是雜湊表內沒有鍵和值的配對,只有單一的元素了。 老版本 Python 的雜湊表結構如下所示: ``` --+-------------------------------+ | 雜湊值 (hash) 鍵 (key) 值 (value) --+-------------------------------+ 0 | hash0 key0 value0 --+-------------------------------+ 1 | hash1 key1 value1 --+-------------------------------+ 2 | hash2 key2 value2 --+-------------------------------+ . | ... __+_______________________________+ ``` 隨著雜湊表的擴張,它會變得越來越稀疏。舉個例子: ``` {'name': 'mike', 'dob': '1999-01-01', 'gender': 'male'} ``` 那麼它會儲存為類似下面的形式: ``` entries = [ ['--', '--', '--'] [-230273521, 'dob', '1999-01-01'], ['--', '--', '--'], ['--', '--', '--'], [1231236123, 'name', 'mike'], ['--', '--', '--'], [9371539127, 'gender', 'male'] ] ``` 這樣的設計結構顯然非常浪費儲存空間。 為了提高儲存空間的利用率,現在的雜湊表除了字典本身的結構,會把索引和雜湊值、鍵、值單獨分開: ``` Indices ---------------------------------------------------- None | index | None | None | index | None | index ... ---------------------------------------------------- Entries -------------------- hash0 key0 value0 --------------------- hash1 key1 value1 --------------------- hash2 key2 value2 --------------------- ... --------------------- ``` 在新的雜湊表結構下的儲存形式,上面的例子就是這樣: ``` indices = [None, 1, None, None, 0, None, 2] entries = [ [1231236123, 'name', 'mike'], [-230273521, 'dob', '1999-01-01'], [9371539127, 'gender', 'male'] ] ``` ### 插入操作 每次向字典或集合插入一個元素時,Python 會首先計算鍵的雜湊值(hash(key)),再和 mask = PyDicMinSize - 1 做與操作,計算這個元素應該插入雜湊表的位置 index = hash(key) & mask。 如果雜湊表中此位置是空的,那麼這個元素就會被插入其中。而如果此位置已被佔用,Python 便會比較兩個元素的雜湊值和鍵是否相等。 若兩者中有一個不相等,這種情況我們通常稱為雜湊衝突(hash collision),意思是兩個元素的鍵不相等,但是雜湊值相等。這種情況下,Python 便會繼續尋找表中空餘的位置,直到找到位置為止。 ### 刪除操作 對於刪除操作,Python 會暫時對這個位置的元素,賦於一個特殊的值,等到重新調整雜湊表的大小時,再將其刪除。 為了保證其高效性,字典和集合內的雜湊表,通常會保證其至少留有 1/3 的剩餘空間。隨著元素的不停插入,當剩餘空間小於 1/3 時,Python 會重新獲取更大的記憶體空間,擴充雜湊表。 ## 函式變數作用域 ### 區域性變數 如果變數是在函式內部定義的,就稱為區域性變數,只在函式內部有效。一旦函式執行完畢,區域性變數就會被回收,無法訪問。 對於巢狀函式來說,內部函式可以訪問外部函式定義的變數,但是無法修改,若要修改,必須加上 nonlocal 這個關鍵字: ``` def outer(): x = "local" def inner(): nonlocal x # nonlocal 關鍵字表示這裡的 x 就是外部函式 outer 定義的變數 x x = 'nonlocal' print("inner:", x) inner() print("outer:", x) outer() # 輸出 inner: nonlocal outer: nonlocal ``` 如果不加上 nonlocal 這個關鍵字,而內部函式的變數又和外部函式變數同名,那麼同樣的,內部函式變數會覆蓋外部函式的變數。 ``` def outer(): x = "local" def inner(): x = 'nonlocal' # 這裡的 x 是 inner 這個函式的區域性變數 print("inner:", x) inner() print("outer:", x) outer() # 輸出 inner: nonlocal outer: local ``` ### 全域性變數 全域性變數則是定義在整個檔案層次上的,可以在檔案內的任何地方被訪問,但是不能在函式內部隨意改變全域性變數的值。 例如: ``` MIN_VALUE = 1 MAX_VALUE = 10 def validation_check(value): ... MIN_VALUE += 1 ... validation_check(5) #輸出 UnboundLocalError: local variable 'MIN_VALUE' referenced before assignment ``` 因為,Python 的直譯器會預設函式內部的變數為區域性變數,但是又發現區域性變數 MIN_VALUE 並沒有宣告,因此就無法執行相關操作。 如果我們一定要在函式內部改變全域性變數的值,就必須加上 global 這個宣告: ``` MIN_VALUE = 1 MAX_VALUE = 10 def validation_check(value): global MIN_VALUE ... MIN_VALUE += 1 ... validation_check(5) ``` 如果遇到函式內部區域性變數和全域性變數同名的情況,那麼在函式內部,區域性變數會覆蓋全域性變數,比如下面這種: ``` MIN_VALUE = 1 MAX_VALUE = 10 def validation_check(value): MIN_VALUE = 3 ... ``` ## 閉包 閉包中外部函式返回的是一個函式,返回的函式通常賦於一個變數,這個變數可以在後面被繼續執行呼叫。 比如,我們想計算一個數的 n 次冪,用閉包可以寫成下面的程式碼: ``` def nth_power(exponent): def exponent_of(base): return base ** exponent return exponent_of # 返回值是 exponent_of 函式 square = nth_power(2) # 計算一個數的平方 cube = nth_power(3) # 計算一個數的立方 square # 輸出 .exponent(base)> cube # 輸出 .exponent(base)> print(square(2)) # 計算 2 的平方 print(cube(2)) # 計算 2 的立方 # 輸出 4 # 2^2 8 # 2^3 ``` 這裡外部函式 nth_power() 返回值,是函式 exponent_of(),而不是一個具體的數值。 ## 面對物件 ### 函式 靜態函式:與類沒有什麼關聯可以用來做一些簡單獨立的任務,既方便測試,也能優化程式碼結構。靜態函式可以通過在函式前一行加上 @staticmethod 來表示。 類函式:第一個引數一般為 cls,表示必須傳一個類進來。類函式最常用的功能是實現不同的 **init** 建構函式,類似java中的構造器。類函式需要裝飾器 @classmethod 來宣告。 成員函式:是我們最正常的類的函式,它不需要任何裝飾器宣告,第一個引數 self 代表當前物件的引用,可以通過此函式,來實現想要的查詢 / 修改類的屬性等功能。 例子如下: ```python class Document(): WELCOME_STR = 'Welcome! The context for this book is {}.' def __init__(self, title, author, context): print('init function called') self.title = title self.author = author self.__context = context # 類函式 @classmethod def create_empty_book(cls, title, author): return cls(title=title, author=author, context='nothing') # 成員函式 def get_context_length(self): return len(self.__context) # 靜態函式 @staticmethod def get_welcome(context): return Document.WELCOME_STR.format(context) empty_book = Document.create_empty_book('What Every Man Thinks About Apart from Sex', 'Professor Sheridan Simove') print(empty_book.get_context_length()) print(empty_book.get_welcome('indeed nothing')) ########## 輸出 ########## init function called 7 Welcome! The context for this book is indeed nothing. ``` ### 繼承 ```python class Entity(): def __init__(self, object_type): print('parent class init called') self.object_type = object_type def get_context_length(self): raise Exception('get_context_length not implemented') def print_title(self): print(self.title) class Document(Entity): def __init__(self, title, author, context): print('Document class init called') Entity.__init__(self, 'document') self.title = title self.author = author self.__context = context def get_context_length(self): return len(self.__context) class Video(Entity): def __init__(self, title, author, video_length): print('Video class init called') Entity.__init__(self, 'video') self.title = title self.author = author self.__video_length = video_length def get_context_length(self): return self.__video_length harry_potter_book = Document('Harry Potter(Book)', 'J. K. Rowling', '... Forever Do not believe any thing is capable of thinking independently ...') harry_potter_movie = Video('Harry Potter(Movie)', 'J. K. Rowling', 120) print(harry_potter_book.object_type) print(harry_potter_movie.object_type) harry_potter_book.print_title() harry_potter_movie.print_title() print(harry_potter_book.get_context_length()) print(harry_potter_movie.get_context_length()) ########## 輸出 ########## Document class init called parent class init called Video class init called parent class init called document video Harry Potter(Book) Harry Potter(Movie) 77 120 ``` 我們可以從中抽象出一個叫做 Entity 的類,來作為Document 和 Video的父類。 每個類都有建構函式,繼承類在生成物件的時候,是不會自動呼叫父類的建構函式的,因此你必須在 init() 函式中顯式呼叫父類的建構函式。它們的執行順序是 子類的建構函式 -> 父類的建構函式。 由於父類的get_context_length方法是用來被重寫的,所以使用 Entity 直接生成物件,呼叫 get_context_length() 函式,就會 raise error 中斷程式的執行。 繼承的優勢:減少重複的程式碼,降低系統的熵值(即複雜度)。 ### 抽象類 抽象類是一種特殊的類,它生下來就是作為父類存在的,一旦物件化就會報錯。同樣,抽象函式定義在抽象類之中,子類必須重寫該函式才能使用。相應的抽象函式,則是使用裝飾器 @abstractmethod 來表示。 抽象類就是這麼一種存在,它是一種自上而下的設計風範,你只需要用少量的程式碼描述清楚要做的事情,定義好介面,然後就可以交給不同開發人員去開發和對接。 ```python from abc import ABCMeta, abstractmethod class Entity(metaclass=ABCMeta): @abstractmethod def get_title(self): pass @abstractmethod def set_title(self, title): pass class Document(Entity): def get_title(self): return self.title def set_title(self, title): self.title = title document = Document() document.set_title('Harry Potter') print(document.get_title()) entity = Entity() ########## 輸出 ########## Harry Potter --------------------------------------------------------------------------- TypeError Traceback (most recent call last) in () 21 print(document.get_title()) 22 ---> 23 entity = Entity() 24 entity.set_title('Test') TypeError: Can't instantiate abstract class Entity with abstract methods get_title, set_title ``` 程式碼中entity = Entity()直接報錯,只有通過 Document 繼承 Entity 才能正常