溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python metaclass的原理及應(yīng)用

發(fā)布時(shí)間:2021-08-31 10:59:31 來源:億速云 閱讀:123 作者:chen 欄目:編程語言

這篇文章主要介紹“Python metaclass的原理及應(yīng)用”,在日常操作中,相信很多人在Python metaclass的原理及應(yīng)用問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Python metaclass的原理及應(yīng)用”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

元編程(meta programming)是一項(xiàng)很神奇的能力,可以通過代碼在運(yùn)行時(shí)動(dòng)態(tài)生成代碼。

元類(meta classes)是 Python 提供的一種元編程的能力。在 Python 中,類也是一種對(duì)象,那么類這種對(duì)象就是元類的實(shí)例,所以我們可以在運(yùn)行時(shí)通過實(shí)例化元類動(dòng)態(tài)生成類。

使用 type “函數(shù)”

首先我們來了解一下 type,type 可以作為函數(shù)使用,用來獲得對(duì)象的類型:

>>> class Foo:
...     pass
>>> obj = Foo()
>>> obj.__class__
<class '__main__.Foo'>
>>> type(obj)
<class '__main__.Foo'>
>>> obj.__class__ is type(obj)
True

實(shí)際上 type 并不是一個(gè)函數(shù),而是一個(gè)類,我們可以使用 type(type) 來確定一下:

>>> type(type)
<class 'type'>

type 實(shí)際上不只是類,而是一個(gè)“元類”。我們接下來要可以看到,所有的元類都需要繼承自 type。type 是所以類的元類,所以在上面的例子中 x 是 Foo 的實(shí)例,F(xiàn)oo 是 type 的實(shí)例,type 又是他自己的實(shí)例。

用 type 動(dòng)態(tài)創(chuàng)建類

如果傳遞給 type 的參數(shù)是三個(gè)的時(shí)候,type 的語義就不再是返回給定參數(shù)的類,而是實(shí)例化生成一個(gè)新的類。

type(name: str, bases: tuple, namespace: dict)

第一個(gè)參數(shù)是新生成的類的名字;第二個(gè)參數(shù)是新生成的類的基類列表;第三個(gè)參數(shù)是要個(gè)這個(gè)類綁定的屬性的列表,比如說這個(gè)類的一些方法。實(shí)際上 class Foo 這種語法只是使用 type 生成類的語法糖而已。

最簡(jiǎn)單的一個(gè)例子,比如我們要?jiǎng)?chuàng)建 Foo[0..9] 這些類,可以這樣做:

classes = []
for i in range(10):
   cls = type("Foo%s" % i, tuple(), {})
   classes.append(cls)

# 就像使用普通類一樣初始化 Foo0

foo0  = clssses[0]()

如果要實(shí)現(xiàn)類的方法,一定要記得同樣是要使用 self 變量的。在 Python 中 self 只是一個(gè)約定俗成的變量,而不是關(guān)鍵字。

def __init__(self, name):
   self.name = name

def print_name(self):
   print(self.name)

Duck = type("Duck", tuple(), {"__init__": __init__, "print_name": print_name})

duck = Duck("Donald")

duck.print_name()
# Donald

創(chuàng)建自己的元類

首先我們來回顧一下 Python 中類的初始化過程:

foo = Foo()

當(dāng)這條語句運(yùn)行的時(shí)候,Python 會(huì)依次調(diào)用 Foo 的 __new__ 和 __init__ 方法。其中 __new__ 方法在 __init__ 之前調(diào)用,并返回已經(jīng)創(chuàng)建好的新對(duì)象,而 __init__ 函數(shù)是沒有返回結(jié)果的。一般情況下,我們都會(huì)覆蓋 __init__ 方法來對(duì)新創(chuàng)建的對(duì)象做一些初始化操作。

現(xiàn)在回歸到元類上,進(jìn)入燒腦部分。前面我們說過元類的實(shí)例化就是類,所以大致相當(dāng)于:

Foo = MetaFoo(name, bases, attrs)  # MetaFoo 默認(rèn)情況下是 type
foo = Foo()

默認(rèn)情況下,所有類的元類是 type,也就是在這個(gè)類是通過 type 來創(chuàng)建的,這和前面說的通過 type 來動(dòng)態(tài)創(chuàng)建類也是一致的。

那么怎樣定義一個(gè) MetaFoo 呢?只需要繼承自 type 就行了。因?yàn)樵惖膶?shí)例化就是類的創(chuàng)建過程,所以在元類中,我們可以修改 __new__ 來在 __init__ 之前對(duì)新創(chuàng)建的類做一些操作。

>>> class MetaFoo(type):
...     def __new__(cls, name, bases, namespace):
...         x = super().__new__(cls, name, bases, namespace)  # super實(shí)際上就是 type
...         x.bar = 100  # 為這個(gè)類增加一個(gè)屬性
...         return x
...

>>> Foo = MetaFoo("Foo", tuple(), {})  # MetaFoo 在這里就相當(dāng)于 type 了,可以動(dòng)態(tài)創(chuàng)建類
>>> Foo.bar
100
>>> foo = Foo()
>>> foo.bar
100

在這里我們創(chuàng)建了 MetaFoo 這個(gè)元類,他會(huì)給新創(chuàng)建的類增加一個(gè)叫做 bar 的屬性。

在實(shí)際的代碼中,我們一般還是不會(huì)直接動(dòng)態(tài)生成類的,還是調(diào)用 class Foo 語法來生成類比較常見一點(diǎn),這時(shí)候可以指定 metaclass 參數(shù)就好了。可以通過 Foo(metaclass=MetaFoo) 這種方式來指定元類。

class Foo(metaclass=MetaFoo):
   pass

這種定義和上面的元類用法效果完全是一致的。

一個(gè)現(xiàn)實(shí)世界的元類例子

在 django.models 或者 peewee 等 ORM 中,我們一般使用類的成員變量來定義字段,這里就用到了元類。

class Field:
   pass

class IntegerField(Field):
   pass

class CharField(Field):
   pass

class MetaModel(type):
   def __new__(meta, name, bases, attrs):
       # 這里最神奇的是:用戶定義的類中的 bases 和 attrs 都會(huì)作為參數(shù)傳遞進(jìn)來
       fields = {}
       for key, value in attrs.items():
           if isinstance(value, Field):
               value.name = '%s.%s' % (name, key)
               fields[key] = value
       for base in bases:
           if hasattr(base, '_fields'):
               fields.update(base._fields)
       attrs['_fields'] = fields
       return type.__new__(meta, name, bases, attrs)

class Model(metaclass=MetaModel):
   pass

這樣用戶使用的時(shí)候就可以這樣定義:

>>> class A(Model):
...     foo = IntegerField()
...
>>> class B(A):
...     bar = CharField()
...
>>> B._fields
{'foo': Integer('A.foo'), 'bar': String('B.bar')}

程序在執(zhí)行的時(shí)候就可以直接訪問 X._fields,而不用每次都通過反射遍歷一次,從而提高效率以及做一些驗(yàn)證。

不過,其實(shí)這個(gè)完全可以通過裝飾器來實(shí)現(xiàn):

def model(cls):
   fields = {}
   for key, value in vars(cls).items():
       if isinstance(value, Field):
           value.name = '%s.%s' % (cls.__name__, key)
           fields[key] = value
   for base in cls.__bases__:
       if hasattr(base, '_fields'):
           fields.update(base._fields)
   cls._fields = fields
   return cls

@model
class A():
   foo = IntegerField()

class B(A):
   bar = CharField()

但是用裝飾器的話,就失去了一些類型繼承的語義信息。

到此,關(guān)于“Python metaclass的原理及應(yīng)用”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI