python之理解元類
1桥滨、類也是對象
在大多數(shù)編程語言中摆碉,類就是一組用來描述如何生成對象的代碼段。在python中顾画,這一點仍然成立取劫。
class BaseObject(object):
pass
demo_object = BaseObject()
print(demo_object)
print(BaseObject)
結(jié)果:
<__main__.BaseObject object at 0x0000000001DC8780>
<class '__main__.BaseObject'>
demo_object顯而易見的是一個實例對象,那類BaseObject也是一個對象研侣?對的谱邪,是一個對象,只要你使用關(guān)鍵字class庶诡,python解釋器在執(zhí)行的時候就會創(chuàng)建這個一個對象惦银,如上:<class '__main__.BaseObject'>
。
解釋器將在內(nèi)存中創(chuàng)建一個對象末誓,名字就叫做BaseObject扯俱。這個對象(類對象BaseObject)擁有創(chuàng)建對象(實例對象)能力。但是喇澡,它的本質(zhì)仍舊是一個對象蘸吓,針對對象類型,我們通常有下面的一些操作:
- 將對象復(fù)制給一個變量
- 拷貝對象
- 為對象增加屬性
- 將對象作為函數(shù)參數(shù)傳遞
class BaseObject(object):
pass
def echo(o):
print(o)
echo(BaseObject)
print(hasattr(BaseObject, 'new_attr'))
BaseObject.new_attr = 'daocoder'
print(hasattr(BaseObject, 'new_attr'))
print(BaseObject.new_attr)
a = BaseObject
print(a)
結(jié)果:
<class '__main__.BaseObject'>
False
True
daocoder
<class '__main__.BaseObject'>
2撩幽、動態(tài)的創(chuàng)建類
因為類也是對象,那么你可以在運行的時候動態(tài)創(chuàng)建他們箩艺,就像一般實例化對象一樣窜醉。那么,你可以在函數(shù)中創(chuàng)建類艺谆,使用class關(guān)鍵字即可榨惰。
def create_object(name):
if name == 'Foo':
class Foo(object):
pass
return Foo
elif name == 'Bar':
class Bar(object):
pass
return Bar
MyClass = create_object('Foo')
print(MyClass)
print(MyClass())
結(jié)果:
<class '__main__.create_object.<locals>.Foo'>
<__main__.create_object.<locals>.Foo object at 0x0000000002B745F8>
由運行結(jié)果可以看出確實可以實現(xiàn)這樣的需求,但是這樣還是需要自己編寫整個代碼静汤,不夠動態(tài)琅催,那么我們可以想到對于一門語言,肯定可以自動創(chuàng)建對象虫给,既然類也是一個對象藤抡,那么它肯定也是通過什么創(chuàng)建出來的。
當我們利用class關(guān)鍵字時抹估,python解釋器會自動操作這個類對象缠黍,那么和python實現(xiàn)的大多數(shù)事情一樣,python也提供了手動創(chuàng)建的方法药蜻。
回歸到我們學(xué)習(xí)python函數(shù)的基礎(chǔ)瓷式,有一個內(nèi)建函數(shù)用來獲取各個變量的類型type()替饿,就像下面這樣。
print(type(1))
print(type('1'))
print(type(True))
print(type(range(1)))
print(type([]))
print(type({}))
print(type(()))
print(type(MyClass))
結(jié)果:
<class 'int'>
<class 'str'>
<class 'bool'>
<class 'range'>
<class 'list'>
<class 'dict'>
<class 'tuple'>
<class 'type'>
注意看最后一個贸典,MyClass的類型竟然是type视卢。那么這個type到底是啥東西。
3廊驼、利用type創(chuàng)建類
type還有一個完全不同的功能据过,就是動態(tài)創(chuàng)建類。
type可以接受一個類的描述符作為參數(shù)蔬充,然后返回一個類蝶俱。(通常根據(jù)傳入?yún)?shù)的不同,同一個函數(shù)可以擁有兩種完全不同的用法饥漫,是很忌諱的事情榨呆,但python這里是為了保持向后兼容性)。
所以庸队,type可以像下面這樣工作:
type(類名, 由父類名稱組成的元組(繼承情況积蜻,可為空), 包含字典的屬性(名稱和值))
如下面的代碼:
class Test(object):
pass
print(Test)
Dog = type('Dog', (), {})
print(Dog)
結(jié)果:
<class '__main__.Test'>
<class '__main__.Dog'>
再看幫助:
help(Test)
help(Dog)
結(jié)果:
Help on class Test in module __main__:
class Test(builtins.object)
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
Help on class Dog in module __main__:
class Dog(builtins.object)
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
一樣的彻消。
4竿拆、利用type創(chuàng)建帶有屬性、方法的類
1宾尚、創(chuàng)建帶屬性的類
Animal = type('Animal', (), {'name': 'daocoder'})
animal = Animal()
print(animal.name)
結(jié)果:
daocoder
2丙笋、創(chuàng)建帶屬性、方法且繼承的類
Animal = type('Animal', (), {'name': 'daocoder'})
animal = Animal()
print(animal.name)
def talk(obj, age):
print('my name is %s and %s years old' % (obj.name, age))
Dog = type('Dog', (Animal,), {'name': 'huang', 'talk': talk})
dog = Dog()
# dog.name = 'da huang'
dog.talk(10)
結(jié)果:
daocoder
my name is huang and 10 years old
這里注意:
- type的第2個參數(shù)煌贴,元組中是父類的名字御板,不是字符串。
- 添加的屬性是類屬性牛郑,不是實例屬性怠肋。
3、添加靜態(tài)方法和類方法
Animal = type('Animal', (), {'name': 'daocoder'})
animal = Animal()
print(animal.name)
def talk(obj, age):
print('my name is %s and %s years old' % (obj.name, age))
@staticmethod
def eat(food):
print('i can eat %s' % food)
@classmethod
def sleep(self, dt):
print('%s can sleep %s hours' % (self.name, dt))
Dog = type('Dog', (Animal,), {'name': 'huang', 'talk': talk, 'eat': eat, 'sleep': sleep})
dog = Dog()
# dog.name = 'da huang'
dog.talk(10)
dog.eat('meat')
dog.sleep(8)
結(jié)果:
daocoder
my name is huang and 10 years old
i can eat meat
huang can sleep 8 hours
5淹朋、什么是元類
元類就是用來創(chuàng)建類(對象)的笙各,可以理解為元類就是創(chuàng)建類的類。
MyClass = type('MetaClass', (), {})
# class MyClass(object):
# pass
MyObject = MyClass()
print(MyClass)
print(MyObject)
結(jié)果:
<class '__main__.MetaClass'>
<__main__.MetaClass object at 0x00000000023E9D30>
上面利用type創(chuàng)建了一個MyClass類(對象)础芍,然后基于它實例化一個對象MyObject杈抢。注釋的基本形式結(jié)果一致。
這里實際上type函數(shù)就是一個元類仑性。type就是在python解釋器用來創(chuàng)建所有類的元類春感。我們可以這么理解,類比str是創(chuàng)建字符串對象的類(對象),int是創(chuàng)建整數(shù)對象的類(對象)鲫懒,那么type就是創(chuàng)建對象的類(對象)嫩实。python中,所有的東西都是對象窥岩,包括整數(shù)甲献、字符串、函數(shù)及類颂翼。它們?nèi)菍ο蠡稳鳎覐囊粋€類中創(chuàng)建而來,這個類就是type朦乏。
print(type(1))
print(type('1'))
print(type(True))
print(type(int.__class__))
print(type(str.__class__))
print(type(bool.__class__))
print(type(object.__class__))
print(type(int.__class__.__class__))
print(type(str.__class__.__class__))
print(type(bool.__class__.__class__))
print(type(object.__class__.__class__))
結(jié)果:
<class 'int'>
<class 'str'>
<class 'bool'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
從上面可以看出元類就是創(chuàng)建類這種對象的東西球及,type是python內(nèi)建的元類,那么我們當然可以創(chuàng)建自己的元類啊呻疹。
6吃引、metaclass屬性
我們可以定義一個類的時候為其添加一個metaclass屬性。
class Bar(type):
pass
class Foo(object, metaclass=Bar):
# __metaclass__ = Bar # python2的寫法
pass
當我們運行這段代碼時刽锤,python進行了如下操作:
1镊尺、Foo中有metaclass的屬性么?如有并思,py將通過metaclass創(chuàng)建一個名字為Foo的類(對象)
2庐氮、如果沒有找到metaclass,它會繼續(xù)在其繼承的父類中去尋找metaclass屬性宋彼,并嘗試和之前相同的操作弄砍。
3、如果python在父類中都找不到metaclass输涕,python會在模塊層次去尋找metaclass输枯,并嘗試做和前面一樣的操作。
4占贫、如果還是找不到__metaclass,那么python將會用內(nèi)置的type去創(chuàng)建這個對象先口。
那么問題就顯而易見了型奥,metaclass指什么,答案就是可以創(chuàng)建一個類的東西碉京。創(chuàng)建一個類需要type或繼承自type的子類(對象)厢汹。
7、自定義元類
元類的主要目的就是為了創(chuàng)建類的時候能夠自動的改變類谐宙。
舉一個幫助理解的例子:你決定在你的模塊里所有的類的屬性都應(yīng)該是大寫形式烫葬。有一些辦法可以辦法,其中一種就是在模塊級別設(shè)定metaclass。采用這種方法搭综,這個模塊中的所有類都會通過這個元類來創(chuàng)建垢箕,我們需要做的就是在元類中把所有索性全部改為大寫就可以了。
值得一提的是兑巾,metaclass實際上可以被任意調(diào)用条获,它并不需要一個正式的類,所以下面先以一個函數(shù)來開始蒋歌。
def upper_attr(class_name, parent_class, attr):
new_attr = {}
print(class_name)
print(parent_class)
print(attr)
for name, value in attr.items():
if not name.startswith('__'):
new_attr[name.upper()] = value
return type(class_name, parent_class, new_attr)
class Bar(object):
pass
class MyClass(object):
pass
class Foo(MyClass, Bar, metaclass=upper_attr):
# __metaclass__ = Bar
test = 'test'
foo = Foo()
print(foo)
print(hasattr(foo, 'test'))
print(hasattr(foo, 'TEST'))
結(jié)果:
Foo
(<class '__main__.MyClass'>, <class '__main__.Bar'>)
{'__module__': '__main__', '__qualname__': 'Foo', 'test': 'test'}
<__main__.Foo object at 0x0000000002BA4748>
False
True
如上面代碼帅掘,class_name是當前類名,parent_class為要繼承的父類堂油,attr為字典形式所有的類屬性修档。
下面再考慮用一個真正的class作為元類。
class Bar(object):
pass
class MyMetaClass(type):
def __new__(cls, class_name, class_parents, class_attr):
print(cls)
new_attr = {}
for name, value in class_attr.items():
if not name.startswith('__'):
new_attr[name.upper()] = value
# return type(class_name, class_parents, new_attr)
return type.__new__(cls, class_name, class_parents, new_attr)
class Foo(Bar, metaclass=MyMetaClass):
# __metaclass__ = Bar
test = 'test'
foo = Foo()
print(foo)
print(hasattr(foo, 'test'))
print(hasattr(foo, 'TEST'))
從上面的代碼可以理解府框,元類的作用基本就是攔截類的創(chuàng)建吱窝,然后修改這個待創(chuàng)建的類,最后返回修改后的類寓免。
下一篇會理解new癣诱、init、call等魔術(shù)方法袜香。
最后為什么要使用元類撕予,用python界的領(lǐng)袖Tim Peters的話來說:元類是深度的魔法,99%的人應(yīng)該不為此操心什么蜈首。如果你想搞清楚是否需要用到元類实抡,那么就不需要用到它。那些實際用到元類的人很清楚他們需要做什么欢策,而且根本不需要去解釋為什么要用元類吆寨。
8、利用元類實現(xiàn)ORM
8.1 ORM是什么
ORM(object relational mapping)對象關(guān)系印射踩寇,是py后端框架django的核心思想啄清。
通俗點理解就是通過創(chuàng)建一個實例對象,用創(chuàng)建它的類當作表名俺孙,用創(chuàng)建它的類屬性作為表的字段辣卒,當對這個實例對象進行操作時,能夠生成對應(yīng)的sql語句睛榄。
下面一個簡單的demo:
class User(object):
uid = ('uid', "int unsigned")
name = ('username', "varchar(30)")
email = ('email', "varchar(30)")
password = ('password', "varchar(30)")
u = User(uid=12345, name='Michael', email='test@orm.org', password='my-pwd')
u.save()
# 對應(yīng)如下sql語句
# insert into User (username, email, password, uid) values ('Michael', 'test@orm.org', 'my-pwd', 12345)
將實現(xiàn)的效果如上荣茫,即ORM的作用就是:讓開發(fā)者操作數(shù)據(jù)庫的時候,能夠像操作對象時通過其屬性賦值等操作一樣簡單场靴。
8.2 通過元類實現(xiàn)ORM中的insert功能
編寫底層模塊的第一步就是先把調(diào)用接口寫出來啡莉,比如想寫一個ORM框架港准,想定義一個User類來操作相應(yīng)的數(shù)據(jù)庫表user,需要開發(fā)者寫的代碼類似這個樣子:
class User(Model):
id = IntegerField('id')
name = StringField('name')
age = IntegerField('age')
address = StringField('address')
# 創(chuàng)建一個user實例
user = User(id=1, name='daocoder', age=27, address='anhui')
# 保存到數(shù)據(jù)庫中
user.save()
其中父類Model和屬性類型IntegerField和StringField都是由ORM框架提供咧欣,剩下的方法由metaclass完成浅缸,即創(chuàng)建Model的元類。
下面就按照上面的思路來實現(xiàn)這個簡單的ORM框架该押。
1疗杉、首先定義Field類,它負責(zé)定義數(shù)據(jù)表中字段名和字段類型長度等蚕礼。
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)
2烟具、在Field的基礎(chǔ)上再定義各種數(shù)據(jù)類型,StringField和IntegerField等奠蹬。
class IntegerField(Field):
def __init__(self, name, column_type='int(11)'):
super(IntegerField, self).__init__(name, column_type)
class StringField(Field):
def __init__(self, name, column_type='varchar(100)'):
super(StringField, self).__init__(name, column_type)
3朝聋、下面開始定義基類Model模型。
class Model(dict, metaclass=ModelMetaClass):
def __init__(self, **kwargs):
super(Model, self).__init__(**kwargs)
def __getattr__(self, key):
try:
return str(self[key])
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = str(value)
def save(self):
fields = []
args = []
for k, v in self.__mappings__.items():
print(k, v, v.name, v.column_type)
fields.append(v.name)
args.append(getattr(self, k))
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))
4囤躁、編寫元類ModelMetaclass
class ModelMetaClass(type):
def __new__(cls, class_name, class_parents, class_attr):
if class_name == 'Model':
return type.__new__(cls, class_name, class_parents, class_attr)
print('found model %s' % class_name)
mappings = {}
for name, value in class_attr.items():
if isinstance(value, Field):
mappings[name] = value
for k in mappings.keys():
class_attr.pop(k)
class_attr['__mappings__'] = mappings
class_attr['__table__'] = class_name.lower()
return type.__new__(cls, class_name, class_parents, class_attr)
全部代碼:
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)
class IntegerField(Field):
def __init__(self, name, column_type='int(11)'):
super(IntegerField, self).__init__(name, column_type)
class StringField(Field):
def __init__(self, name, column_type='varchar(100)'):
super(StringField, self).__init__(name, column_type)
class ModelMetaClass(type):
def __new__(cls, class_name, class_parents, class_attr):
if class_name == 'Model':
return type.__new__(cls, class_name, class_parents, class_attr)
print('found model %s' % class_name)
mappings = {}
for name, value in class_attr.items():
if isinstance(value, Field):
mappings[name] = value
for k in mappings.keys():
class_attr.pop(k)
class_attr['__mappings__'] = mappings
class_attr['__table__'] = class_name.lower()
return type.__new__(cls, class_name, class_parents, class_attr)
class Model(dict, metaclass=ModelMetaClass):
def __init__(self, **kwargs):
super(Model, self).__init__(**kwargs)
def __getattr__(self, key):
try:
return str(self[key])
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = str(value)
def save(self):
fields = []
args = []
for k, v in self.__mappings__.items():
print(k, v, v.name, v.column_type)
fields.append(v.name)
args.append(getattr(self, k))
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))
class User(Model):
id = IntegerField('id')
name = StringField('name')
age = IntegerField('age')
address = StringField('address')
user = User(id='1', name='daocoder', age=27, address='anhui')
user.save()
跑起來:
found model User
id <IntegerField:id> id int(11)
name <StringField:name> name varchar(100)
age <IntegerField:age> age int(11)
address <StringField:address> address varchar(100)
SQL: insert into user (id,name,age,address) values (1,daocoder,27,anhui)
ARGS: ['1', 'daocoder', '27', 'anhui']
解釋一番:
User類定義繼承自父類Model冀痕,且有4個屬性,4個屬性分別繼承自IntegerField和StringField狸演,這兩個繼承自Field言蛇,這個不談。聚焦Model。
1、實例化User時局荚,去找父類Model低淡,發(fā)現(xiàn)父類擁有metaclass屬性值為ModelMetaClass父腕,即它是由一個自定義的元類來創(chuàng)建的類,向上尋找ModelMetaClass,這個類是繼承自type。需要先創(chuàng)建它的實例對象民宿。調(diào)用其靜態(tài)方法new,這里面4個參數(shù)(cls, class_name, class_parents, class_attr)像鸡,分別為ModelMetaClass的類對象活鹰、Model類名、父類(dict, )元組只估、自身內(nèi)置屬性志群。類名為Model時,直接創(chuàng)建type.__new__(cls, class_name, class_parents, class_attr)
并返回仅乓。再調(diào)用Model類的init方法,調(diào)用了父類dict的init的方法蓬戚。父類Model作為類對象創(chuàng)建完成夸楣。
2、開始User類對象的創(chuàng)建,Model已有豫喧,然后開始創(chuàng)建User石洗,還是向上找到了ModelMetaClass,這時的4個參數(shù)分別是(cls, class_name, class_parents, class_attr)紧显,分別為ModelMetaClass的類對象讲衫、User類名、父類(Model, )元組孵班、自身內(nèi)置屬性包含id涉兽,name,age篙程,address等枷畏。然后判斷類名不是Model,繼續(xù)向下虱饿,將User屬性遍歷拥诡,其實例自Field的屬性封裝為User類對象的mappings屬性,類名User為User類對象的table屬性氮发。
3渴肉、調(diào)用實例對象user.save方法,沒啥可說的了爽冕,調(diào)用類對象獲取其屬性的內(nèi)置方法仇祭。
9、致謝
某平臺內(nèi)部培訓(xùn)資料扇售。