元類與面向切麵編程

来源:http://www.cnblogs.com/linxiyue/archive/2017/12/15/8030604.html
-Advertisement-
Play Games

元類 在 Python中,實例對象是由類生成的,而類本身也是可以被傳遞和自省的對象。那麼類對象是用什麼創建和生成的呢?答案是元類,元類就是一種知道如何創建和管理類的對象。 讓我們回顧一個內置函數type(),type不僅可以返回對象的類型,而且可以使用類名稱、基類元組、類主體定義的字典作為參數來創建 ...


元類

在 Python中,實例對象是由類生成的,而類本身也是可以被傳遞和自省的對象。那麼類對象是用什麼創建和生成的呢?答案是元類,元類就是一種知道如何創建和管理類的對象。

讓我們回顧一個內置函數type(),type不僅可以返回對象的類型,而且可以使用類名稱、基類元組、類主體定義的字典作為參數來創建一個新類對象:

>>> Foo = type('Foo',(object,),{'foo':lambda self:'foo'})
>>> Foo
<class '__main__.Foo'>
>>> type(Foo)
<type 'type'>

實際上,新型類的預設元類就是type,類可以用__metaclass__類變數顯示的指定元類,上述代碼功能與下述相同:

class Foo():
    __metaclass__ = type

    def foo(self):
        return 'foo'

如果沒有顯式的指定元類,class語句會檢查基類元組中的第一個基類的元類,比如新型類都是繼承object類的,所以新型類與object類的元類相同,為type,繼承object而不顯式的指定元類:

class Foo(object):
    def foo(self):
        return 'foo'

如果沒有指定基類,class語句會檢查全局變數__metaclass__,如果沒有找到__metaclass__值,Python會使用預設的元類。

在python 2中,預設的元類是types.ClassType,就是所謂的舊樣式類。python2.2以後已不提倡使用,比如不指定元類並且不繼承object基類:

class Foo():
    def foo(self):
        return 'foo'
>>> import types
>>> isinstance(Foo, types.ClassType)
True

python 3以後,預設的元類皆為type了,顯式定義元類的時候需要在基類元組中提供metaclass關鍵字,class Foo(metaclass=type)如此定義。

 使用元類的時候,一般會自定義一個繼承自type的子類,並重新實現__init__()與__new__()方法:

class ExampleType(type):
    def __new__(cls, name, bases, dct):
        print 'create class %s'%name
        return type.__new__(cls, name, bases, dct)

    def __init__(cls, name, bases, dct):
        print 'Init class %s'%name
        type.__init__(cls, name, bases, dct)

class Foo(object):
    __metaclass__ = ExampleType

>>> 
create class Foo
Init class Foo
>>> Foo
<class '__main__.Foo'>

可見,使用class語句定義類後,元類就使用傳遞給元類的類名稱、基類元組和類方法字典創建類。

因為元類創建的實例是類對象,所以__init__方法的第一個參數按慣例寫為cls,其實與self功能相同。

面向切麵編程

在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程稱為面向切麵的編程(AOP)。

簡單地說,如果不同的類要實現相同的功能,可以將其中相同的代碼提取到一個切片中,等到需要時再切入到對象中去。這些相同的代碼片段稱為切麵,而切入到哪些類、哪些方法則叫切入點。

比如,要為每個類方法記錄日誌,在python中一個可行的方法是使用裝飾器:

def trace(func):
    def callfunc(self, *args, **kwargs):
        debug_log = open('debug_log.txt', 'a')
        debug_log.write('Calling %s: %s ,%s\n'%(func.__name__, args, kwargs))
        result = func(self, *args, **kwargs)
        debug_log.write('%s returned %s\n'%(func.__name__, result))
        debug_log.close()
        return result
    return callfunc

def logcls(cls):
    for k, v in cls.__dict__.items():
        if k.startswith('__'):
            continue
        if not callable(v):
            continue
        setattr(cls, k, trace(v))
    return cls

@logcls
class Foo(object):
    num = 0

    def spam(self):
        Foo.num += 1
        return Foo.num

另外一個可行的方法就是使用元類了:

def trace(func):
    def callfunc(self, *args, **kwargs):
        debug_log = open('debug_log.txt', 'a')
        debug_log.write('Calling %s: %s ,%s\n'%(func.__name__, args, kwargs))
        result = func(self, *args, **kwargs)
        debug_log.write('%s returned %s\n'%(func.__name__, result))
        debug_log.close()
        return result
    return callfunc


class LogMeta(type):
    def __new__(cls, name, bases, dct):
        for k, v in dct.items():
            if k.startswith('__'):
                continue
            if not callable(v):
                continue
            dct[k] = trace(v)
        return type.__new__(cls, name, bases, dct)


class Foo(object):
    __metaclass__ = LogMeta
    num = 0

    def spam(self):
        Foo.num += 1
        return Foo.num

元類的一個主要用途就是檢查收集或者更改類定義的內容,包括類屬性、類方法、描述符等等。

元類與基類

元類中除了可以定義__init__和__new__方法外,還可以定義其它的屬性和方法:

class ExaMeta(type):
    name = 'ExaMeta'
    def get_cls_name(cls):
        print cls.__name__


class Foo(object):
    __metaclass__ = ExaMeta

那麼,類可不可以訪問元類定義的方法和屬性呢?

>>> Foo.get_cls_name()
Foo
>>> Foo.name
'ExaMeta'

這很好理解,類Foo是元類的一個實例,在實例的__dict__中查找不到要查詢的屬性時,就會到實例所屬的類字典中去查找,而元類正是定義類Foo的類。

可以再嘗試下使用類Foo的實例去訪問元類的屬性或者方法:

>>> Foo().get_cls_name()
AttributeError: 'Foo' object has no attribute 'get_cls_name'
>>> Foo().name
AttributeError: 'Foo' object has no attribute 'name'

顯然不能訪問。

查找一個不與實例關聯的屬性時,即先在實例的類中查找,然後再在從所有的基類中查找,查找的順序可以用__mro__屬性查看:

>>> Foo.__mro__
(<class '__main__.Foo'>, <type 'object'>)

元類並不在其中,畢竟,類與元類不是繼承關係,而是實例與類的創造關係。

元類屬性的可用性是不會傳遞的,也就是說,元類的屬性是對它的類實例是可用的,但是對它的類實例的實例是不可用的,這正是元類與基類的主要不同。

有時候,一個類會同時有元類和基類:

class M(type):
    name = 'M'

class B(object):
    name = 'B'

class A(B):
    __metaclass__ = M

屬性訪問是這樣的:

>>> A.name
'B'
>>> A().name
'B'

可見類會先到繼承的基類中去查找屬性。

元類衝突

假如有兩個不同元類的類,要生成一個繼承這兩個類的子類,會產生什麼情況呢?

class MA(type):
    pass

class A(object):
    __metaclass__ = MA

class MB(type):
    pass

class B(object):
    __metaclass__ = MB

class C(A, B):
    pass

結果會報錯,提示元類衝突:

TypeError: Error when calling the metaclass bases
    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

我們需要手動構造新子類的元類,讓新子類的元類繼承自A和B的元類:

class MC(MA, MB):
    pass

class C(A, B):
    __metaclass__ = MC

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • pow 求誰的多少次方 四捨五入 向上取整 向下取整 求一堆數的最大值 求一堆數的最小值 求隨機數 0 1之間的數 ...
  • 交換2個number類型的變數的值,不能使用第三方變數 ...
  • 1.彈出框 ,小括弧中就是彈出的內容 alert("我是一個彈出框"); 2.控制台輸出 小括弧裡面就是 控制台輸出的東西 console.log("我是控制台輸出的內容"); 3.彈出輸入框,可以接受用戶輸入的信息。 3.1 prompt("請輸入你的名字"); 3.2 prompt("請輸入你的 ...
  • vue——props的兩種常用方法 1、實現父—— 子的通信 舉例如下: 父組件 parent.vue <children :channel="object1" </children 子組件 children.vue export default{ name:"children", pr ...
  • 背景圖片自適應瀏覽器大小 之前在網上看到的一個小技巧,記錄一下:背景圖片鋪滿屏幕並且可以隨著瀏覽器進行自適應,代碼如下: <body <div style="position:absolute; width:100%; height:100%; z index: 1" <img s ...
  • 1、塊級作用域 (1)使用let代替var 好處:變數應該只在其聲明的代碼塊內有效;var命令存在變數提升效用,let命令沒有這個問題。 (2)全局常量 在let和const之間,建議優先使用const,尤其是在全局環境,不應該設置變數,只應設置常量。 const優於let有幾個原因。一個是cons ...
  • Apache Log4j 2 Apache Log4j 2是對Log4j的升級,它比它的前輩Log4j 1提供了顯著的改進。在解決Logback的架構中存在的一些固有問題時,提供了許多可用的改進。 特性 API分離 Log4j的API與實現分離,使應用程式開發人員清楚地知道,他們可以使用哪些類和方法 ...
  • CLR(Common Language Runtime)公共語言進行時是一個可由多種編程語言使用的“進行時”。 將源代碼編譯成托管模塊 可用支持CLR的任何語言創建源代碼文件,然後用對應的編譯器檢查語法和分析源代碼。無論選擇哪個編譯器,結果都是托管模塊(managed module)。托管模塊是標準 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...