神級程式員通過兩句話帶你完全掌握Python最難知識點——元類!

來源:互聯網
上載者:User

標籤:www   ref   ssl   __init__   uri   gecko   生態圈   port   items   

千萬不要被所謂“元類是99%的python程式員不會用到的特性”這類的說辭嚇住。因為 每個中國人,都是天生的元類使用者

學懂元類,你只需要知道兩句話:

道生一,一生二,二生三,三生萬物

我是誰?我從哪來裡?我要到哪裡去?

在python世界,擁有一個永恒的道,那就是“type”,請記在腦海中,type就是道。如此廣袤無垠的python生態圈,都是由type產生出來的。在給大家分享之前呢,小編推薦一下一個挺不錯的交流寶地,裡面都是一群熱愛並在學習Python的小夥伴們,大幾千了吧,各種各樣的人群都有,特別喜歡看到這種大家一起交流解決難題的氛圍,群資料也上傳了好多,各種大牛解決小白的問題,這個Python群:330637182 歡迎大家進來一起交流討論,一起進步,儘早掌握這門Python語言。

道生一,一生二,二生三,三生萬物。

道即是 type

一即是 metaclass(元類,或者叫類產生器)

二即是 class(類,或者叫執行個體產生器)

三即是 instance(執行個體)

萬物即是 執行個體的各種屬性與方法,我們平常使用python時,調用的就是它們。

道和一,是我們今天討論的命題,而二、三、和萬物,則是我們常常使用的類、執行個體、屬性和方法,用hello world來舉例:

造物主,可以直接創造單個的人,但這是一件苦役。造物主會先創造“人”這一物種,再批量創造具體的個人。並將三大永恒命題,一直傳遞下去。

“道”可以直接生出“二”,但它會先生出“一”,再批量地製造“二”。

type可以直接產生類(class),但也可以先產生元類(metaclass),再使用元類批量定製類(class)。

元類——道生一,一生二

一般來說,元類均被命名尾碼為Metalass。想象一下,我們需要一個可以自動打招呼的元類,它裡面的類方法呢,有時需要say_Hello,有時需要say_Hi,有時又需要say_Sayolala,有時需要say_Nihao。

如果每個內建的say_xxx都需要在類裡面聲明一次,那將是多麼可怕的苦役! 不如使用 元類來解決問題。

以下是建立一個專門“打招呼”用的元類代碼:

來!一起根據道生一、一生二、二生三、三生萬物的準則,走進元類的生命週期吧!

注意:通過元類建立的類,第一個參數是父類,第二個參數是metaclass

普通人出生都不會說話,但有的人出生就會打招呼說“Hello”,“你好”,“sayolala”,這就是天賦的力量。它會給我們物件導向的編程省下無數的麻煩。

現在,保持元類不變,我們還可以繼續建立Sayolala, Nihao類,如下:

太棒了!學到這裡,你是不是已經體驗到了造物主的樂趣?

python世界的一切,盡在掌握。

年輕的造物主,請隨我一起開創新世界。

我們選擇兩個領域,一個是Django的核心思想,“Object Relational Mapping”,即對象-關係映射,簡稱ORM。

這是Django的一大痛點,但學完了元類,一切變得清晰。你對Django的理解將更上一層樓!

另一個領域是爬蟲領域(駭客領域),一個自動搜尋網路上的可用代理,然後換著IP去突破別的人反爬蟲限制。

這兩項技能非常有用,也非常好玩!

挑戰一:通過元類建立ORM

準備工作,建立一個Field類

它的作用是

在StringField,IntegerField執行個體初始化時,時自動調用父類的初始化方式。

道生一

它做了以下幾件事

建立一個新的字典mapping

將每一個類的屬性,通過.items()遍曆其索引值對。如果值是Field類,則列印索引值,並將這一對索引值綁定到mapping字典上。

將剛剛傳入值為Field類的屬性刪除。

建立一個專門的__mappings__屬性,儲存字典mapping。

建立一個專門的__table__屬性,儲存傳入的類的名稱。

一生二

IntegerField(‘id’)就會自動解析為:

Model.setattr(self, ‘id’, IntegerField(‘id’))

因為IntergerField(‘id’)是Field的子類的執行個體,自動觸發元類的__new__,所以將IntergerField(‘id’)存入__mappings__並刪除這個索引值對。

二生三、三生萬物

當你初始化一個執行個體的時候並調用save()方法時候

u = User(id=12345, name=‘Batman‘, email=‘[email protected]‘, password=‘iamback‘)u.save()

這時先完成了二生三的過程:

先調用Model.setattr,將索引值載入私人對象

然後調用元類的“天賦”,ModelMetaclass.new,將Model中的私人對象,只要是Field的執行個體,都自動存入u.mappings

接下來完成了三生萬物的過程:

通過u.save()類比資料庫存入操作。這裡我們僅僅做了一下遍曆__mappings__操作,虛擬了sql並列印,在現實情況下是通過輸入sql語句與資料庫來運行。

這裡,我們利用request包,把百度的源碼爬了出來。

試一試抓百度

把這一段粘在get_page.py後面,試完刪除

接下來進入正題:使用元類批量抓取代理

批量處理抓取代理

道生一:元類的__new__中,做了四件事:

將“crawl_”開頭的類方法的名稱推入ProxyGetter.CrawlName

將“crawl_”開頭的類方法的本身推入ProxyGetter.CrawlFunc

計算符合“crawl_”開頭的類方法個數

刪除所有符合“crawl_”開頭的類方法

怎麼樣?是不是和之前建立ORM的__mappings__過程極為相似?

一生二:類裡面定義了使用pyquery抓取頁面元素的方法

分別從三個免費代理網站抓取了頁面上顯示的全部代理。

如果對yield用法不熟悉,可以查看:

二生三:建立執行個體對象crawler

三生萬物:遍曆每一個__CrawlFunc__

在ProxyGetter.__CrawlName__上面,擷取可以抓取的的網址名。

觸發類方法ProxyGetter.get_raw_proxies(site)

遍曆ProxyGetter.CrawlFunc,如果方法名和網址名稱相同的,則執行這一個方法

把每個網址擷取到的代理整合成數組輸出。

那麼。。。怎麼利用批量代理,衝擊別人的網站,套取別人的密碼,狂發廣告水貼,定時騷擾客戶? 呃!想啥呢!這些自己悟!如果悟不到,請聽下回分解!

年輕的造物主,創造世界的工具已經在你手上,請你將它的威力發揮到極致!

請記住揮動工具的口訣:

道生一,一生二,二生三,三生萬物

我是誰,我來自哪裡,我要到哪裡去

在理解元類之前,需要先掌握Python中的類,Python中類的概念與SmallTalk中類的概念相似。

在大多數語言中,類是用來描述如何建立對象的程式碼片段,這在Python中也是成立的

Python那些事——何為元類?道生一,一生二,二生三,三生萬物元

學懂元類,你只需要知道兩句話:

道生一,一生二,二生三,三生萬物

我是誰?我從哪來裡?我要到哪裡去?

在python世界,擁有一個永恒的道,那就是“type”,請記在腦海中,type就是道。如此廣袤無垠的python生態圈,都是由type產生出來的。

道生一,一生二,二生三,三生萬物。

道 即是 type

一 即是 metaclass(元類,或者叫類產生器)

二 即是 class(類,或者叫執行個體產生器)

三 即是 instance(執行個體)

萬物 即是 執行個體的各種屬性與方法,我們平常使用python時,調用的就是它們。

道和一,是我們今天討論的命題,而二、三、和萬物,則是我們常常使用的類、執行個體、屬性和方法,用hello world來舉例:

Python

1

2

3

4

5

6

7

8

9

10

11

建立一個Hello類,擁有屬性say_hello ----二的起源

classHello():

defsay_hello(self,name=‘world‘):

print(‘Hello, %s.‘%name)

從Hello類建立一個執行個體hello ----二生三

hello=Hello()

使用hello調用方法say_hello ----三生萬物

hello.say_hello()

輸出效果:

Hello,world.

這就是一個標準的“二生三,三生萬物”過程。 從類到我們可以調用的方法,用了這兩步。

那我們不由自主要問,類從何而來呢?回到代碼的第一行。

class Hello其實是一個函數的“語義化簡稱”,只為了讓代碼更淺顯易懂,它的另一個寫法是:

deffn(self,name=‘world‘):# 假如我們有一個函數叫fn

Hello=type(‘Hello‘,(object,),dict(say_hello=fn))# 通過type建立Hello class ---- 神秘的“道”,可以點化一切,這次我們直接從“道”生出了“二”

這樣的寫法,就和之前的Class Hello寫法作用完全相同,你可以試試建立執行個體並調用

從Hello類建立一個執行個體hello ----二生三,完全一樣使用hello調用方法say_hello ----三生萬物,完全一樣

Hello,world.----調用結果完全一樣。

我們回頭看一眼最精彩的地方,道直接生出了二:

Hello = type(‘Hello’, (object,), dict(say_hello=fn))

這就是“道”,python世界的起源,你可以為此而驚歎。

注意它的三個參數!暗合人類的三大永恒命題:我是誰,我從哪裡來,我要到哪裡去。

第一個參數:我是誰。 在這裡,我需要一個區分於其它一切的命名,以上的執行個體將我命名為“Hello”

第二個參數:我從哪裡來

在這裡,我需要知道從哪裡來,也就是我的“父類”,以上執行個體中我的父類是“object”——python中一種非常初級的類。

第三個參數:我要到哪裡去

在這裡,我們將需要調用的方法和屬性包含到一個字典裡,再作為參數傳入。以上執行個體中,我們有一個say_hello方法封裝進了字典中。

值得注意的是,三大永恒命題,是一切類,一切執行個體,甚至一切執行個體屬性與方法都具有的。理所應當,它們的“創造者”,道和一,即type和元類,也具有這三個參數。但平常,類的三大永恒命題並不作為參數傳入,而是以如下方式傳入

classHello(object){

class 後聲明“我是誰”小括弧內聲明“我來自哪裡”中括弧內聲明“我要到哪裡去”

defsay_hello(){

}

造物主,可以直接創造單個的人,但這是一件苦役。造物主會先創造“人”這一物種,再批量創造具體的個人。並將三大永恒命題,一直傳遞下去。

“道”可以直接生出“二”,但它會先生出“一”,再批量地製造“二”。

type可以直接產生類(class),但也可以先產生元類(metaclass),再使用元類批量定製類(class)。

元類——道生一,一生二

一般來說,元類均被命名尾碼為Metalass。想象一下,我們需要一個可以自動打招呼的元類,它裡面的類方法呢,有時需要say_Hello,有時需要say_Hi,有時又需要say_Sayolala,有時需要say_Nihao。

如果每個內建的say_xxx都需要在類裡面聲明一次,那將是多麼可怕的苦役! 不如使用元類來解決問題。

以下是建立一個專門“打招呼”用的元類代碼:

classSayMetaClass(type):

def__new__(cls,name,bases,attrs):

attrs[‘say_‘+name]=lambdaself,value,saying=name:print(saying+‘,‘+value+‘!‘)

returntype.new(cls,name,bases,attrs)

記住兩點:

1、元類是由“type”衍生而出,所以父類需要傳入type。【道生一,所以一必須包含道】

2、元類的操作都在 __new__中完成,它的第一個參數是將建立的類,之後的參數即是三大永恒命題:我是誰,我從哪裡來,我將到哪裡去。 它返回的對象也是三大永恒命題,接下來,這三個參數將一直陪伴我們。

在__new__中,我只進行了一個操作,就是

它跟據類的名字,建立了一個類方法。比如我們由元類建立的類叫“Hello”,那建立時就自動有了一個叫“say_Hello”的類方法,然後又將類的名字“Hello”作為預設參數saying,傳到了方法裡面。然後把hello方法調用時的傳參作為value傳進去,最終列印出來。

那麼,一個元類是怎麼從建立到調用的呢?

來!一起根據道生一、一生二、二生三、三生萬物的準則,走進元類的生命週期吧!

12

13

14

15

16

17

18

19

道生一:傳入type傳入三大永恒命題:類名稱、父類、屬性創造“天賦”傳承三大永恒命題:類名稱、父類、屬性一生二:建立類

classHello(object,metaclass=SayMetaClass):

pass

二生三:建立實列三生萬物:調用執行個體方法

hello.say_Hello(‘world!‘)

輸出為

Hello,world!

注意:通過元類建立的類,第一個參數是父類,第二個參數是metaclass

普通人出生都不會說話,但有的人出生就會打招呼說“Hello”,“你好”,“sayolala”,這就是天賦的力量。它會給我們物件導向的編程省下無數的麻煩。

現在,保持元類不變,我們還可以繼續建立Sayolala, Nihao類,如下:

classSayolala(object,metaclass=SayMetaClass):

s=Sayolala()

s.say_Sayolala(‘japan!‘)

輸出

Sayolala,japan!

也可以說中文

classNihao(object,metaclass=SayMetaClass):

n=Nihao()

n.say_Nihao(‘中華!‘)

Nihao,中華!

再來一個小例子:

道生一

classListMetaclass(type):

天賦:通過add方法將值綁定

attrs[‘add‘]=lambdaself,value:self.append(value)

一生二

classMyList(list,metaclass=ListMetaclass):

二生三

L=MyList()

三生萬物

L.add(1)

現在我們列印一下L

print(L)

[1]

而普通的list沒有add()方法

L2=list()

L2.add(1)

AttributeError:‘list‘objecthas no attribute‘add‘

太棒了!學到這裡,你是不是已經體驗到了造物主的樂趣?

python世界的一切,盡在掌握。

年輕的造物主,請隨我一起開創新世界。

我們選擇兩個領域,一個是Django的核心思想,“Object Relational Mapping”,即對象-關係映射,簡稱ORM。

這是Django的一大痛點,但學完了元類,一切變得清晰。你對Django的理解將更上一層樓!

另一個領域是爬蟲領域(駭客領域),一個自動搜尋網路上的可用代理,然後換著IP去突破別的人反爬蟲限制。

這兩項技能非常有用,也非常好玩!

挑戰一:通過元類建立ORM

準備工作,建立一個Field類

classField(object):

def__init__(self,name,column_type):

self.name=name

self.column_type=column_type

def__str__(self):

return‘<%s:%s>‘%(self.class.name,self.name)

它的作用是

在Field類執行個體化時將得到兩個參數,name和column_type,它們將被綁定為Field的私人屬性,如果要將Field轉化為字串時,將返回“Field:XXX” , XXX是傳入的name名稱。

準備工作:建立StringField和IntergerField

classStringField(Field):

def__init__(self,name):

super(StringField,self).init(name,‘varchar(100)‘)

classIntegerField(Field):

super(IntegerField,self).init(name,‘bigint‘)

在StringField,IntegerField執行個體初始化時,時自動調用父類的初始化方式。

道生一

classModelMetaclass(type):

ifname==‘Model‘:

print(‘Found model: %s‘%name)

mappings=dict()

fork,vinattrs.items():

ifisinstance(v,Field):

print(‘Found mapping: %s ==> %s‘%(k,v))

mappings[k]=v

forkinmappings.keys():

attrs.pop(k)

attrs[‘mappings‘]=mappings# 儲存屬性和列的映射關係

attrs[‘table‘]=name# 假設表名和類名一致

它做了以下幾件事

建立一個新的字典mapping

將每一個類的屬性,通過.items()遍曆其索引值對。如果值是Field類,則列印索引值,並將這一對索引值綁定到mapping字典上。

將剛剛傳入值為Field類的屬性刪除。

建立一個專門的__mappings__屬性,儲存字典mapping。

建立一個專門的__table__屬性,儲存傳入的類的名稱。

一生二

20

21

22

23

24

classModel(dict,metaclass=ModelMetaclass):

def__init__(self,**kwarg):

super(Model,self).init(**kwarg)

def__getattr__(self,key):

try:

returnself[key]

exceptKeyError:

raiseAttributeError("‘Model‘ object has no attribute ‘%s‘"%key)

def__setattr__(self,key,value):

self[key]=value

類比建表操作

defsave(self):

fields=[]

args=[]

fork,vinself.mappings.items():

fields.append(v.name)

args.append(getattr(self,k,None))

sql=‘insert into %s (%s) values (%s)‘%(self.table,‘,‘.join(fields),‘,‘.join([str(i)foriinargs]))

print(‘SQL: %s‘%sql)

print(‘ARGS: %s‘%str(args))

如果從Model建立一個子類User:

classUser(Model):

定義類的屬性到列的映射:

id=IntegerField(‘id‘)

name=StringField(‘username‘)

email=StringField(‘email‘)

password=StringField(‘password‘)

這時

id= IntegerField(‘id’)就會自動解析為:

Model.setattr(self, ‘id’, IntegerField(‘id’))

因為IntergerField(‘id’)是Field的子類的執行個體,自動觸發元類的__new__,所以將IntergerField(‘id’)存入__mappings__並刪除這個索引值對。

二生三、三生萬物

當你初始化一個執行個體的時候並調用save()方法時候

u=User(id=12345,name=‘Batman‘,email=‘[email protected]‘,password=‘iamback‘)

u.save()

這時先完成了二生三的過程:

先調用Model.setattr,將索引值載入私人對象

然後調用元類的“天賦”,ModelMetaclass.new,將Model中的私人對象,只要是Field的執行個體,都自動存入u.mappings

接下來完成了三生萬物的過程:

通過u.save()類比資料庫存入操作。這裡我們僅僅做了一下遍曆__mappings__操作,虛擬了sql並列印,在現實情況下是通過輸入sql語句與資料庫來運行。

輸出結果為

Found model:User

Found mapping:name==>

Found mapping:password==>

Found mapping:id==>

Found mapping:email==>

SQL:insert into User(username,password,id,email)values(Batman,iamback,12345,[email protected])

ARGS:[‘Batman‘,‘iamback‘,12345,‘[email protected]‘]

年輕的造物主,你已經和我一起體驗了由“道”演化“萬物”的偉大曆程,這也是Django中的Model版塊核心原理。

接下來,請和我一起進行更好玩的爬蟲實戰(嗯,你現在已經是初級駭客了):網路代理程式的爬取吧!

挑戰二:網路代理程式的爬取

準備工作,先爬個頁面玩玩

請確保已安裝requests和pyquery這兩個包。

檔案:get_page.py

importrequests

base_headers={

‘User-Agent‘:‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36‘,

‘Accept-Encoding‘:‘gzip, deflate, sdch‘,

‘Accept-Language‘:‘zh-CN,zh;q=0.8‘

defget_page(url):

headers=dict(base_headers)

print(‘Getting‘,url)

r=requests.get(url,headers=headers)

print(‘Getting result‘,url,r.status_code)

ifr.status_code==200:

returnr.text

exceptConnectionError:

print(‘Crawling Failed‘,url)

returnNone

這裡,我們利用request包,把百度的源碼爬了出來。

試一試抓百度

把這一段粘在get_page.py後面,試完刪除

if(name==‘main‘):

rs=get_page(‘https://www.baidu.com‘)

print(‘result:\r\n‘,rs)

試一試抓代理

frompyquery importPyQuery aspq

start_url=‘//www.proxy360.cn/Region/China‘

print(‘Crawling‘,start_url)

html=get_page(start_url)

ifhtml:

doc=pq(html)

lines=doc(‘div[name="list_proxy_ip"]‘).items()

forline inlines:

ip=line.find(‘.tbBottomLine:nth-child(1)‘).text()

port=line.find(‘.tbBottomLine:nth-child(2)‘).text()

print(ip+‘:‘+port)

接下來進入正題:使用元類批量抓取代理

批量處理抓取代理

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

fromgetpage importget_page

道生一:建立抽取代理的metaclass

classProxyMetaclass(type):

"""

元類,在FreeProxyGetter類中加入

__CrawlFunc__和CrawlFuncCount

兩個參數,分別表示爬蟲函數,和爬蟲函數的數量。

count=0

attrs[‘CrawlFunc‘]=[]

attrs[‘CrawlName‘]=[]

if‘crawl_‘ink:

attrs[‘CrawlName‘].append(k)

attrs[‘CrawlFunc‘].append(v)

count+=1

forkinattrs[‘CrawlName‘]:

attrs[‘CrawlFuncCount‘]=count

一生二:建立代理擷取類

classProxyGetter(object,metaclass=ProxyMetaclass):

defget_raw_proxies(self,site):

proxies=[]

print(‘Site‘,site)

forfunc inself.CrawlFunc:

iffunc.name==site:

this_page_proxies=func(self)

forproxy inthis_page_proxies:

print(‘Getting‘,proxy,‘from‘,site)

proxies.append(proxy)

returnproxies

defcrawl_daili66(self,page_count=4):

start_url=‘//www.66ip.cn/{}.html‘

urls=[start_url.format(page)forpage inrange(1,page_count+1)]

forurl inurls:

print(‘Crawling‘,url)

html=get_page(url)

trs=doc(‘.containerbox table tr:gt(0)‘).items()

fortr intrs:

ip=tr.find(‘td:nth-child(1)‘).text()

port=tr.find(‘td:nth-child(2)‘).text()

yield‘:‘.join([ip,port])

defcrawl_proxy360(self):

defcrawl_goubanjia(self):

start_url=‘//www.goubanjia.com/free/gngn/index.shtml‘

tds=doc(‘td.ip‘).items()

fortd intds:

td.find(‘p‘).remove()

yieldtd.text().replace(‘ ‘,‘‘)

if__name__==‘main‘:

二生三:執行個體化ProxyGetter

crawler=ProxyGetter()

print(crawler.CrawlName)

forsite_label inrange(crawler.CrawlFuncCount):

site=crawler.CrawlName[site_label]

myProxies=crawler.get_raw_proxies(site)

道生一:元類的__new__中,做了四件事:

將“crawl_”開頭的類方法的名稱推入ProxyGetter.CrawlName

將“crawl_”開頭的類方法的本身推入ProxyGetter.CrawlFunc

計算符合“crawl_”開頭的類方法個數

刪除所有符合“crawl_”開頭的類方法

怎麼樣?是不是和之前建立ORM的__mappings__過程極為相似?

一生二:類裡面定義了使用pyquery抓取頁面元素的方法

分別從三個免費代理網站抓取了頁面上顯示的全部代理。

如果對yield用法不熟悉,可以查看:

廖雪峰的python教程:產生器

二生三:建立執行個體對象crawler

三生萬物:遍曆每一個__CrawlFunc__

在ProxyGetter.__CrawlName__上面,擷取可以抓取的的網址名。

觸發類方法ProxyGetter.get_raw_proxies(site)

遍曆ProxyGetter.CrawlFunc,如果方法名和網址名稱相同的,則執行這一個方法

把每個網址擷取到的代理整合成數組輸出。

那麼。。。怎麼利用批量代理,衝擊別人的網站,套取別人的密碼,狂發廣告水貼,定時騷擾客戶? 呃!想啥呢!這些自己悟!如果悟不到,請聽下回分解!

年輕的造物主,創造世界的工具已經在你手上,請你將它的威力發揮到極致!

神級程式員通過兩句話帶你完全掌握Python最難知識點——元類!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.