python元编程运用_Python 中的元编程
就像元數(shù)據(jù)是有關(guān)數(shù)據(jù)的數(shù)據(jù)一樣,元編程就是編寫用于操縱程序的某些程序。人們普遍認(rèn)為,元程序就是生成其他程序的某些程序,但范式更加廣泛。所有旨在自我讀取、分析、轉(zhuǎn)換或修改的程序都是元編程的范例。例如:
領(lǐng)域特定語言 (DSL)解析器解釋器編譯器定理證明器術(shù)語重寫器
本教程探究 Python 中的元編程。本文通過考察 Python 的特性,更新您的 Python 知識,讓您能夠更深入地理解本教程中的概念。同時(shí),還說明了 Python 中的類型為何會(huì)比只返回對象的類更重要。之后,討論了在 Python 中進(jìn)行元編程的方法,以及元編程如何簡化某些任務(wù)。
稍作反思
如果您使用 Python 進(jìn)行編程已經(jīng)有段時(shí)間,可能知道一切都是對象,而類創(chuàng)建了這些對象。但是,如果一切都是對象(類也是對象),那么誰來創(chuàng)建這些類呢?這就是我要解答的問題。
我們來驗(yàn)證一下上述說法是否正確:
1 2 3 4 5? >>> class SomeClass:? ...? ? ?pass >>> some_object = SomeClass() >>> type(some_obj) <__main__.someclass instance at>
因此,在對象上調(diào)用的 type 函數(shù)返回該對象的類。
1 2 3 4 5 6 7? >>> import inspect >>>inspect.isclass(SomeClass) True >>>inspect.isclass(some_object) False >>>inspect.isclass(type(some_object)) True
如果將類傳遞給 inspect.isclass,它就會(huì)返回 True,否則即返回 False。因?yàn)?some_object 不是類(它是 SomeClass 類的實(shí)例),所以 inspect.isclass 返回 False。又因?yàn)?type(some_object) 返回 some_object 的類,所以inspect.isclass(type(some_object)) 返回 True:
1 2 3 4? >>> type(SomeClass) >>> inspect.isclass(type(SomeClass)) True
在 Python 3 中,所有類在默認(rèn)情況下從 Classobj 類繼承。現(xiàn)在,一切都說得通了。但 classobj 又該如何解釋?我們來開開眼:
1 2 3 4 5 6 7 8? >>> type(type(SomeClass)) >>>inspect.isclass(type(type(SomeClass))) True >>>type(type(type(SomeClass))) >>>isclass(type(type(type(SomeClass)))) True
發(fā)現(xiàn)了嗎?事實(shí)證明,一開始的說法(一切都是對象)并不完全正確。下面是更準(zhǔn)確的說法:
除 type 之外,Python 中的一切都是對象,它們要么是類的實(shí)例,要么是元類的實(shí)例。
驗(yàn)證這一點(diǎn):
1 2 3 4 5? >>> some_obj = SomeClass() >>> isinstance(some_obj,SomeClass) True >>> isinstance(SomeClass, type) True
現(xiàn)在,我們知道了實(shí)例是實(shí)例化的類,而類是元類的實(shí)例。
type 并不是我們所想的那樣
type 本身是類,也是自己的類型。它是一個(gè)元類。元類用于實(shí)例化并定義類的行為,就像類用于實(shí)例化并定義實(shí)例的行為一樣。
type 是 Python 使用的內(nèi)置元類。為改變 Python 中類的行為(比如,SomeClass 的行為),我們可以通過繼承 type 元類,定義一個(gè)自定義元類。元類是在 Python 中進(jìn)行元編程的一種方法。
定義了某個(gè)類后會(huì)發(fā)生什么情況?
我們首先回顧下已知的內(nèi)容。Python 程序的基本構(gòu)建塊是:
語句函數(shù)類
語句用于在程序中執(zhí)行實(shí)際的工作。語句可以在全局范圍(模塊級別)或局部范圍(限于函數(shù)內(nèi))執(zhí)行。函數(shù)類似代碼的基本單元,由用于完成特定任務(wù)的一個(gè)或多個(gè)語句以某種順序構(gòu)成。可以在模塊級別定義函數(shù),也可以將函數(shù)定義為類的方法。類為函數(shù)提供面向?qū)ο蟮木幊谭椒āK鼈兌x對象如何進(jìn)行實(shí)例化以及對象的特征(屬性和方法)。
類的名稱空間被分層為不同的字典。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15? >>> class SomeClass:? ...? ? ?class_var = 1 ...? ? ?def __init__(self):? ...? ? ? ? ?self.some_var = 'Some value'? ?>>> SomeClass.__dict__ {'__doc__': None,? '__init__': ,? '__module__': '__main__',? 'class_var': 1}? ?>>> s = SomeClass()? ?>>> s.__dict__ {'some_var': 'Some value'}
以下是每次遇到關(guān)鍵字類時(shí)發(fā)生的情況:
類的主體(語句和函數(shù))被隔離。創(chuàng)建類的名稱空間字典(但尚未填充)。先執(zhí)行類的主體,然后使用所有屬性、定義的方法以及與類有關(guān)的其他一些有用信息來填充名稱空間字典。在基類或要?jiǎng)?chuàng)建的類的元類掛鉤(稍后解釋)中確定元類。然后,通過類的名稱、基類和屬性調(diào)用元類,對其進(jìn)行實(shí)例化。
因?yàn)?type 是 Python 中的默認(rèn)元類,所以可以在 Python 中使用 type 來創(chuàng)建類。
type 的另一面
通過一個(gè)參數(shù)調(diào)用 type 時(shí),會(huì)生成現(xiàn)有類的 type 信息。通過三個(gè)參數(shù)調(diào)用 type 時(shí),會(huì)創(chuàng)建一個(gè)新的類對象。調(diào)用 type時(shí),參數(shù)是類名、基類列表以及指定類的名稱空間的字典(所有字段和方法)。
所以:>>> class SomeClass: pass
等同于:>>> SomeClass = type('SomeClass', (), {})
以及:
1 2 3 4 5 6 7? class ParentClass:? ? ? pass? ?class SomeClass(ParentClass):? ? ? some_var = 5? ? ?def some_function(self):? ? ? ? ? print("Hello!")
實(shí)際等同于:
1 2 3 4 5 6 7 8? def some_function(self):? ? ? print("Hello")? ?ParentClass = type('ParentClass', (), {}) SomeClass = type('SomeClass',? ? ? ? ? ? ? ? ? [ParentClass],? ? ? ? ? ? ? ? ? {'some_function': some_function,? ? ? ? ? ? ? ? ? ?'some_var':5})
因此,通過使用自定義元類而不是 type,我們可以將某種行為注入到不太可能的類中。但是在實(shí)現(xiàn)元類來改變行為之前,我們來看一下在 Python 中進(jìn)行元編程更為常見的方法。
裝飾器:在 Python 中進(jìn)行元編程的常見例子
裝飾器是用于改變函數(shù)或類的行為的一種方法。裝飾器的用法類似如下:
1 2 3? @some_decorator def some_func(*args, **kwargs):? ? ? pass
some_func 包裝的 @some_decorator 只是語法糖的代表 。我們知道在 Python 中,函數(shù)和類(metaclass 類型除外)都是對象,也就是說它們可以:
分配給某個(gè)變量復(fù)制作為參數(shù)傳遞給其他函數(shù)
前面的語法實(shí)際等同于:some_func = some_decorator(some_func)
您可能想知道 some_decorator 是如何定義的:
1 2 3 4 5 6 7 8 9? def some_decorator(f):? ? ? """? ? ?The decorator receives function as a parameter.? ? ?"""? ? ?def wrapper(*args, **kwargs):? ? ? ? ? # doing something before calling the function? ? ? ? ?f(*args, **kwargs)? ? ? ? ?# doing something after the function is called? ? ?return wrapper
我們假設(shè)自己有一個(gè)從 URL 訪存已提取數(shù)據(jù)的函數(shù)。我們從中訪存數(shù)據(jù)的服務(wù)器具備調(diào)節(jié)機(jī)制,如果它檢測到在相同時(shí)間間隔內(nèi)從某個(gè) IP 地址傳入大量請求,就會(huì)進(jìn)行調(diào)節(jié)。所以,為了讓提取器像人類一樣,我們愿意等待隨機(jī)長度的時(shí)間,然后再提交請求以欺騙服務(wù)器。 能否使裝飾器也做到這樣?我們來看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18? from functools import wraps import random import time? ?def wait_random(min_wait=1, max_wait=30):? ? ? def inner_function(func):? ? ? ? ? @wraps(func)? ? ? ? ?def wrapper(*args, **kwargs):? ? ? ? ? ? ? time.sleep(random.randint(min_wait, max_wait))? ? ? ? ? ? ?return func(*args, **kwargs)? ? ? ? ? ?return wrapper? ? ? ?return inner_function? ?@wait_random(10, 15) def function_to_scrape():? ? ? # some scraping stuff
您對 Inner_function 和 @wraps 裝飾器可能有點(diǎn)陌生。如果您仔細(xì)查看就會(huì)發(fā)現(xiàn),inner_function 與我們前面定義的some_decorator 類似。 Wait_random 中的另一層包裝也支持將參數(shù)傳遞到裝飾器(min_wait 和 max_wait)。@Wraps 是個(gè)不錯(cuò)的裝飾器,可以復(fù)制 func 的元數(shù)據(jù)(比如名稱、文檔字符串以及函數(shù)屬性)。如果不使用這些,我們就無法從 help(func)等函數(shù)調(diào)用獲得有用的結(jié)果,因?yàn)樵谶@種情況下,它會(huì)返回 wrapper 而不是 func 的文檔字符串和信息。
但是,如果我們擁有 scraper 類以及多個(gè)此類函數(shù)會(huì)怎樣:
1 2 3 4 5 6 7 8 9 10? class Scraper:? ? ? def func_to_scrape_1(self):? ? ? ? ? # some scraping stuff? ? ? ? ?pass? ? ?def func_to_scrape_2(self):? ? ? ? ? # some scraping stuff? ? ? ? ?pass? ? ?def func_to_scrape_3(self):? ? ? ? ? # some scraping stuff? ? ? ? ?pass
一種選擇就是使用 @wait_random 單獨(dú)包裝所有函數(shù)。但我們可以做得更好:我們可以創(chuàng)建一個(gè)類裝飾器。方法就是遍歷類名稱空間,確定函數(shù),然后通過裝飾器包裝這些函數(shù)。
1 2 3 4 5 6 7 8? def classwrapper(cls):? ? ? for name, val in vars(cls).items():? ? ? ? ? # `callable` return `True` if the argument is callable? ? ? ? ?# i.e. implements the `__call`? ? ? ? ?if callable(val):? ? ? ? ? ? ? # instead of val, wrap it with our decorator.? ? ? ? ? ? ?setattr(cls, name, wait_random()(val))? ? ?return cls
現(xiàn)在,您可以使用 @classwrapper 包裝整個(gè)類。但是,如果存在多個(gè) scraper 類或者 scraper 的多個(gè)子類會(huì)怎樣?您可以對這些類單獨(dú)使用 @classwrapper,或者在這種情況下,也可以創(chuàng)建元類。
元類
編寫自定義元類分為兩個(gè)步驟:
編寫元類類型的子類。使用元類掛鉤將新元類插入到類創(chuàng)建流程中。
我們使 type 類實(shí)現(xiàn)子類化,并修改魔術(shù)方法,比如 __init__、__new__、__prepare__ 以及 __call__,以便在創(chuàng)建類時(shí)修改類的行為。這些方法包含基類、類名、屬性及其值等方面的信息。在 Python 2 中,元類掛鉤是稱為 __metaclass__ 的類中的靜態(tài)字段。在 Python 3 中,您可以將元類指定為類的基類列表中的一個(gè) metaclass 參數(shù)。
1 2 3 4 5 6 7 8 9 10 11 12? >>> class CustomMetaClass(type):? ...? ? ?def __init__(cls, name, bases, attrs):? ?...? ? ? ? ?for name, value in attrs.items():? ? ? ? ? ? ? ? ? # do some stuff ...? ? ? ? ? ? ?print('{} :{}'.format(name, value)) >>> class SomeClass:? ...? ? ? ? ? # the Python 2.x way ...? ? ? ? ?__metaclass__ = CustomMetaClass ...? ? ? ? ?class_attribute = "Some string" __module__ :__main__ __metaclass__ : class_attribute :Some string
由于 CustomMetaClass 的 __init__ 方法中的 print 語句,這些屬性將會(huì)自動(dòng)打印。我們假設(shè)您在 Python 項(xiàng)目中有個(gè)令人討厭的合作者,此人更喜歡使用 camelCase 來命名類屬性和方法。您知道這樣不好,該合作者應(yīng)該使用 snake_case(畢竟,這是 Python!)。我們能否編寫元類,將所有這些 camelCase 屬性更改為 snake_case?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17? def camel_to_snake(name):? ? ? """? ? ?A function that converts camelCase to snake_case.? ? ?Referred from: https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case? ? ?"""? ? ?import re? ? ?s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)? ? ?return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()? ?class SnakeCaseMetaclass(type):? ? ? def __new__(snakecase_metaclass, future_class_name,? ? ? ? ? ? ? ? ?future_class_parents, future_class_attr):? ? ? ? ? snakecase_attrs = {}? ? ? ? ?for name, val in future_class_attr.items():? ? ? ? ? ? ? snakecase_attrs[camel_to_snake(name)] = val? ? ? ? ?return type(future_class_name, future_class_parents,? ? ? ? ? ? ? ? ? ? ?snakecase_attrs)
您可能想知道我們在這里為什么使用 __new__ 而不是 __init__。__new__ 實(shí)際上是創(chuàng)建實(shí)例的第一步。它負(fù)責(zé)返回類的新實(shí)例。而在另一方面,__init__ 則不會(huì)返回任何內(nèi)容。它只負(fù)責(zé)在創(chuàng)建實(shí)例后對其進(jìn)行初始化。請牢記一條簡單的經(jīng)驗(yàn)法則:當(dāng)需要控制新實(shí)例的創(chuàng)建時(shí)使用 new,而在需要控制新實(shí)例的初始化時(shí)則使用 init。
在元類中實(shí)現(xiàn) __init__ 并不常見,因?yàn)樗皇悄敲磸?qiáng)大 — 在實(shí)際調(diào)用 __init__ 之前,已經(jīng)構(gòu)造了類。您可以將其視為具有一個(gè)類裝飾器,但不同點(diǎn)在于:在構(gòu)建子類時(shí)會(huì)運(yùn)行 __init__,而不會(huì)為子類調(diào)用類裝飾器。
因?yàn)槲覀兊娜蝿?wù)包括創(chuàng)建新實(shí)例(防止這些 camelCase 屬性潛入類中),所以覆蓋自定義 SnakeCaseMetaClass 中的 __new__ 方法。我們來確認(rèn)下它是否運(yùn)行:
1 2 3 4 5 6? >>> class SomeClass(metaclass=SnakeCaseMetaclass):? ...? ? ?camelCaseVar = 5 >>> SomeClass.camelCaseVar AttributeError: type object 'SomeClass' has no attribute 'camelCaseVar' >>> SomeClass.camel_case_var 5
它已運(yùn)行!現(xiàn)在,您已了解了如何在 Python 中編寫和使用元類。我們再來探究一下這有何用途。
在 Python 中使用元類
您可以使用元類對屬性、方法及其值執(zhí)行不同的準(zhǔn)則。前面例子(使用 snake_case)的類似示例包括:
值的域限制隱式轉(zhuǎn)換自定義類的值(您可能希望向用戶隱藏編寫類的所有這些復(fù)雜方面)執(zhí)行不同的命名約定和樣式準(zhǔn)則(比如,“每種方法都應(yīng)有一個(gè)文檔字符串”)向類添加新的屬性
在類定義本身中定義所有這種邏輯時(shí)使用元類,主要原因就是為了避免在整個(gè)代碼庫中出現(xiàn)重復(fù)代碼。
元類的實(shí)際使用
因?yàn)樵谧宇愔袝?huì)繼承元類,所以元類解決了代碼冗余(不要重復(fù)自己 — DRY)這一實(shí)際問題。 通常情況下,在生成類對象的同時(shí),通過執(zhí)行額外操作或添加額外代碼,元類也可以幫助提取有關(guān)類創(chuàng)建的復(fù)雜邏輯。元類的一些實(shí)際用例包括:
抽象基類類的注冊在庫和框架中創(chuàng)建 API
我們來具體看一下每個(gè)示例。
抽象基類
抽象基類是只能被繼承而不會(huì)被實(shí)例化的類。Python 具有以下內(nèi)容:
1 2 3 4 5 6 7 8 9 10 11? from abc import ABCMeta, abstractmethod? ?class Vehicle(metaclass=ABCMeta):? ? ? ? @abstractmethod? ? ?def refill_tank(self, litres):? ? ? ? ? pass? ? ? ?@abstractmethod? ? ?def move_ahead(self):? ? ? ? ? pass
我們來創(chuàng)建一個(gè)從 Vehicle 類繼承的 Truck 類:
1 2 3 4 5 6 7 8 9 10 11? class Truck(Vehicle):? ? ? def __init__(self, company, color, wheels):? ? ? ? ? self.company = company? ? ? ? ?self.color = color? ? ? ? ?self.wheels = wheels? ? ? ?def refill_tank(self, litres):? ? ? ? ? pass? ? ? ?def move_ahead(self):? ? ? ? ? pass
請注意,我們沒有實(shí)現(xiàn)抽象方法。我們來看下如果嘗試實(shí)例化 Truck 類的對象會(huì)發(fā)生什么情況:
1 2 3? >>> mini_truck = Truck("Tesla Roadster", "Black", 4)? ?TypeError: Can't instantiate abstract class Truck with abstract methods move_ahead, refill_tank
可以通過在 Truck 類中定義兩種抽象方法來修復(fù)這個(gè)問題:
1 2 3 4 5 6 7 8 9 10 11 12 13 14? class Truck(Vehicle):? ? ? def __init__(self, company, color, wheels):? ? ? ? ? self.company = company? ? ? ? ?self.color = color? ? ? ? ?self.wheels = wheels? ? ? ?def refill_tank(self, litres):? ? ? ? ? pass? ? ? ?def move_ahead(self):? ? ? ? ? pass >>> mini_truck = Truck("Tesla Roadster", "Black", 4) >>> mini_truck <__main__.truck at>
類的注冊
為理解這一點(diǎn),我們以某個(gè)服務(wù)器上的多個(gè)文件處理程序?yàn)槔O敕ň褪悄軌蚋鶕?jù)文件格式快速找到正確的處理程序類。我們將創(chuàng)建處理程序字典,讓 CustomMetaclass 注冊在代碼中遇到的不同處理程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24? handlers = {}? ?class CustomMetaclass(type):? ? ? def __new__(meta, name, bases, attrs):? ? ? ? ? cls = type.__new__(meta, name, bases, attrs)? ? ? ? ?for ext in attrs["formats"]:? ? ? ? ? ? ? handlers[ext] = cls? ? ? ? ?return cls? ?class Handler(metaclass=CustomMetaclass):? ? ? formats = []? ? ?# common stuff for all kinds of file format handlers? ? ?class ImageHandler(Handler):? ? ? formats = ["jpeg", "png"]? ?class AudioHandler(Handler):? ? ? formats = ["mp3", "wav"] >>> handlers {'mp3': __main__.AudioHandler,? 'jpeg': __main__.ImageHandler,? 'png': __main__.ImageHandler,? 'wav': __main__.AudioHandler}
現(xiàn)在,根據(jù)文件格式,我們很容易就知道要使用哪個(gè)處理程序類。一般來說,無論何時(shí)需要維護(hù)存儲類特征的某種數(shù)據(jù)結(jié)構(gòu),都可以使用元類。
創(chuàng)建 API
由于元類能夠防止子類中的邏輯冗余,能夠隱藏用戶無需知道的自定義類創(chuàng)建邏輯,因此元類在框架和庫中得到廣泛應(yīng)用。這為減少樣板和擁有更出色的 API 創(chuàng)造了一些值得注意的機(jī)會(huì)。例如,思考一下 Django ORM 的這個(gè)使用片段:
1 2 3 4? >>> from from django.db import models >>> class Vehicle(models.Model):? ...? ? color = models.CharField(max_length=10) ...? ? wheels = models.IntegerField()
我們在此創(chuàng)建了一個(gè) Vehicle 類,它從 Django 包中的 models.Model 類繼承而來。在該類的內(nèi)部,我們定義了幾個(gè)字段(color 和 wheels)來表示車輛的特征。現(xiàn)在,我們嘗試實(shí)例化剛剛所創(chuàng)建類的對象。
1 2 3 4 5? >>> four_wheeler = Vehicle(color="Blue", wheels="Four") # Raises an error >>> four_wheeler = Vehicle(color="Blue", wheels=4) >>> four_wheeler.wheels 4
作為創(chuàng)建車輛模型的用戶,我們只需從 models.Model 類繼承,然后編寫一些高級語句。其余工作(比如創(chuàng)建數(shù)據(jù)庫掛鉤,提出無效值錯(cuò)誤,返回 int 類型而不是 models.IntegerField)則由 model.Models 類以及它使用的元類在后臺完成。
總結(jié)
在本教程中,您了解了 Python 中的實(shí)例、類以及元類之間的關(guān)系。您學(xué)習(xí)了元編程知識,這是一種操縱代碼的方法。我們討論了函數(shù)裝飾器和類裝飾器,二者是將自定義行為注入到類和方法中的一種方式。然后,我們通過使 Python 的默認(rèn) type 元類子類化,實(shí)現(xiàn)了自定義元類。最后,我們介紹了元類的一些實(shí)際用例。是否使用元類這個(gè)問題在網(wǎng)上飽受爭議,但是現(xiàn)在,對于某種問題是否利用元編程才能更好地解決,您應(yīng)該可以更輕松地展開分析并得出答案。
相關(guān)主題
Python 初學(xué)者指南技術(shù)講座:Python:一種持續(xù)奉獻(xiàn)、不斷發(fā)展和永續(xù)學(xué)習(xí)的語言
總結(jié)
以上是生活随笔為你收集整理的python元编程运用_Python 中的元编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java jnotify_Jnotify
- 下一篇: python文件操作模式是什么,pyth