目錄
- 1、一切皆物件
- 一、 類也是物件
- 二、type和object
- 三、元類、類、實體
- 2、metaclass
- 一、type--“造物的上帝”
- 二、metaclass屬性
- 3、應用
- 一、實作ORM
- 二、單例
- 1)、__new__方法實作單例
- 2)、元類實作單例
- 三、動態加載
- 4、總結
- 參考:
1、一切皆物件
一、 類也是物件
在大多數編程語言中,類就是一組用來描述如何生成一個物件的代碼段,在Python中這一點仍然成立,但是,Python中的類還遠不止如此,類同樣也是一種物件,只要你使用關鍵字class,Python解釋器在執行的時候就會創建一個物件,下面的代碼段:
class MyClass(object):
pass
將在記憶體中創建一個物件,名字就是MyClass,這個物件(類)自身擁有創建物件(類實體)的能力,而這就是為什么它是一個類的原因,但是,它的本質仍然是一個物件,于是你可以對它做如下的操作:
你可以將它賦值給一個變數, 你可以拷貝它, 你可以為它增加屬性, 你可以將它作為函式引數進行傳遞,
在 python 中有兩種物件:
- 型別(類,新的版本中類和型別是一樣的)物件:可以被實體化和繼承
- 非型別(實體)物件:不可以被實體和繼承
python 中一切皆為物件:
在python里,
int整形是物件,整數2也是物件,你定義的函式啊,類啊都是物件,你定義的變數也是物件,總之,你在python里能用到的都可以稱之為物件,
二、type和object
明白了python中一切皆物件之后,再了解一下python中物件之間的兩種關系
面向物件體系中的兩種關系:
- 父子關系:通常描述為“子類是一種父類”
- 型別實體關系:這種關系存在于兩個物件中,其中一個物件(實體)是另一個物件(型別)的具體實作,
python中萬物皆物件,一個python物件可能擁有兩個屬性,__class__ 和 __bases__,__class__ 表示這個物件是誰創建的,__bases__ 表示一個類的父類是誰們,__class__和type()函式效果一樣,
class MyClass:
pass
MyClass.__class__
#Out: type
MyClass.__bases__
#Out: (object,)
int.__class__
#Out: type
int.__bases__
#Out: (object,)
object.__class__ #object是type的實體,object創建自type
#Out: type
object.__bases__ #object沒有超類,它本身是所以物件的超類
#Out: ()
type.__class__ #type創建自本身
#Out: type
type.__bases__ #type繼承自object,即object是type的超類
#Out: (object,)
- type為物件的頂點,所有物件都創建自type,
- object為類繼承的頂點,所有類都繼承自object,
三、元類、類、實體
- object是所有類的超類,而且type也是繼承自object;所有物件創建自type,而且object也是type的實體,
- “type是object的型別,同時,object又是type的超類”,那到底是先有object還是先有type呢?這就像“先有雞還是先有蛋問題”,
- object和type是python中的兩個源物件,事實上,它們是互相依賴對方來定義,所以它們不能分開而論,
- 通過這兩個源物件可以繁育出一堆物件:list、tuple、dict等,元類就是這些類的類,這些類是元類的實體,
l1=list()
l1.__class__ #l是list的實體
#Out: list
list.__class__ #list是type的實體
#Out: type
l1.__bases__ #實體沒有超類
#AttributeError: 'list' object has no attribute '__bases__'
list.__bases__
#Out: (object,)
l2=[1,2,3] #l1是利用"型別名()"的方式創建實體,l2是利用python內置型別創造實體,比l1的創建速度要快
2、metaclass
一、type--“造物的上帝”
就像str是用來創建字串物件的類,int是用來創建整數物件的類,而type就是創建類物件的類,
類本身不過是一個名為 type 類的實體,在 Python的型別世界里,type這個類就是造物的上帝,
用戶自定義類,只不過是type類的__call__運算子的多載,當我們定義一個類的陳述句結束時,
真正發生的情況,是 Python 呼叫 type 的__call__運算子,簡單來說,當你定義一個類時,寫成下面時:
class MyClass:
data = https://www.cnblogs.com/fengqiang626/p/1
Python 真正執行的是下面這段代碼:
class = type(classname, superclasses, attributedict)
這里等號右邊的type(classname, superclasses, attributedict),就是 type 的__call__運算子多載,它會進一步呼叫:
type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(class, classname, superclasses, attributedict)
當然,這一切都可以通過代碼驗證,比如下面這段代碼示例:
class MyClass:
data = https://www.cnblogs.com/fengqiang626/p/1
instance = MyClass()
MyClass, instance
#Out: (__main__.MyClass, <__main__.MyClass at 0x4eac358>)
MyClass = type('MyClass', (), {'data': 1})
instance = MyClass()
MyClass, instance
#Out: (__main__.MyClass, <__main__.MyClass at 0x4f915c0>)
instance.data
#Out: 1
由此可見,正常的 MyClass 定義,和手工去呼叫 type運算子的結果是一樣的,
二、metaclass屬性
metaclass 是 type 的子類,通過替換 type的__call__運算子多載機制,“超越變形”正常的類,
其實,理解了以上幾點,我們就會明白,正是 Python 的類創建機制,給了 metaclass 大展身手的機會,
一旦你把一個型別 MyClass 的 metaclass 設定成 MyMeta,MyClass 就不再由原生的 type創建,而是會呼叫 MyMeta 的__call__運算子多載,
class = type(classname, superclasses, attributedict)
# 變為了
class = MyMeta(classname, superclasses, attributedict)
class MyMeta(type):
def __new__(cls, *args, **kwargs):
print('===>MyMeta.__new__')
print(cls.__name__)
return super().__new__(cls, *args, **kwargs)
def __init__(self, classname, superclasses, attributedict):
super().__init__(classname, superclasses, attributedict)
print('===>MyMeta.__init__')
print(self.__name__)
print(attributedict)
print(self.tag)
def __call__(self, *args, **kwargs):
print('===>MyMeta.__call__')
obj = self.__new__(self, *args, **kwargs)
self.__init__(self, *args, **kwargs)
return obj
class Foo(object, metaclass=MyMeta):
tag = '!Foo'
def __new__(cls, *args, **kwargs):
print('===>Foo.__new__')
return super().__new__(cls)
def __init__(self, name):
print('===>Foo.__init__')
self.name = name
print('test start')
foo = Foo('test')
print('test end')
#輸出
#===>MyMeta.__new__
#MyMeta
#===>MyMeta.__init__
#Foo
#{'tag': '!Foo', '__module__': '__main__', '__init__': <function Foo.__init__ at 0x00000000010F7950>, '__new__': <function Foo.__new__ at 0x00000000010F78C8>, '__qualname__': 'Foo'}
#!Foo
#test start
#===>MyMeta.__call__
#===>Foo.__new__
#===>Foo.__init__
#test end
在創建Foo類的時候,python做了如下操作,
- Foo中有metaclass這個屬性嗎?如果是,Python會在記憶體中通過metaclass創建一個名字為Foo的類物件(我說的是類物件,請緊跟我的思路),
- 如果Python沒有找到metaclass,它會繼續在父類中尋找metaclass屬性,并嘗試做和前面同樣的操作,
- 如果Python在任何父類中都找不到metaclass,它就會在模塊層次中去尋找metaclass,并嘗試做同樣的操作,
- 如果還是找不到metaclass,Python就會用內置的type來創建這個類物件,
現在的問題就是,你可以在metaclass中放置些什么代碼呢?
答案就是:可以創建一個類的東西,那么什么可以用來創建一個類呢?type,或者任何使用到type或者子類化type的東西都可以,用類實作可以(比如上面這個例子),用函式實作也可以,但是metaclass必須回傳一個類,
def MyMetaFunction(classname, superclasses, attributedict):
attributedict['year'] = 2019
return type(classname, superclasses, attributedict)
class Foo(object, metaclass=MyMetaFunction):
tag = '!Foo'
def __new__(cls, *args, **kwargs):
print('===>Foo.__new__')
return super().__new__(cls)
def __init__(self, name):
print('===>Foo.__init__')
self.name = name
print('test start')
foo = Foo('test')
print('name:%s,tag:%s,year:%s' % (foo.name, foo.tag, foo.year))
print('test end')
#輸出
#test start
#===>Foo.__new__
#===>Foo.__init__
#name:test,tag:!Foo,year:2019
#test end
把上面的例子運行完之后就會明白很多了,正常情況下我們在父類中是不能對子類的屬性進行操作,但是元類可以,換種方式理解:元類、裝飾器、類裝飾器都可以歸為元編程(參考自 python-cook-book 中的一句話),
3、應用
一、實作ORM
我們通過創建一個類似Django中的ORM來熟悉一下元類的使用,通過元類用來創建API是非常好的選擇,使用元類的撰寫很復雜但使用者可以非常簡潔的呼叫API
#一、首先定義Field類,它負責保存資料庫表的欄位名和欄位型別
class Field(object):
def __init__(self, name, column_type):
self.name = name
self.colmun_type = column_type
def __str__(self):
return '<%s:%s>' % (self.__class__.__name__, self.name)
class StringField(Field):
def __init__(self, name):
super().__init__(name, 'varchar(100)')
class IntegerField(Field):
def __init__(self, name):
super().__init__(name, 'bigint')
#二、定義元類,控制Model物件的創建
class ModelMetaClass(type):
def __new__(cls, name, bases, attrs):
if name == 'Model':
return super().__new__(cls, name, bases, attrs)
mappings = dict()
for k,v in attrs.items():
#保持類屬性和列的映射關系到mappings字典
if isinstance(v,Field):
print('Found mapping:%s==>%s' % (k, v))
mappings[k] = v
for k in mappings.keys(): #將類屬性移除,是定義的類欄位不污染User類屬性,只在實體中可以訪問這些key
attrs.pop(k)
attrs['__table__'] = name.lower() #假設表名為類名的小寫,創建類時添加一個__table__屬性
attrs['__mappings__'] = mappings #保持屬性和列的關系映射,創建類時添加一個__mappings__屬性
return super().__new__(cls, name, bases, attrs)
#三、Model基類
class Model(dict, metaclass=ModelMetaClass):
def __init__(self, **kwargs):
super().__init__(**kwargs)
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 = []
params = []
args = []
for k,v in self.__mappings__.items():
fields.append(v.name)
params.append('?')
args.append(getattr(self,k,None))
sql = 'insert into %s (%s) values (%s)' % (self.__table__,','.join(fields),','.join(params))
print('SQL:%s' % sql)
print('ARGS:%s' % str(args))
#我們想創建類似Django的ORM,只要定義欄位就可以實作對資料庫表和欄位的操作
#最后、我們使用定義好的ORM介面,使用起來非常簡單
class User(Model):
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
user = User(id=1,name='Job',email='[email protected]',password='pw')
user.save()
#輸出
#Found mapping:password==><StringField:password>
#Found mapping:id==><IntegerField:id>
#Found mapping:email==><StringField:email>
#Found mapping:name==><StringField:username>
#SQL:insert into user (email,id,password,username) values (?,?,?,?)
#ARGS:['[email protected]', 1, 'pw', 'Job']
二、單例
依照Python官方檔案的說法,__new__方法主要是當你繼承一些不可變的class時(比如int, str, tuple), 提供給你一個自定義這些類的實體化程序的途徑,還有就是實作自定義的metaclass,
簡單來說,單例模式的原理就是通過在類屬性中添加一個單例判定位ins_flag,通過這個flag判斷是否已經被實體化過了,如果被實體化過了就回傳該實體,
1)、__new__方法實作單例
class Singleton:
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'): #_instance是類(Singleton)物件的一個屬性
cls._instance= super().__new__(cls, *args, **kwargs)
return cls._instance #類的__new__方法之后,必須生成本類的實體(注意是“本類”的實體)才能自動呼叫本類的__init__方法進行初始化,否則不會自動呼叫__init__
class SubSingleton(Singleton):
pass
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)
ss1 = SubSingleton()
ss2 = SubSingleton()
print(ss1 is ss2)
#輸出
#True
#True
因為重寫__new__方法,所以繼承至Singleton的類,在不重寫__new__的情況下都將是單例模式, _instance(單下劃線開頭)屬性換成__instance(雙下劃線開頭)會得到不一樣的結果,將無法實作單例模式,如果__instance(雙下劃線開頭)屬性就變成了私有的(其實變成了_Singleton__instance),
2)、元類實作單例
class SingletonMeta(type):
def __init__(self,*args,**kwargs):
self.__instance = None #這是一個私有屬性來保存屬性,而不會污染Singleton類(其實還是會污染,只是無法直接通過__instance屬性訪問)
super().__init__(*args,**kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super().__call__(*args, **kwargs)
return self.__instance
class Singleton(metaclass=SingletonMeta): #在代碼執行到這里的時候,元類中的__new__方法和__init__方法其實已經被執行了,而不是在Singleton實體化的時候執行,且僅會執行一次,
pass
class SubSingleton(metaclass=SingletonMeta):
pass
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)
ss1 = SubSingleton()
ss2 = SubSingleton()
print(ss1 is ss2)
#輸出
#True
#True
- 我們知道元類(SingletonMeta)生成的實體是一個類(Singleton),而這里我們僅僅需要對這個實體(Singleton)增加一個屬性(__instance)來判斷和保存生成的單例,想想也知道為一個類添加一個屬性當然是在__init__中實作了,
- 關于__call__方法的呼叫,因為Singleton是SingletonMeta的一個實體,所以Singleton()這樣的方式就呼叫了SingletonMeta的__call__方法,
三、動態加載
YAML是一個家喻戶曉的 Python 工具,可以方便地序列化 / 逆序列化結構資料,YAMLObject 的一個超越變形能力,就是它的任意子類支持序列化和反序列化(serialization & deserialization),比如說下面這段代碼(https://pyyaml.org/wiki/PyYAMLDocumentation):
class Monster(yaml.YAMLObject):
yaml_tag = u'!Monster'
def __init__(self, name, hp, ac, attacks):
self.name = name
self.hp = hp
self.ac = ac
self.attacks = attacks
def __repr__(self):
return "%s(name=%r, hp=%r, ac=%r, attacks=%r)" % (
self.__class__.__name__, self.name, self.hp, self.ac,
self.attacks)
yaml.load("""
--- !Monster
name: Cave spider
hp: [2,6] # 2d6
ac: 16
attacks: [BITE, HURT]
""")
Monster(name='Cave spider', hp=[2, 6], ac=16, attacks=['BITE', 'HURT'])
print yaml.dump(Monster(
name='Cave lizard', hp=[3,6], ac=16, attacks=['BITE','HURT']))
# 輸出
!Monster
ac: 16
attacks: [BITE, HURT]
hp: [3, 6]
name: Cave lizard
這里 YAMLObject 的特異功能體現在哪里呢?
你看,呼叫統一的 yaml.load(),就能把任意一個 yaml 序列載入成一個 Python Object;而呼叫統一的 yaml.dump(),就能把一個 YAMLObject 子類序列化,對于 load() 和 dump() 的使用者來說,他們完全不需要提前知道任何型別資訊,這讓超動態配置編程成了可能,聽大神說在他的實戰經驗中,許多大型專案都需要應用這種超動態配置的理念,
比方說,在一個智能語音助手的大型專案中,我們有 1 萬個語音對話場景,每一個場景都是不同團隊開發的,作為智能語音助手的核心團隊成員,我不可能去了解每個子場景的實作細節,
在動態配置實驗不同場景時,經常是今天我要實驗場景 A 和 B 的配置,明天實驗 B 和 C 的配置,光組態檔就有幾萬行量級,作業量不可謂不小,而應用這樣的動態配置理念,我就可以讓引擎根據我的文本組態檔,動態加載所需要的 Python 類,
對于 YAML 的使用者,這一點也很方便,你只要簡單地繼承 yaml.YAMLObject,就能讓你的 Python Object 具有序列化和逆序列化能力,是不是相比普通 Python 類,有一點“變態”,有一點“超越”?
YAML 的這種動態序列化 / 逆序列化功能正是用metaclass 實作的,
我們這里只看 YAMLObject 的 load() 功能,簡單來說,我們需要一個全域的注冊器,讓 YAML 知道,序列化文本中的 !Monster 需要載入成 Monster 這個 Python 型別,
一個很自然的想法就是,那我們建立一個全域變數叫 registry,把所有需要逆序列化的 YAMLObject,都注冊進去,比如下面這樣:
registry = {}
def add_constructor(target_class):
registry[target_class.yaml_tag] = target_class
然后,在 Monster 類定義后面加上下面這行代碼:
add_constructor(Monster)
但這樣的缺點也很明顯,對于 YAML 的使用者來說,每一個 YAML 的可逆序列化的類 Foo 定義后,都需要加上一句話,add_constructor(Foo),這無疑給開發者增加了麻煩,也更容易出錯,畢竟開發者很容易忘了這一點,
那么,更優的實作方式是什么樣呢?如果你看過 YAML 的原始碼,就會發現,正是 metaclass 解決了這個問題,
# Python 2/3 相同部分
class YAMLObjectMetaclass(type):
def __init__(cls, name, bases, kwds):
super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
# 省略其余定義
# Python 3
class YAMLObject(metaclass=YAMLObjectMetaclass):
yaml_loader = Loader
# 省略其余定義
# Python 2
class YAMLObject(object):
__metaclass__ = YAMLObjectMetaclass
yaml_loader = Loader
# 省略其余定義
你可以發現,YAMLObject 把 metaclass 都宣告成了YAMLObjectMetaclass,利用 YAMLObjectMetaclass 的__init__方法,為所有 YAMLObject 子類偷偷執行add_constructor(),在 YAMLObjectMetaclass 中,下面這行代碼就是魔法發生的地方:
cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
YAML 應用 metaclass,攔截了所有 YAMLObject 子類的定義,也就說說,在你定義任何 YAMLObject 子類時,Python 會強行插入運行下面這段代碼,把我們之前想要的add_constructor(Foo)給自動加上,
cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
所以 YAML 的使用者,無需自己去手寫add_constructor(Foo) ,怎么樣,是不是其實并不復雜?
4、總結
正常情況下我們在父類中是不能對子類的屬性進行操作,但是元類可以,這就使得程式代碼的維護變得困難,metaclass 是 Python 的黑魔法之一,在掌握它之前不要輕易嘗試,感覺上python的規范原則很松,但這也使得python更靈活,對代碼的撰寫理應由程式員自己負責,自己寫的代碼還是得自己負責啊,
元類、裝飾器、類裝飾器都可以歸為元編程,它們之間有一些相似點,還是在實際的應用中選擇比較,使用合適的工具進行編程吧,
參考:
https://www.cnblogs.com/tkqasn/p/6524879.html
https://time.geekbang.org/column/article/101288
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/85041.html
標籤:Python
上一篇:136只出現一次的數字
下一篇:python的串列生成式
