python學習要點(一)
阿新 • • 發佈:2020-04-05
![](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 才能正常