Metaclass tersuai

Sekarang, kita sudah tahu apa itu metaclass. Jadi, dari awal hingga akhir, kita masih tidak tahu apa kegunaan metaclass. Baru belajar tentang metaclass. Sebelum memahami penggunaannya, mari kita fahami dahulu cara menyesuaikan metaclass. Kerana hanya dengan memahami cara menyesuaikannya, anda boleh memahami fungsinya dengan lebih baik.

Mula-mula, mari kita fahami atribut __metaclass__

metaclass, secara literal diterjemahkan sebagai metaclass, penjelasan mudahnya ialah:

Selepas kita mentakrifkan kelas, kita boleh mencipta contoh berdasarkan kelas ini, jadi: tentukan kelas dahulu, Kemudian buat contoh.

Tetapi bagaimana jika kita ingin membuat kelas? Kemudian anda mesti mencipta kelas berdasarkan metaclass, jadi: tentukan metaclass dahulu, dan kemudian buat kelas.

Sambungannya ialah: tentukan dahulu metaclass, kemudian anda boleh mencipta kelas, dan akhirnya mencipta contoh.

Jadi, metaclass membolehkan anda membuat kelas atau mengubah suai kelas. Dengan kata lain, anda boleh menganggap kelas sebagai "contoh" yang dibuat oleh metaclass.

class MyObject(object):
    __metaclass__ = something…
[…]

Jika ditulis seperti ini, Python akan menggunakan metaclass untuk mencipta kelas MyObject. Apabila anda menulis kelas MyObject(objek), objek kelas MyObject belum lagi dicipta dalam ingatan. Python akan mencari atribut __metaclass__ dalam definisi kelas Jika ia dijumpai, Python akan menggunakannya untuk mencipta kelas MyObject Jika ia tidak dijumpai, ia akan menggunakan fungsi jenis terbina dalam untuk mencipta kelas. Jika anda masih tidak memahaminya, lihat carta alir di bawah:

c7dd72cd3c802993425a3b6516a97e2.png

Contoh lain:

class Foo(Bar):
    pass

Apakah proses penghakimannya?

Tentukan dahulu sama ada Foo mempunyai atribut __metaclass__? Jika ada, Python akan mencipta objek kelas bernama Foo dalam ingatan melalui __metaclass__ (perhatikan bahawa ini adalah objek kelas). Jika Python tidak menjumpai __metaclass__, ia akan terus mencari atribut __metaclass__ dalam Bar (kelas induk) dan cuba melakukan operasi yang sama seperti sebelumnya. Jika Python tidak dapat mencari __metaclass__ dalam mana-mana kelas induk, ia mencari __metaclass__ dalam hierarki modul dan cuba melakukan perkara yang sama. Jika __metaclass__ masih tidak ditemui, Python akan menggunakan jenis terbina dalam untuk mencipta objek kelas.

Malah, __metaclass__ mentakrifkan kelakuan kelas. Sama seperti cara kelas mentakrifkan gelagat contoh, metaclass mentakrifkan gelagat kelas. Boleh dikatakan bahawa kelas adalah contoh metaclass.

Kini, pada asasnya kami memahami atribut __metaclass__, tetapi kami belum bercakap tentang cara menggunakan atribut ini, atau apakah yang boleh diletakkan dalam atribut ini?

Jawapannya ialah: anda boleh membuat kelas. Jadi apa yang boleh digunakan untuk membuat kelas? jenis, atau apa-apa sahaja yang menggunakan jenis atau jenis subkelas.

Tujuan utama metaclass adalah untuk menukar kelas secara automatik apabila ia dicipta. Biasanya, anda akan melakukan sesuatu seperti ini untuk API, di mana anda ingin mencipta kelas yang sesuai dengan konteks semasa. Bayangkan contoh bodoh di mana anda memutuskan bahawa semua atribut kelas dalam modul anda hendaklah dalam huruf besar. Terdapat beberapa cara untuk melakukan ini, tetapi salah satunya adalah dengan menetapkan __metaclass__ pada tahap modul. Menggunakan kaedah ini, semua kelas dalam modul ini akan dibuat melalui metaclass ini Kita hanya perlu memberitahu metaclass untuk menukar semua atribut kepada huruf besar dan semuanya akan baik-baik saja.

Nasib baik __metaclass__ sebenarnya boleh dipanggil sewenang-wenangnya, tak perlu kelas formal. Jadi, mari kita mulakan dengan fungsi mudah sebagai contoh.

# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    '''返回一个类对象,将属性都转为大写形式'''
    #  选择所有不以'__'开头的属性
    attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
# 将它们转为大写形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
 
# 通过'type'来做类对象的创建
return type(future_class_name, future_class_parents, uppercase_attr)
 
__metaclass__ = upper_attr  
#  这会作用到这个模块中的所有类
 
class Foo(object):
    # 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
    bar = 'bip'
print hasattr(Foo, 'bar')
# 输出: False
print hasattr(Foo, 'BAR')
# 输出:True
 
f = Foo()
print f.BAR
# 输出:'bip'
用 class 当做元类的做法:
# 请记住,'type'实际上是一个类,就像'str'和'int'一样
# 所以,你可以从type继承
class UpperAttrMetaClass(type):
    # __new__ 是在__init__之前被调用的特殊方法
    # __new__是用来创建对象并返回之的方法
    # 而__init__只是用来将传入的参数初始化给对象
    # 你很少用到__new__,除非你希望能够控制对象的创建
    # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
    # 如果你希望的话,你也可以在__init__中做些事情
    # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return type(future_class_name, future_class_parents, uppercase_attr)

Namun, pendekatan ini sebenarnya bukan OOP. Kami memanggil taip secara langsung dan kami tidak mengatasi kaedah __new__ kelas induk. Sekarang mari kita lakukan seperti ini:

class UpperAttrMetaclass(type):
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
 
        # 复用type.__new__方法
        # 这就是基本的OOP编程,没什么魔法
        return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)

Anda mungkin perasan bahawa terdapat parameter tambahan upperattr_metaclass, tiada apa yang istimewa mengenainya. Argumen pertama kepada kaedah kelas sentiasa mewakili contoh semasa, sama seperti argumen diri dalam kaedah kelas biasa. Sudah tentu, demi kejelasan, saya telah membuat nama di sini lebih panjang. Tetapi sama seperti diri, semua parameter mempunyai nama tradisional mereka. Jadi dalam kod pengeluaran sebenar metaclass akan kelihatan seperti ini:

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')
        uppercase_attr  = dict((name.upper(), value) for name, value in attrs)
        return type.__new__(cls, name, bases, uppercase_attr)

Kita juga boleh menjadikannya lebih jelas sedikit jika kita menggunakan kaedah super, yang memudahkan pewarisan (ya, anda boleh mempunyai metaclass , mewarisi daripada metaclass, mewarisi daripada jenis)

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

Biasanya kami menggunakan metaclass untuk melakukan beberapa perkara yang tidak jelas, bergantung pada introspeksi, mengawal warisan, dll. Sesungguhnya, ia amat berguna untuk menggunakan metaclass untuk melakukan beberapa "sihir gelap" dan dengan itu mencipta beberapa perkara yang rumit. Tetapi setakat metaclass itu sendiri, mereka sebenarnya sangat mudah:

Memintas penciptaan kelas

Ubah suai kelas

Kembalikan kelas yang diubah suai

Meneruskan pembelajaran
  • Cadangan kursus
  • Muat turun perisian kursus