深入淺出Python超程式設計
隔壁的Java 世界為了建立一個物件搞得雞飛狗跳,這邊的Python直譯器倒是樂得清閒。
(參見:《當建立物件時......》)
我作為他的第n任助手正式上崗。
“老大,有程式設計師要建立物件,怎麼辦?”我向Python直譯器發出了預警,上崗後頭一次遇到這種情況,我有點緊張。
class Person: def sayHello(self,name): print("hello,"+name) p = Person() p.sayHello("andy")
“怕啥,我告訴你怎麼做啊,首先找到Metaclass(元類),用元類來建立Class, 最後用Class物件來建立例項。” 老大說著還給我畫了個圖:
“不是吧!剛才還說人家Java雞飛狗跳,我看我們這兒也絲毫不差,一個Class(如Person)在記憶體中用個物件來表示我理解,畢竟在我們的世界中,一切都是物件嘛, 但是這Metaclass(元類)是什麼鬼?”
“是啊,類是一個物件,呼叫這個類物件的__new__方法就可以創建出這個類的例項。那麼問題來了, 類物件是怎麼來的?怎麼把這個類物件給new 出來?” 老大沒有回答,只是反問。
“不是程式設計師寫的嗎, class Person.....” 我有點底氣不足。
“程式設計師寫的只是程式碼,都是文字而已,我們在執行的過程中需要用Metaclass 把這個Person類物件給建立起來的。”
“可是我也沒有看到Person類的Metaclass啊?! 他到底在哪兒?”
“那是你沒有找到, Person類中沒有,就去它的父類中去找,如果也沒有,就繼續向父類的父類去找,如果在任何父類中都找不到Metaclass,就去模組中找,如果還是找不到,就用預設的Metaclass,即type。”
我按照老大的要求,去找這個Metaclass,沒有找到,只好用預設的type了。
可是我記得這個type不是個類,是個函式啊,可以用來檢視一個變數的型別:
>>> type(1)
<class 'int'>
>>> type("aaa")
<class 'str'>
>>>
老大說:“這個type啊,還有另外一個用法,可以建立其他類物件,在建立的時候,需要三個引數:”
1. 要建立的類物件的名稱,例如"Person"
2. 要建立的類物件的父類,例如(object)
3. 包含屬性的字典,即類的屬性和方法。例如{"sayHello": sayHello}
比如,下面這段程式碼也建立了一個類物件Person,和程式設計師寫的class Person... 效果是一樣的。
def sayHello(self,name):
print("hello,"+name)
#通過type來建立一個類物件,名稱為Person,這個類物件有一個方法sayHello
Person = type("Person",(),{"sayHello":sayHello})
#通過類物件來建立例項
p = Person()
p.sayHello("andy") # hello andy
(友情提示:可左右滑動)
嘿,這個辦法不錯啊,可以在執行時、動態地建立一個全新的類出來!隔壁的Java雖然也能做到,但是得利用ASM之類工具去直接操作位元組碼,太麻煩了,我大Python直接通過普普通通、簡簡單單的Python程式碼就搞定了!
這就是動態指令碼語言的一個優勢吧!
之前聽說過超程式設計,現在應該就是超程式設計了吧?但是這個Metaclass到底有什麼用處呢? 程式設計師為什麼不直接在程式碼中寫class Person..... 這樣的程式碼? 這樣多直觀啊。
老大說:“有些程式設計師會自定義Metaclass,這些自定義的Metaclass 主要做這些事情:”
1. 攔截類的建立
2. 讀取類的資訊,可能做修改
3. 返回新的類。
攔截類的建立? 為什麼有這樣“變態”的需求?
我真想看看一個自定義的Metaclass,看看它到底是怎麼“變態”的。
沒多久,機會來了,又要建立物件了。
from django.db import models
class Employee(models.Model):
name = models.CharField(maxlength = 50)
age = models.IntegerField()
#其他程式碼略#
在Employee中沒有看到Metaclass, 我就去父類Model中去尋找,運氣不錯,一下子就找到了metaclass ,叫做ModelBase:
class Model(metaclass=ModelBase):
#其他程式碼略
趕緊去看ModelBase的程式碼,唉,實在是有點複雜了,讓我看得頭暈。
老大說:“你不用花費時間了,你的前任的前任曾經研究過它,是為了實現ORM !”
“ORM?”
“就是物件和關係資料庫的對映。你想想,程式設計師建立的Python物件想要儲存到資料庫中,該怎麼辦?“ 老大問道。
”那還不簡單,程式設計師可以寫SQL程式碼啊,insert into employee(name,age) values(?,?),其中包含那個Employee物件的name ,age的值不就行了?“
”那樣就有點笨拙了,你再想想,能不能簡化程式設計師的工作,別讓他們去寫這些煩人的、容易出錯的SQL程式碼?能不能讓框架來做這件事?“ 老大寫了兩行程式碼。
employee = Employee(name="andy",age=20)
employee.save()
“看看,程式設計師只要把物件創建出來,呼叫下save方法就行了,SQL語句就會形成,儲存到資料庫中。”
(注:這裡略過了資料庫連線的管理)
“難道ModelBase這個元類在後面做‘手腳'?”我似乎有點理解了。
”沒錯,你看到這些Employee類的屬性沒有? 就是程式設計師寫的那些name, age...... 程式設計師這麼寫,其實就是在告訴ModelBase,尊敬的Metaclass 啊, 這些都是資料庫的列啊,列名是 name, 型別是char(50) , 還有個列名是age,是個整數。”
“那個MetaClass ,對,就是ModelBase會讀取這些列名、型別,並且記錄下來。 有了列名的資訊,將來就可以形成insert, update,delete等SQL語句了。對不對?”
原來如此!看來ModelBase在建立Employee類物件的時候,“偷偷地”讀取了Employee類的定義資訊,這樣才能在背後實現ORM!
我按照老大的指示,呼叫ModelBase的__new__方法,建立了Employee類物件。
接下來又呼叫Employee類物件的__new__方法,建立了Employee例項物件。
employee = Employee(name="andy",age=20)
employee.save()
當程式設計師呼叫employee.save()方法的時候,正如老大所說,神奇的魔法發生了,一條sql語句形成,並且傳送給了資料庫去執行。
我感慨到:“這Python的超程式設計還是真是不錯啊,能在執行時動態地修改類,比隔壁的Java強多了!”
“Python超程式設計的技術不僅僅是Metaclass,還多著呢,你慢慢學吧!”