作者:袁首京
原創文章,轉載時請保留此宣告,并給出原文連接,
元編程并不象它聽起來那么時髦和新奇,常用的 decorator 就可以認為是一種元編程,簡單來說,元編程就是撰寫操作代碼的代碼,
有點繞,是吧?別著急,咱們一點一點來討論,
注意:本文中的代碼適用于 Python 3.3 及以上,
元類
多數編程語言中,一切東西都有型別,Python 也不例外,我們可以用 type() 函式獲取任意變數的型別,
num = 23
print("Type of num is:", type(num))
lst = [1, 2, 4]
print("Type of lst is:", type(lst))
name = "Atul"
print("Type of name is:", type(name))
執行結果是:
Type of num is: <class 'int'>
Type of lst is: <class 'list'>
Type of name is: <class 'str'>
Python 中的所有型別都是由 Class 定義的,這一條與其它編程語言,比如 Java、C++ 等等不同,在那些語言中,int、char、float 之類是基本資料型別,但是在 Python 中,它們是 int 類或 str 類的物件,
象其它 OOP 語言一樣,我們可以使用 class 定義新型別:
class Student:
pass
stu_obj = Student()
print("Type of stu_obj is:", type(stu_obj))
執行結果是:
Type of stu_obj is: <class '**main**.Student'>
一點兒也不意外,對吧?其實有意外,因為在 Python 中,類也是一個物件,就像任何其他物件一樣,它是元類的實體,即一個特殊的類,創建了 Class 這個特殊的類實體,看如下代碼:
class Student:
pass
print("Type of Student class is:", type(Student))
執行結果是:
Type of Student class is: <class 'type'>
既然類也是一個物件,所以以修改物件相同的方式修改它就順理成章,如下先定義一個沒有任何屬性和方法的類,然后在外部為其添加屬性和方法:
class test:
pass
test.x = 45
test.foo = lambda self: print('Hello')
myobj = test()
print(myobj.x)
myobj.foo()
執行結果是:
45
Hello
以上程序可以簡單概括為:
元類創建類,類創建實體
畫個圖象這樣:
元類 -> 類 -> 實體
因此,我們就可以撰寫自定義的元類,執行額外的操作或者注入代碼,來改變類的生成程序,這在某些場景下很有用,主要是比如有些情況下使用元編程更簡單,另一些情況只有元編程才能解決問題,
創建自定義元類
創建自定義元類,有兩種方法,第一種是繼承 type 元類,并且覆寫兩個方法:
- new()
它在 init() 之前呼叫,生成類實體并回傳,我們可以覆寫此方法來控制物件的創建程序,
- init()
這個不多解釋,相信你都明白,
如下是個例子:
class MultiBases(type):
def __new__(cls, clsname, bases, clsdict):
if len(bases)>1:
raise TypeError("Inherited multiple base classes!!!")
return super().__new__(cls, clsname, bases, clsdict)
class Base(metaclass=MultiBases):
pass
class A(Base):
pass
class B(Base):
pass
class C(A, B):
pass
執行結果是:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 8, in **new**
TypeError: Inherited multiple base classes!!!
第二種方法是直接使用 type() 函式創建類,這個方法如果只用一個引數呼叫,它會回傳該引數的型別,前文已經描述過,但是使用三個引數呼叫時,它會創建一個類,這三個引數如下:
- 類名稱;
- 繼承的父類的元組,你沒看錯,是元組,別忘了 Python 可以多繼承;
- 一個字典,定義類屬性和方法;
以下是示例:
def test_method(self):
print("This is Test class method!")
class Base:
def myfun(self):
print("This is inherited method!")
Test = type('Test', (Base, ), dict(x="atul", my_method=test_method))
print("Type of Test class: ", type(Test))
test_obj = Test()
print("Type of test_obj: ", type(test_obj))
test_obj.myfun()
test_obj.my_method()
print(test_obj.x)
執行結果是:
Type of Test class: <class 'type'>
Type of test_obj: <class '**main**.Test'>
This is inherited method!
This is Test class method!
atul
使用元類解決問題
了解了元類的創建方法后,可以來解決一些實際問題了,例如,如果我們想在每次呼叫類方法時,都先輸出一下它的全限定名,該怎么辦呢?
最常用的方法是使用 decorator,象這樣:
from functools import wraps
def debug(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Full name of this method:", func.__qualname__)
return func(*args, **kwargs)
return wrapper
def debugmethods(cls):
for key, val in vars(cls).items():
if callable(val):
setattr(cls, key, debug(val))
return cls
@debugmethods
class Calc:
def add(self, x, y):
return x+y
def mul(self, x, y):
return x\*y
def div(self, x, y):
return x/y
mycal = Calc()
print(mycal.add(2, 3))
print(mycal.mul(5, 2))
執行結果是:
Full name of this method: Calc.add
5
Full name of this method: Calc.mul
10
這個方案很漂亮,但是,如果變更一下需求,例如我們希望 Calc 的所有子類的方法執行時,都先輸出一下它的全限定名,該怎么辦呢?
在每一個子類上加上 @debugmethods 是一種方案,但是有點啰嗦,是不是?
該基于元類的解決方案出場了,以下是個例子:
from functools import wraps
def debug(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Full name of this method:", func.__qualname__)
return func(*args, **kwargs)
return wrapper
def debugmethods(cls):
for key, val in vars(cls).items():
if callable(val):
setattr(cls, key, debug(val))
return cls
class debugMeta(type):
def __new__(cls, clsname, bases, clsdict):
obj = super().__new__(cls, clsname, bases, clsdict)
obj = debugmethods(obj)
return obj
class Base(metaclass=debugMeta):
pass
class Calc(Base):
def add(self, x, y):
return x+y
class Calc_adv(Calc):
def mul(self, x, y):
return x\*y
mycal = Calc_adv()
print(mycal.mul(2, 3))
執行結果是:
Full name of this method: Calc_adv.mul
6
何時使用元類
該說的基本說完了,剩下最好一件事,元編程算是 Python 的一個魔法,多數時候我們其實用不到,但是什么時候需要呢?大概有三種情況:
- 如果我們想要一個特性,沿著繼承層次結構向下傳遞,可以用;
- 如果我們想在類創建后,能動態修改,可以用;
- 如果我們是在開發類別庫或者 API,可能會用到;
原創文章,轉載時請保留此宣告,并給出原文連接,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/549484.html
標籤:Python
