<wbr id="wsjqy"></wbr>

          <form id="wsjqy"></form>
          <sub id="wsjqy"></sub>
          <nav id="wsjqy"><listing id="wsjqy"></listing></nav>
          更多課程 選擇中心


          Python培訓

          400-111-8989

          記住道生一,一生二,二生三,三生萬物,學懂Python元類

          • 發布:劉羽沖
          • 來源:segmentfault
          • 時間:2018-06-07 18:13

          英語單詞很枯燥,但是有了形象記憶法簡單有趣多了,python編程中的元類有很多人也覺得不好理解,但是記住道生一,一生二,二生三,三生萬物;我是誰?我從哪來里?我要到哪里去?卻學會了,這是怎么回事呢?快來看看吧:

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

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

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

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

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

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

          道 即是 type

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

          二 即是 class(類,或者叫實例生成器)

          三 即是 instance(實例)

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

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

          # 創建一個Hello類,擁有屬性say_hello ----二的起源
          
          class Hello():
          
          def say_hello(self, name='world'):
          
          print('Hello, %s.' % name)
          
          # 從Hello類創建一個實例hello ----二生三
          
          hello = Hello()
          
          # 使用hello調用方法say_hello ----三生萬物
          
          hello.say_hello()

          輸出效果:

          Hello, world.

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

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

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

          def fn(self, name='world'): # 假如我們有一個函數叫fn
          
          print('Hello, %s.' % name)
          
          Hello = type('Hello', (object,), dict(say_hello=fn)) # 通過type創建Hello class ---- 神秘的“道”,可以點化一切,這次我們直接從“道”生出了“二”

          這樣的寫法,就和之前的Class Hello寫法作用完全相同,你可以試試創建實例并調用

          # 從Hello類創建一個實例hello ----二生三,完全一樣
          
          hello = Hello()
          
          # 使用hello調用方法say_hello ----三生萬物,完全一樣
          
          hello.say_hello()

          輸出效果:

          Hello, world.

           ----調用結果完全一樣。

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

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

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

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

          第一個參數:我是誰。 在這里,我需要一個區分于其它一切的命名,以上的實例將我命名為“Hello”

          第二個參數:我從哪里來。在這里,我需要知道從哪里來,也就是我的“父類”,以上實例中我的父類是“object”——python中一種非常初級的類。

          第三個參數:我要到哪里去。在這里,我們將需要調用的方法和屬性包含到一個字典里,再作為參數傳入。以上實例中,我們有一個say_hello方法包裝進了字典中。

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

          class Hello(object){
          
          # class 后聲明“我是誰”
          
          # 小括號內聲明“我來自哪里”
          
          # 中括號內聲明“我要到哪里去”
          
          def say_hello(){
          
          }
          
          }

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

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

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

          元類——道生一,一生二

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

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

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

          class SayMetaClass(type):
          
          def __new__(cls, name, bases, attrs):
          
          attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!')
          
          return type.__new__(cls, name, bases, attrs)

          記住兩點:

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

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

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

          attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!')

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

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

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

          # 道生一:傳入type
          
          class SayMetaClass(type):
          
          # 傳入三大永恒命題:類名稱、父類、屬性
          
          def __new__(cls, name, bases, attrs):
          
          # 創造“天賦”
          
          attrs['say_'+name] = lambda self,value,saying=name: print(saying+','+value+'!')
          
          # 傳承三大永恒命題:類名稱、父類、屬性
          
          return type.__new__(cls, name, bases, attrs)
          
          # 一生二:創建類
          
          class Hello(object, metaclass=SayMetaClass):
          
          pass
          
          # 二生三:創建實列
          
          hello = Hello()
          
          # 三生萬物:調用實例方法
          
          hello.say_Hello('world!')

          輸出為

          Hello, world!

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

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

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

          # 一生二:創建類
          
          class Sayolala(object, metaclass=SayMetaClass):
          
          pass
          
          # 二生三:創建實列
          
          s = Sayolala()
          
          # 三生萬物:調用實例方法
          
          s.say_Sayolala('japan!')
          
          輸出
          
          Sayolala, japan!
          
          也可以說中文
          
          # 一生二:創建類
          
          class Nihao(object, metaclass=SayMetaClass):
          
          pass
          
          # 二生三:創建實列
          
           = Nihao()
          
          # 三生萬物:調用實例方法
          
          .say_Nihao('中華!')
          
          輸出
          
          Nihao, 中華!
          
          再來一個小例子:
          
          # 道生一
          
          class ListMetaclass(type):
          
          def __new__(cls, name, bases, attrs):
          
          # 天賦:通過add方法將值綁定
          
          attrs['add'] = lambda self, value: self.append(value)
          
          return type.__new__(cls, name, bases, attrs)
          
          # 一生二
          
          class MyList(list, metaclass=ListMetaclass):
          
          pass
          
          # 二生三
          
          L = MyList()
          
          # 三生萬物
          
          L.add(1)
          
          現在我們打印一下L
          
          print(L)
          
          >>> [1]
          
          而普通的list沒有add()方法
          
          L2 = list()
          
          L2.add(1)
          
          >>>AttributeError: 'list' object has no attribute 'add'

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

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

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

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

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

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

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

          挑戰一:通過元類創建ORM

          準備工作,創建一個Field類

          class Field(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

          class StringField(Field):
          
          def __init__(self, name):
          
          super(StringField, self).__init__(name, 'varchar(100)')
          
          class IntegerField(Field):
          
          def __init__(self, name):
          
          super(IntegerField, self).__init__(name, 'bigint')

          它的作用是

          在StringField,IntegerField實例初始化時,時自動調用父類的初始化方式。

          道生一

          class ModelMetaclass(type):
          
          def __new__(cls, name, bases, attrs):
          
          if name=='Model':
          
          return type.__new__(cls, name, bases, attrs)
          
          print('Found model: %s' % name)
          
          mappings = dict()
          
          for k, v in attrs.items():
          
          if isinstance(v, Field):
          
          print('Found mapping: %s ==> %s' % (k, v))
          
          mappings[k] = v
          
          for k in mappings.keys():
          
          attrs.pop(k)
          
          attrs['__mappings__'] = mappings # 保存屬性和列的映射關系
          
          attrs['__table__'] = name # 假設表名和類名一致
          
          return type.__new__(cls, name, bases, attrs)

          它做了以下幾件事

          創建一個新的字典mapping

          將每一個類的屬性,通過.items()遍歷其鍵值對。如果值是Field類,則打印鍵值,并將這一對鍵值綁定到mapping字典上。

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

          創建一個專門的__mappings__屬性,保存字典mapping。

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

          一生二

          class Model(dict, metaclass=ModelMetaclass):
          
          def __init__(self, **kwarg):
          
          super(Model, self).__init__(**kwarg)
          
          def __getattr__(self, key):
          
          try:
          
          return self[key]
          
          except KeyError:
          
          raise AttributeError("'Model' object has no attribute '%s'" % key)
          
          def __setattr__(self, key, value):
          
          self[key] = value
          
          # 模擬建表操作
          
          def save(self):
          
          fields = []
          
          args = []
          
          for k, v in self.__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) for i in args]))
          
          print('SQL: %s' % sql)
          
          print('ARGS: %s' % str(args))

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

          class User(Model):
          
          # 定義類的屬性到列的映射:
          
          id = IntegerField('id')
          
          ame = 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='batman@nasa.org', password='iamback')
          
          u.save()

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

          先調用Model.__setattr__,將鍵值載入私有對象

          然后調用元類的“天賦”,ModelMetaclass.__new__,將Model中的私有對象,只要是Field的實例,都自動存入u.__mappings__。

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

          通過u.save()模擬數據庫存入操作。這里我們僅僅做了一下遍歷__mappings__操作,虛擬了sql并打印,在現實情況下是通過輸入sql語句與數據庫來運行。

          輸出結果為

          Found model: User
          
          Found mapping: name ==> <StringField:username>
          
          Found mapping: password ==> <StringField:password>
          
          Found mapping: id ==> <IntegerField:id>
          
          Found mapping: email ==> <StringField:email>
          
          SQL: insert into User (username,password,id,email) values (Batman,iamback,12345,batman@nasa.org)
          
          ARGS: ['Batman', 'iamback', 12345, 'batman@nasa.org']

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

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

          挑戰二:網絡代理的爬取

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

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

          # 文件:get_page.py
          
          import requests
          
          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'
          
          }
          
          def get_page(url):
          
          headers = dict(base_headers)
          
          print('Getting', url)
          
          try:
          
          r = requests.get(url, headers=headers)
          
          print('Getting result', url, r.status_code)
          
          if r.status_code == 200:
          
          return r.text
          
          except ConnectionError:
          
          print('Crawling Failed', url)
          
          return None

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

          試一試抓百度

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

          if(__name__ == '__main__'):
          
          rs = get_page('https://www.baidu.com')
          
          print('result: ', rs)

          試一試抓代理

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

          if(__name__ == '__main__'):
          
          from pyquery import PyQuery as pq
          
          start_url = 'http://www.proxy#/Region/China'
          
          print('Crawling', start_url)
          
          html = get_page(start_url)
          
          if html:
          
          doc = pq(html)
          
          lines = doc('div[name="list_proxy_ip"]').items()
          
          for line in lines:
          
          ip = line.find('.tbBottomLine:nth-child(1)').text()
          
          port = line.find('.tbBottomLine:nth-child(2)').text()
          
          print(ip+':'+port)

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

          批量處理抓取代理

          from getpage import get_page
          
          from pyquery import PyQuery as pq
          
          # 道生一:創建抽取代理的metaclass
          
          class ProxyMetaclass(type):
          
          """
          
          元類,在FreeProxyGetter類中加入
          
          __CrawlFunc__和__CrawlFuncCount__
          
          兩個參數,分別表示爬蟲函數,和爬蟲函數的數量。
          
          """
          
          def __new__(cls, name, bases, attrs):
          
          count = 0
          
          attrs['__CrawlFunc__'] = []
          
          attrs['__CrawlName__'] = []
          
          for k, v in attrs.items():
          
          if 'crawl_' in k:
          
          attrs['__CrawlName__'].append(k)
          
          attrs['__CrawlFunc__'].append(v)
          
          count += 1
          
          for k in attrs['__CrawlName__']:
          
          attrs.pop(k)
          
          attrs['__CrawlFuncCount__'] = count
          
          return type.__new__(cls, name, bases, attrs)
          
          # 一生二:創建代理獲取類
          
          class ProxyGetter(object, metaclass=ProxyMetaclass):
          
          def get_raw_proxies(self, site):
          
          proxies = []
          
          print('Site', site)
          
          for func in self.__CrawlFunc__:
          
          if func.__name__==site:
          
          this_page_proxies = func(self)
          
          for proxy in this_page_proxies:
          
          print('Getting', proxy, 'from', site)
          
          proxies.append(proxy)
          
          return proxies
          
          def crawl_daili66(self, page_count=4):
          
          start_url = 'http://www.66ip.cn/{}.html'
          
          urls = [start_url.format(page) for page in range(1, page_count + 1)]
          
          for url in urls:
          
          print('Crawling', url)
          
          html = get_page(url)
          
          if html:
          
          doc = pq(html)
          
          trs = doc('.containerbox table tr:gt(0)').items()
          
          for tr in trs:
          
          ip = tr.find('td:nth-child(1)').text()
          
          port = tr.find('td:nth-child(2)').text()
          
          yield ':'.join([ip, port])
          
          def crawl_proxy360(self):
          
          start_url = 'http://www.proxy#/Region/China'
          
          print('Crawling', start_url)
          
          html = get_page(start_url)
          
          if html:
          
          doc = pq(html)
          
          lines = doc('div[name="list_proxy_ip"]').items()
          
          for line in lines:
          
          ip = line.find('.tbBottomLine:nth-child(1)').text()
          
          port = line.find('.tbBottomLine:nth-child(2)').text()
          
          yield ':'.join([ip, port])
          
          def crawl_goubanjia(self):
          
          start_url = 'http://www.goubanjia.com/free/gngn/index.shtml'
          
          html = get_page(start_url)
          
          if html:
          
          doc = pq(html)
          
          tds = doc('td.ip').items()
          
          for td in tds:
          
          td.find('p').remove()
          
          yield td.text().replace(' ', '')
          
          if __name__ == '__main__':
          
          # 二生三:實例化ProxyGetter
          
          crawler = ProxyGetter()
          
          print(crawler.__CrawlName__)
          
          # 三生萬物
          
          for site_label in range(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編程中的元類你理解了嗎?還有python相關的其他問題嗎?歡迎你來達內python培訓機構進行咨詢。

          免責聲明:內容和圖片源自網絡,版權歸原作者所有,如有侵犯您的原創版權請告知,我們將盡快刪除相關內容。

          預約申請免費試聽課

          填寫下面表單即可預約申請免費試聽!怕錢不夠?可就業掙錢后再付學費! 怕學不會?助教全程陪讀,隨時解惑!擔心就業?一地學習,可全國推薦就業!

          上一篇:使用Pyecharts實現python數據可視化,真是驚艷!
          下一篇:送你入門python的三大神器,在你進階的路上助你一臂之力!

          如何運用Python編程處理大數據?用Python編程處理大數據的技巧是什么?

          Python面向對象編程的知識點都在這了!

          Python的高級特征及用法(部分)

          聽說這些Python知識,很少有人知道!

          • 掃碼領取資料

            回復關鍵字:視頻資料

            免費領取 達內課程視頻學習資料

          • 視頻學習QQ群

            添加QQ群:1143617948

            免費領取達內課程視頻學習資料

          Copyright ? 2021 Tedu.cn All Rights Reserved 京ICP備08000853號-56 京公網安備 11010802029508號 達內時代科技集團有限公司 版權所有

          選擇城市和中心
          黑龍江省

          吉林省

          河北省

          湖南省

          貴州省

          云南省

          廣西省

          海南省

          网友自拍 偷拍 校园性爱青青草曰逼视屏老鸭窝国产偷自视频区视频 百度 好搜 搜狗
          <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>