元類(metaclass)

目錄

  • 一、引言
  • 二、什么是元類
  • 三、為什么用元類
  • 四、內置函數exec(儲備)
  • 五、class創建類
    • 5.1 type實現
  • 六、自定義元類控制類的創建
    • 6.1 應用
  • 七、__call__(儲備)
  • 八、__new__(儲備)
  • 九、自定義元類控制類的實例化
  • 一十、自定義元類后類的繼承順序
  • 十一、練習

一、引言

  • 元類屬于python面向對象編程的深層魔法,99%的人都不得要領,一些自以為搞明白元類的人其實也只是自圓其說、點到為止,從對元類的控制上來看就破綻百出、邏輯混亂,今天我就來帶大家來深度了解python元類的來龍去脈。

  • 筆者深入淺出的背后是對技術一日復一日的執念,希望可以大家可以尊重原創,為大家能因此文而解開對元類所有的疑惑而感到開心!!!

二、什么是元類

  • 在python中一切皆對象,那么我們用class關鍵字定義的類本身也是一個對象,負責產生該對象的類稱之為元類,即元類可以簡稱為類的類
class Foo:  # Foo=元類()pass

114-元類metaclass-類的創建.png?x-oss-process=style/watermark

三、為什么用元類

  • 元類是負責產生類的,所以我們學習元類或者自定義元類的目的:是為了控制類的產生過程,還可以控制對象的產生過程

四、內置函數exec(儲備)

cmd = """
x=1
print('exec函數運行了')
def func(self):pass
"""
class_dic = {}
# 執行cmd中的代碼,然后把產生的名字丟入class_dic字典中
exec(cmd, {}, class_dic)
exec函數運行了
print(class_dic)
{'x': 1, 'func': <function func at 0x10a0bc048>}

五、class創建類

  • 如果說類也是對象,那么用class關鍵字的去創建類的過程也是一個實例化的過程,該實例化的目的是為了得到一個類,調用的是元類

  • 用class關鍵字創建一個類,用的默認的元類type,因此以前說不要用type作為類別判斷

class People:  # People=type(...)country = 'China'def __init__(self, name, age):self.name = nameself.age = agedef eat(self):print('%s is eating' % self.name)
print(type(People))
<class 'type'>

114-元類metaclass-class關鍵字.png?x-oss-process=style/watermark

5.1 type實現

  • 創建類的3個要素:類名,基類,類的名稱空間

  • People = type(類名,基類,類的名稱空間)

class_name = 'People'  # 類名class_bases = (object, )  # 基類# 類的名稱空間
class_dic = {}
class_body = """
country='China'
def __init__(self,name,age):self.name=nameself.age=age
def eat(self):print('%s is eating' %self.name)
"""exec(class_body,{},class_dic,
)
print(class_name)
People
print(class_bases)
(<class 'object'>,)
print(class_dic)  # 類的名稱空間
{'country': 'China', '__init__': <function __init__ at 0x10a0bc048>, 'eat': <function eat at 0x10a0bcd08>}
  • People = type(類名,基類,類的名稱空間)
People1 = type(class_name, class_bases, class_dic)
print(People1)
<class '__main__.People'>
obj1 = People1(1, 2)
obj1.eat()
1 is eating
  • class創建的類的調用
print(People)
<class '__main__.People'>
obj = People1(1, 2)
obj.eat()
1 is eating

六、自定義元類控制類的創建

  • 使用自定義的元類
class Mymeta(type):  # 只有繼承了type類才能稱之為一個元類,否則就是一個普通的自定義類def __init__(self, class_name, class_bases, class_dic):print('self:', self)  # 現在是Peopleprint('class_name:', class_name)print('class_bases:', class_bases)print('class_dic:', class_dic)super(Mymeta, self).__init__(class_name, class_bases,class_dic)  # 重用父類type的功能
  • 分析用class自定義類的運行原理(而非元類的的運行原理):

    1. 拿到一個字符串格式的類名class_name='People'

    2. 拿到一個類的基類們class_bases=(obejct,)

    3. 執行類體代碼,拿到一個類的名稱空間class_dic={...}

    4. 調用People=type(class_name,class_bases,class_dic)

class People(object, metaclass=Mymeta):  # People=Mymeta(類名,基類們,類的名稱空間)country = 'China'def __init__(self, name, age):self.name = nameself.age = agedef eat(self):print('%s is eating' % self.name)
self: <class '__main__.People'>
class_name: People
class_bases: (<class 'object'>,)
class_dic: {'__module__': '__main__', '__qualname__': 'People', 'country': 'China', '__init__': <function People.__init__ at 0x10a0bcbf8>, 'eat': <function People.eat at 0x10a0bc2f0>}

6.1 應用

  • 自定義元類控制類的產生過程,類的產生過程其實就是元類的調用過程

  • 我們可以控制類必須有文檔,可以使用如下的方式實現

class Mymeta(type):  # 只有繼承了type類才能稱之為一個元類,否則就是一個普通的自定義類def __init__(self, class_name, class_bases, class_dic):if class_dic.get('__doc__') is None or len(class_dic.get('__doc__').strip()) == 0:raise TypeError('類中必須有文檔注釋,并且文檔注釋不能為空')if not class_name.istitle():raise TypeError('類名首字母必須大寫')super(Mymeta, self).__init__(class_name, class_bases,class_dic)  # 重用父類的功能
try:class People(object, metaclass=Mymeta):  #People  = Mymeta('People',(object,),{....})#     """這是People類"""country = 'China'def __init__(self, name, age):self.name = nameself.age = agedef eat(self):print('%s is eating' % self.name)
except Exception as e:print(e)
類中必須有文檔注釋,并且文檔注釋不能為空

七、__call__(儲備)

  • 要想讓obj這個對象變成一個可調用的對象,需要在該對象的類中定義一個方法、、__call__方法,該方法會在調用對象時自動觸發
class Foo:def __call__(self, *args, **kwargs):print(args)print(kwargs)print('__call__實現了,實例化對象可以加括號調用了')obj = Foo()
obj('nick', age=18)
('nick',)
{'age': 18}
__call__實現了,實例化對象可以加括號調用了

八、__new__(儲備)

我們之前說類實例化第一個調用的是__init__,但__init__其實不是實例化一個類的時候第一個被調用 的方法。當使用 Persion(name, age) 這樣的表達式來實例化一個類時,最先被調用的方法 其實是 __new__ 方法。

__new__方法接受的參數雖然也是和__init__一樣,但__init__是在類實例創建之后調用,而 __new__方法正是創建這個類實例的方法。

注意:new() 函數只能用于從object繼承的新式類。

class A:passclass B(A):def __new__(cls):print("__new__方法被執行")return cls.__new__(cls)def __init__(self):print("__init__方法被執行")b = B()

九、自定義元類控制類的實例化

class Mymeta(type):def __call__(self, *args, **kwargs):print(self)  # self是Peopleprint(args)  # args = ('nick',)print(kwargs)  # kwargs = {'age':18}# return 123# 1. 先造出一個People的空對象,申請內存空間# __new__方法接受的參數雖然也是和__init__一樣,但__init__是在類實例創建之后調用,而 __new__方法正是創建這個類實例的方法。obj = self.__new__(self)  # 雖然和下面同樣是People,但是People沒有,找到的__new__是父類的# 2. 為該對空對象初始化獨有的屬性self.__init__(obj, *args, **kwargs)# 3. 返回一個初始化好的對象return obj
  • People = Mymeta(),People()則會觸發__call__
class People(object, metaclass=Mymeta):country = 'China'def __init__(self, name, age):self.name = nameself.age = agedef eat(self):print('%s is eating' % self.name)#     在調用Mymeta的__call__的時候,首先會找自己(如下函數)的,自己的沒有才會找父類的
#     def __new__(cls, *args, **kwargs):
#         # print(cls)  # cls是People
#         # cls.__new__(cls) # 錯誤,無限死循環,自己找自己的,會無限遞歸
#         obj = super(People, cls).__new__(cls)  # 使用父類的,則是去父類中找__new__
#         return obj
  • 類的調用,即類實例化就是元類的調用過程,可以通過元類Mymeta的__call__方法控制

  • 分析:調用Pepole的目的

    1. 先造出一個People的空對象

    2. 為該對空對象初始化獨有的屬性

    3. 返回一個初始化好的對象

obj = People('nick', age=18)
<class '__main__.People'>
('nick',)
{'age': 18}
print(obj.__dict__)
{'name': 'nick', 'age': 18}

一十、自定義元類后類的繼承順序

結合python繼承的實現原理+元類重新看屬性的查找應該是什么樣子呢???

在學習完元類后,其實我們用class自定義的類也全都是對象(包括object類本身也是元類type的 一個實例,可以用type(object)查看),我們學習過繼承的實現原理,如果把類當成對象去看,將下述繼承應該說成是:對象OldboyTeacher繼承對象Foo,對象Foo繼承對象Bar,對象Bar繼承對象object

class Mymeta(type):  # 只有繼承了type類才能稱之為一個元類,否則就是一個普通的自定義類n = 444def __call__(self, *args,**kwargs):  #self=<class '__main__.OldboyTeacher'>obj = self.__new__(self)self.__init__(obj, *args, **kwargs)return objclass Bar(object):n = 333class Foo(Bar):n = 222class OldboyTeacher(Foo, metaclass=Mymeta):n = 111school = 'oldboy'def __init__(self, name, age):self.name = nameself.age = agedef say(self):print('%s says welcome to the oldboy to learn Python' % self.name)print(OldboyTeacher.n
)  # 自下而上依次注釋各個類中的n=xxx,然后重新運行程序,發現n的查找順序為OldboyTeacher->Foo->Bar->object->Mymeta->type
111
print(OldboyTeacher.n)
111
  • 查找順序:

    1. 先對象層:OldoyTeacher->Foo->Bar->object

    2. 然后元類層:Mymeta->type

依據上述總結,我們來分析下元類Mymeta中__call__里的self.__new__的查找

class Mymeta(type):n = 444def __call__(self, *args,**kwargs):  #self=<class '__main__.OldboyTeacher'>obj = self.__new__(self)print(self.__new__ is object.__new__)  #Trueclass Bar(object):n = 333# def __new__(cls, *args, **kwargs):#     print('Bar.__new__')class Foo(Bar):n = 222# def __new__(cls, *args, **kwargs):#     print('Foo.__new__')class OldboyTeacher(Foo, metaclass=Mymeta):n = 111school = 'oldboy'def __init__(self, name, age):self.name = nameself.age = agedef say(self):print('%s says welcome to the oldboy to learn Python' % self.name)# def __new__(cls, *args, **kwargs):#     print('OldboyTeacher.__new__')OldboyTeacher('nick',18)  # 觸發OldboyTeacher的類中的__call__方法的執行,進而執行self.__new__開始查找

總結,Mymeta下的__call__里的self.__new__在OldboyTeacher、Foo、Bar里都沒有找到__new__的情況下,會去找object里的__new__,而object下默認就有一個__new__,所以即便是之前的類均未實現__new__,也一定會在object中找到一個,根本不會、也根本沒必要再去找元類Mymeta->type中查找__new__

十一、練習

需求:使用元類修改屬性為隱藏屬性

class Mymeta(type):def __init__(self, class_name, class_bases, class_dic):# 加上邏輯,控制類Foo的創建super(Mymeta, self).__init__(class_name, class_bases, class_dic)def __call__(self, *args, **kwargs):# 加上邏輯,控制Foo的調用過程,即Foo對象的產生過程obj = self.__new__(self)self.__init__(obj, *args, **kwargs)# 修改屬性為隱藏屬性obj.__dict__ = {'_%s__%s' % (self.__name__, k): vfor k, v in obj.__dict__.items()}return obj
class Foo(object, metaclass=Mymeta):  # Foo = Mymeta(...)def __init__(self, name, age, sex):self.name = nameself.age = ageself.sex = sexobj = Foo('nick', 18, 'male')
print(obj.__dict__)
{'_Foo__name': 'egon', '_Foo__age': 18, '_Foo__sex': 'male'}

轉載于:https://www.cnblogs.com/nickchen121/p/10992975.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/386671.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/386671.shtml
英文地址,請注明出處:http://en.pswp.cn/news/386671.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux環境下使用rpm包安裝GitLab

1.安裝依賴環境 [rootgitlab ~]# yum install curl openssh-server postfix cronie 2.下載安裝GitLab包 我安裝的環境是Red Hat Enterprise Linux Server release 7.4 (Maipo) GitLab下載地址:https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7 以上是清華大學開源…

面試字節跳動Android工程師該怎么準備?深度解析,值得收藏

前言 Android高級架構師需要學習哪些知識呢&#xff1f; 下面總結一下我認為作為一個資深開發者需要掌握的技能點。 1.Android開發的幾個階段 我的10年開發生涯中&#xff0c;有9年都是做Android相關開發&#xff0c;以我個人的經歷來看&#xff0c;Android開發市場分為以下…

以JSONobject形式提交http請求

總結一下設置圖標的三種方式&#xff1a; &#xff08;1&#xff09;button屬性&#xff1a;主要用于圖標大小要求不高&#xff0c;間隔要求也不高的場合。 &#xff08;2&#xff09;background屬性&#xff1a;主要用于能夠以較大空間顯示圖標的場合。 &#xff08;3&#xf…

阿里巴巴Android面試都問些什么?系列篇

Google 為了幫助 Android 開發者更快更好地開發 App&#xff0c;推出了一系列組件&#xff0c;這些組件被打包成了一個整體&#xff0c;稱作 Android Jetpack&#xff0c;它包含的組件如下圖所示&#xff1a; 老的 support 包被整合進了 Jetpack&#xff0c;例如上圖 Foundatio…

安裝容器編排工具 Docker Compose

安裝容器編排工具 Docker Compose curl -L https://get.daocloud.io/docker/compose/releases/download/1.22.0/docker-compose-uname -s-uname -m > /usr/local/bin/docker-compose 授權&#xff1a; chmod x /usr/local/bin/docker-compose 查看安裝結果 docker-com…

docker-compose安裝elk7.1.1版本

在用docker-compose編排elk三個服務時&#xff0c;碰到了很多坑&#xff0c;網上很多資料編排的版本都不是最新的版本&#xff0c;我們這里用的 elasticsearch&#xff0c;logstash&#xff0c;kibana全都是elastic官方提供的目前最新版本7.1.1&#xff0c;高版本和低版本的一些…

阿里P8成長路線!我的頭條面試經歷分享,吊打面試官系列!

正式加入字節跳動&#xff0c;分享一點面試小經驗 今天正式入職了字節跳動。工號超吉利&#xff0c;尾數是3個6。然后辦公環境也很好&#xff0c;這邊一棟樓都是辦公區域。公司內部配備各種小零食、飲料&#xff0c;還有免費的咖啡。15樓還有健身房。而且公司包三餐來著。下午…

實驗十一:圖形界面二

實驗程序如下&#xff1a;import java.awt.*;import java.awt.event.*;import javax.swing.*;public class Example1 extends JFrame { private int add1,sub2,mul3,div4; private int op0; boolean ifOp; private String output"0"; private Button[] jbanew Button…

Docker安裝部署ELK教程 (Elasticsearch+Kibana+Logstash)

Elasticsearch 是個開源分布式搜索引擎&#xff0c;它的特點有&#xff1a;分布式&#xff0c;零配置&#xff0c;自動發現&#xff0c;索引自動分片&#xff0c;索引副本機制&#xff0c;restful風格接口&#xff0c;多數據源&#xff0c;自動搜索負載等。 Logstash 是一個完…

阿里P8面試官都說太詳細了,面試資料分享

背景 知乎客戶端中有一個自己維護的 Hybrid 框架&#xff0c;在此基礎上開發了一些 Hybrid 頁面&#xff0c;當需要前端或者客戶端開發接口的時候&#xff0c;就涉及到聯調的問題。 和一般的 前端 <> 服務端&#xff0c;或者 客戶端 <> 服務端 類似&#xff0c;前…

virtual和override

偶然間看到的題&#xff0c;借此記錄。 class Program{static void Main(string[] args){D d new D(); //第一個D是申明類&#xff0c;第二個D是實例類A a d;B b d;C c d;a.F();b.F();c.F();d.F();}class A{public virtual void F() { Console.WriteLine("A.F")…

阿里內部資料!如何試出一個Android開發者真正的水平?系列教學

前言 馬爸爸總結了一句話&#xff1a;跳槽&#xff0c;要么是錢不到位&#xff0c;要么是受了委屈。 我給自己這次的跳槽經歷做了一個分析&#xff0c;希望能對那些想換工作的朋友有所幫助。 許多朋友想換工作&#xff0c;但是對“換工作”的理解可能僅限于寫簡歷、投簡歷、…

CentOS7 linux下yum安裝redis以及使用

CentOS7 linux下yum安裝redis以及使用 1.安裝redis數據庫 1 yum install redis 2.下載fedora的epel倉庫 yum install epel-release 3.啟動redis服務 systemctl start redis 4.查看redis狀態 systemctl status redis systemctl stop redis 停止服務 systemctl restart r…

Codeforces 1182A Filling Shapes

題目鏈接&#xff1a;http://codeforces.com/problemset/problem/1182/A 思路&#xff1a;n為奇數時不可能完全填充&#xff0c;ans 0。發現若要完全填充&#xff0c;每倆列可產生倆種情況&#xff0c;所以為 ans 2n/2 AC代碼&#xff1a; 1 #include<bits/stdc.h>2 us…

阿里大神最佳總結Flutter進階學習筆記,技術詳細介紹

開頭 很多人工作了十年&#xff0c;但只是用一年的工作經驗做了十年而已。 高級工程師一直是市場所需要的&#xff0c;然而很多初級工程師在進階高級工程師的過程中一直是一個瓶頸。 移動研發在最近兩年可以說越來越趨于穩定&#xff0c;因為越來越多人開始學習Android開發&…

Mysql 允許所有IP訪問

$ mysql -u root -p Enter password:mysql> use mysql#mysql> GRANT ALL ON *.* to root192.168.1.4 IDENTIFIED BY your-root-password; mysql> GRANT ALL ON *.* to root% IDENTIFIED BY root; mysql> FLUSH PRIVILEGES;

貪心算法小結2

F-Ants 一隊螞蟻在一根水平桿上行走&#xff0c;每只螞蟻固定速度 1cm/s. 當一只螞蟻走到桿的盡頭時&#xff0c;立即從稈上掉落. 當兩只螞蟻相遇時它們會掉頭向相反的方向前進. 我們知道每只螞蟻在桿上的初始位置, 但是, 我們不知道螞蟻向哪個方向前行. 你的任務是計算所有螞蟻…

掌握這些Android開發熱門前沿知識,跳槽薪資翻倍

前言 這是一篇軟文、但是絕對不是雞湯&#xff1b;為啥不是呢&#xff1f;因為我文筆太差…偶爾矯情發發牢騷&#xff08;勿噴&#xff09; 說說程序猿行業 現在社會上給IT行業貼上了幾個標簽&#xff1a;高薪、高危、高大上、禿頂&#xff08;哈哈&#xff09;。這些標簽我…

linux環境-docker安裝rabbitmq

1、進入docker hub鏡像倉庫地址&#xff1a;https://hub.docker.com/ 2、搜索rabbitMq&#xff0c;進入官方的鏡像&#xff0c;可以看到以下幾種類型的鏡像&#xff1b;我們選擇帶有“mangement”的版本&#xff08;包含web管理頁面&#xff09;&#xff1b; 3、拉取鏡像 doc…

揭秘ARouter路由機制,源碼+原理+手寫框架

前言 每個程序員都有一個夢想&#xff0c;那就是進一線互聯網公司深造&#xff0c;不要跟我說你不想進去&#xff0c;如果給你一個這樣的平臺&#xff0c;不管是薪資待遇還是接觸的高度來說&#xff0c;對我們程序員來說都是一個機會&#xff0c;我以前有一個同事&#xff0c;…