1. 程式人生 > >python學習筆記_序

python學習筆記_序

ini 新浪 方法名 enumerate use dog 正常的 多人 傳參

說明

這裏的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類型來判斷。

列表生成式

格式:[元素或表達式 , 循環式]
循環式還可以用兩層循環。
運用列表生成式,可以寫出非常簡潔的代碼。例如,列出當前目錄下的所有文件和目錄名,可以通過一行代碼實現:

>>> import os # 導入os模塊,模塊的概念後面講到
>>> [d for d in os.listdir(‘.‘)] # os.listdir可以列出文件和目錄

確實不愧為“人生苦短,我用python”,腦海中想著如果要用C的話,先不論要設多少個邊界條件,關鍵是也不知道怎麽寫啊……

生成器generator

生成器可以看成是一個集成了算法的列表。
要比較列表生成式和生成器的差別,可以看一段代碼:

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

因而生成器是要通過一邊循環一遍計算得到所有的結果的。
另外,如果在一個函數裏添加了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 = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

f = lazy_sum(1, 3, 5, 7, 9)

當我們調用lazy_sum() 時,返回的是求和函數。只有調用函數f 時,才能計算真正求和的結果。

需要註意的兩個地方是:

  1. 當我們調用lazy_sum() 時,每次調用都會返回一個新的函數,即使傳入相同的參數,兩次調用也是不等價的。
  2. 當一個函數返回了一個函數後,其內部的局部變量還被新函數引用。這時,如果返回函數的返回值使用了循環變量,那麽多次調用的函數會共享最後一次變量的值,達不到我們的目的。

匿名函數

關鍵詞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是不支持函數名重載的,內部對比無從說起。但是在類的繼承中,相同的函數名子類可以覆蓋父類。在引用子類的實例時,實例的函數是子類所覆蓋的那個函數。
看一個例子來理解會好一點:

def run_twice(animal):
    animal.run()
    animal.run()

因為繼承,所以所有的子類都可以作為animal參數;又因為多態,所以在引用參數的內部函數時其實是引用了覆蓋了的函數。

廖老師的總結:

這就是多態真正的威力:調用方只管調用,不管細節,而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的“開閉”原則:
1.對擴展開放:允許新增Animal子類;
2.對修改封閉:不需要修改依賴Animal類型的run_twice()等函數。

最後是靜態語言和動態語言在繼承上的不同。靜態語言,以C++為例,像上面那個例子,傳入的參數必須只能是animal或它的子類;而動態語言,如python,只要那個實例有run()方法,那就可以傳入,亦被稱為“鴨子類型”。

獲取對象信息

當我們拿到一個對象的參數時,可以使用type() isinstance() dir() 來知道這個對象是什麽類型,有哪些方法。

type()

基本類型、指向函數或類的變量都可以用type()判斷,返回的是對應的Class()類型。
如果要判斷一個對象是否為函數,可以使用types模塊中定義的常量。

isinstance()

可用於判斷class的類型,告訴我們一個對象是否是某一個類型。
能用type()判斷的都可以用isinstance()判斷,而後者還可以判斷一個變量是否是某些類型中的一種。如:

>>>isinstance([1,2,3], (list,tuple))
True
dir()

用於獲得一個對象的所有屬性和方法,返回值是一個包含字符串的list。
類似__xxx__的屬性和方法在Python中都是有特殊用途的,比如__len__方法返回長度。在Python中,如果你調用len()函數試圖獲取一個對象的長度,實際上,在len()函數內部,它自動去調用該對象的__len__()方法,所以,下面的代碼是等價的:

>>> len(‘ABC‘)
3
>>> ‘ABC‘.__len__()
3

我們自己寫的類,如果也想用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裝飾器,將一個方法編程屬性來調用。如:

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError(‘score must be an integer!‘)
        if value < 0 or value > 100:
            raise ValueError(‘score must between 0 ~ 100!‘)
        self._score = value

把一個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, b
    
    def __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__,可以寫一個鏈式調用。

class Chain(object):

    def __init__(self, path=‘‘):
        self._path = path

    def __getattr__(self, path):
        return Chain(‘%s/%s‘ % (self._path, path))

    def __str__(self):
        return self._path

    __repr__ = __str__
>>> Chain().status.user.timeline.list
‘/status/user/timeline/list‘

還有些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 = name

    def __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 Enum

Month = 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被設定為0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 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個參數:

  1. class的名稱;
  2. 繼承的父類集合,註意Python支持多重繼承,如果只有一個父類,別忘了tuple的元素寫法;
  3. class的方法名稱與函數綁定,這裏我們把函數fn綁定到方法名hello上。

通過type()函數創建的類和直接寫class是完全一樣的,因為Python解釋器遇到class定義時,僅僅是掃描一下class定義的語法,然後調用type()函數創建出class。
正常情況下,我們都用class Xxx...來定義類,但是,type()函數也允許我們動態創建出類來,也就是說,動態語言本身支持運行期動態創建類,這和靜態語言有非常大的不同,要在靜態語言運行期創建類,必須構造源代碼字符串再調用編譯器,或者借助一些工具生成字節碼實現,本質上都是動態編譯,會非常復雜。

metaclass 元類

除了使用type()動態創建類外,要控制類的創建行為,還可以使用metaclass。
當我們定義了類以後,就可以根據這個類創建出實例,所以:先定義類,然後創建實例。
但是如果我們想創建出類呢?那就必須根據metaclass創建出類,所以:先定義metaclass,然後創建類。
連接起來就是:先定義metaclass,就可以創建類,最後創建實例。
所以metaclass允許你創建類或修改類。

後面的……沒看懂,先不記了。

python學習筆記_序