1、一切皆對象 一、 類也是對象 在大多數編程語言中,類就是一組用來描述如何生成一個對象的代碼段,在Python中這一點仍然成立。但是,Python中的類還遠不止如此。類同樣也是一種對象。只要你使用關鍵字class,Python解釋器在執行的時候就會創建一個對象。下麵的代碼段: class MyCl ...
目錄
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 = 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 = 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