pythoncookbook 第9章 元編程

[toc]

9 元編程

http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Metaprogramming.html
http://pythoncentral.io/how-metaclasses-work-technically-in-python-2-and-3/
http://eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example

三種特殊方法的理解

__new__, __init__, __call__
  • 無(wú)論在元類(lèi)和普通類(lèi)中者三者都存在,無(wú)論meta類(lèi)還是普通類(lèi)new, super()調(diào)用父類(lèi)方法并return .
__call__
  • 在元類(lèi)中必須調(diào)用super(),才能使子類(lèi)正常。

三種特殊方法的作用

new: 它是創(chuàng)建對(duì)象時(shí)調(diào)用牙躺,會(huì)返回當(dāng)前對(duì)象的一個(gè)實(shí)例愁憔;
init: 它是創(chuàng)建對(duì)象后調(diào)用,對(duì)當(dāng)前對(duì)象的一些實(shí)例初始化述呐,無(wú)返回值
call: 子類(lèi)(不嚴(yán)謹(jǐn))調(diào)用中起作用惩淳,

class Singleton(type):
    def __new__(mcl, *args, **kwargs):
        print "meta__new__"
        return type.__new__(mcl, *args, **kwargs)
    def __init__(cls, *args, **kwargs):
        print "mew__init__"
        cls.instance = None # 類(lèi)屬性
        super(Singleton, cls).__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        # Spam()觸發(fā)
        print "meta__call__"
        if cls.instance is None:
            # 觸發(fā) spam 中的__mew__,__init__,完成實(shí)例化
            cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
            return cls.instance
        else:
            return cls.instance

class Spam(object):
    __metaclass__ = Singleton
    def __call__(self, *args, **kwargs):
        # spam()()觸發(fā)
        return 123
    def __init__(self):
        print('Creating Spam')
    def __new__(cls,*args,**kwargs):
        print "instanc__new__"
        return super(Spam, cls).__new__(cls,*args,**kwargs)
print Spam()()
>>>
meta__new__
meta__init__
meta__call__
instance__new__
instance__inta__
123

元編程的一點(diǎn)理解:

  • 元類(lèi)是實(shí)例化出類(lèi)的.因此在元類(lèi)里面定義普通方法,相當(dāng)于類(lèi)方法
  • 元類(lèi)中的new 方法和init 不同,init方法不能直接操作name, bases, attrs. 以及solt屬性.而new則無(wú)所不能(所有起作用的都是通過(guò)type類(lèi)).
  • 執(zhí)行順序,類(lèi)定義好方法以后,再去跑對(duì)應(yīng)元類(lèi)里面的 new ,后init.
from pprint import pprint
 
class Tag1: pass
class Tag2: pass
class Tag3:
    def tag3_method(self): pass
 
class MetaBase(type):
    def __new__(mcl, name, bases, nmspc):
        print('2MetaBase.__new__\n')
        return super(MetaBase, mcl).__new__(mcl, name, bases, nmspc)
 
    def __init__(cls, name, bases, nmspc):
        print('MetaBase.__init__\n')
        super(MetaBase, cls).__init__(name, bases, nmspc)
 
class MetaNewVSInit(MetaBase):
    def __new__(mcls, name, bases, dict):
        # 分配物理地址,準(zhǔn)備構(gòu)建類(lèi)的材料(類(lèi)有哪些元素組成,父類(lèi),類(lèi)名稱(chēng)是什么,屬性字典有木有),創(chuàng)建類(lèi)
        print('MetaNewVSInit.__new__')
        for x in (mcls, name, bases, dict): pprint(x)
        print('1')
 
        if 'foo' in dict: dict.pop('foo')
        name += '_x'
        bases += (Tag1,)
        dict['baz'] = 42
        return super(MetaNewVSInit, mcls).__new__(mcls, name, bases, dict)
 
    def __init__(cls, name, bases, dict):
        # 初始化類(lèi),不能直接修改類(lèi)的基類(lèi),名字.字典等
        print('MetaNewVSInit.__init__')
        for x in (cls, name, bases, dict): pprint(x)
        print('3')
        if 'bar' in dict: dict.pop('bar') # No effect
        name += '_y' # No effect
        bases += (Tag2,) # No effect
        dict['pi'] = 3.14159 # No effect
        # These do work because they operate on the class object:
        # 只能修改類(lèi)的屬性
        super(MetaNewVSInit, cls).__init__(name, bases, dict) #所有這句話在不在都一樣
        cls.__name__ += '_z'
        cls.__bases__ += (Tag3,)
        cls.e = 2.718
 
class Test(object):
    __metaclass__ = MetaNewVSInit
    def __init__(self):
        print('Test.__init__')
    def foo(self): print('foo still here')
    def bar(self): print('bar still here')
 
print 4
t = Test()

$關(guān)于裝飾器$

不管是類(lèi)裝飾器還是函數(shù)裝飾器
func = warp(func) # 兩層 
func = warp(*agrs,**kwargs )(func)  # 三層

用wraps可以保留被包裝函數(shù)的原信息乓搬,如函數(shù)的name 和doc的()

函數(shù)裝飾器

  • 最基礎(chǔ)裝飾器
def decorator(func):
    print '定義裝飾器時(shí)執(zhí)行'
    @wraps(func)
    def wrapper(*args, **kwargs):
        print '調(diào)用原函數(shù)執(zhí)行'
        return func(*args, **kwargs)
    return wrapper
# @decorator
def add(x, y):
    return x + y
add = decorator(add) ## add就是wrapper了
  • 帶參數(shù)裝飾器
    對(duì)于函數(shù)裝飾器來(lái)說(shuō)思犁,裝飾方法和類(lèi)是一樣的
def decorator_args(*args,**kwargs):
    print '定義裝飾器時(shí)執(zhí)行1'
    a = args
    b = kwargs
    def decorator(func):
        print '定義裝飾器時(shí)執(zhí)行2'
        @wraps(func)
        def wrapper(*args, **kwargs):
            print a,b
            print '調(diào)用原函數(shù)執(zhí)行'
            return func(*args, **kwargs)
        return wrapper
    return decorator

class Human(object):
    # @decorator_args(1,2,3)
    def add(self, x, y):
        return x + y
    add = decorator_args(123)(add)
Human().add(1 ,2)

類(lèi)裝飾器

如何將外部函數(shù),綁定為類(lèi)的方法??
如果將函數(shù)自己賦值給類(lèi)的屬性,這樣是不行的

class MyObj(object):
    def __init__(self, val):
        self.val = val
        
def new_method(self, value):
    return self.val + value
    
obj = MyObj(3)
obj.method = new_method
obj.method(11)
##會(huì)出錯(cuò),屬性指向一個(gè)函數(shù),無(wú)法隱式傳入self

正確的方法

import types
obj.method = types.MethodType(new_method, obj, MyObj)  #將一個(gè)函數(shù)和實(shí)例,類(lèi)綁定為方法
obj.method(5)

進(jìn)一步解釋 types.MethodType

import types
class MyObj1(object):
    def __init__(self, val):
        self.val = val
 
class MyObj2(object):
    def __init__(self,func):
        self.func = new_method
 
    def __call__(self, *args, **kwargs):
        print args
        self.func(*args)
        return self.func(*args, **kwargs)
 
def new_method(self, value):
    return self.val + value
 
instance = MyObj1(3)
obj_method = types.MethodType(MyObj2(new_method), instance)
obj_method = types.MethodType(new_method, instance)
## 調(diào)用方法obj_method時(shí)进肯,隱形傳入instance
print obj_method(100)  #(<__main__.MyObj1 object at 0x0000000002FA1160>, 100)

下面開(kāi)始解釋類(lèi)的裝飾器舉例

import types
class Decorator(object):
    def __init__(self, func)
        self.func = func

    def __call__(self,*args,**kwargs):
        # self為Decorator()實(shí)例激蹲,而arg里面有instance即h
        print "調(diào)用時(shí)執(zhí)行2"
        return self.func(*args,**kwargs)

    def __get__(self, instance, cls):
        print "調(diào)用時(shí)執(zhí)行1"
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)


class Human(object):
    def __init__(self):
        self.val = 1
    # @decorator_args(1,2,3)
    def add(self, x, y):
        return x + y + self.val
    add = Decorator(add)
print Human().add(1,2)
## h.add --->types.MethodType(self, instance)
## types.MethodType(self, instance)    return 一個(gè)對(duì)象,這個(gè)對(duì)象調(diào)用的時(shí)候江掩,隱形的傳入實(shí)例
## return self 学辱,self()調(diào)用 觸發(fā)Decorator的__call__,
## 因此Decorator()()則會(huì)訪問(wèn)__call__
## 因此Decorator()會(huì)訪問(wèn)meta類(lèi)中的__call__
  • 帶參數(shù)的類(lèi)裝飾器 加一層類(lèi)的嵌套
import types
class Decorator(object):
    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
 
    def __call__(self, func):
 
        class META(object):
 
            def __init__(self, func, *args, **kwargs):
                self.func = func
                self.args = args
                self.kwargs = kwargs
 
            def __get__(self, instance, cls):
                if instance is None:
                    return self
                return types.MethodType(self, instance)
 
            def __call__(self, *args, **kwargs):
                print self.args
                print self.kwargs
                return self.func(*args, **kwargs)
 
        return META(func, *self.args, **self.kwargs)
 
 
class Human(object):
 
    def __init__(self):
        self.val = 1
 
    @Decorator(1, 2, 3)
    def add(self, x, y):
        return x + y + self.val
    # add = Decorator(1, 2, 3)(add)
 
h = Human()
print h.add(44, 55)
 
@Decorator(1, 2, 3)
def func():
    print 'im function'
 
func()

9.13 使用元類(lèi)控制實(shí)例的創(chuàng)建

限制類(lèi)實(shí)例化环形,clall函數(shù)定義普通類(lèi)里面策泣,實(shí)例調(diào)用的時(shí)候觸發(fā),call函數(shù)定義在元類(lèi)里面抬吟,則在類(lèi)實(shí)例化時(shí)調(diào)用萨咕。

class NoInstances(type):
    def __call__(self, *args, **kwargs):
        raise TypeError("Can't instantiate directly")

class Spam(object):
    __metaclass__ = NoInstances
    @staticmethod
    def grok(x):
        print('Spam.grok')
s = Spam()

單例模式

class Singleton(type):
    def __init__(self, *args, **kwargs):
        self.instance = None # 類(lèi)屬性
        super(Singleton,self).__init__(*args, **kwargs)
    def __call__(self, *args, **kwargs):
        if self.instance is None:
# 類(lèi)里面的調(diào)用Spam()觸發(fā)元類(lèi)中的__call__,默認(rèn)的元類(lèi)中__call__方法(應(yīng)該是再次觸發(fā)類(lèi)中的__init__方法).在這里被override了.
# 所以,無(wú)法進(jìn)行下一步的操作,需要調(diào)用父類(lèi)的__call__正常實(shí)例化即為Spam().
#元類(lèi)中的__call__相當(dāng)于實(shí)例化的開(kāi)關(guān).
            self.instance = super(Singleton,self).__call__(*args, **kwargs)
            return self.instance
        else:
            return self.instance
# Example
class Spam(object):
    __metaclass__ = Singleton
    # 實(shí)例的a()觸發(fā)
    def __call__(self, *args, **kwargs): 
        return 123
    def __init__(self):
        print('Creating Spam')
print Spam.__dict__
a = Spam()
print Spam.instance
print Spam.__dict__
b = Spam()
print b()

甚至可以做到,一次實(shí)例化,化出來(lái)多個(gè)實(shí)例,即元類(lèi)可以花樣定制類(lèi)的實(shí)例化過(guò)程

class Singleton(type):
    def __call__(cls, *args, **kwargs):
        x = super(Singleton, cls).__call__(2)
        y = super(Singleton, cls).__call__(3)
        return x,y
class Spam(object):
    __metaclass__ = Singleton
    def __init__(self,value):
        self.value = value
        print('Creating Spam')
(a,b)=Spam()
print a.value
print b.value

用元類(lèi)的方法緩存實(shí)例8.25小節(jié)
緩存意思是指,有一樣的東西,就去調(diào)用存在的,不一樣就再生成

class Cached(type):
    def __init__(self, *args, **kwargs):
        super(Cached,self).__init__(*args, **kwargs)
        self.__cache = weakref.WeakValueDictionary()
 
    def __call__(self, *args):
        if args in self.__cache:
            return self.__cache[args]
        else:
            obj = super(Cached,self).__call__(*args)
            self.__cache[args] = obj
            return obj
 
# Example
class Spam(object):
    __metaclass__ = Cached
    def __init__(self, name):
        print('Creating Spam({!r})'.format(name))
        self.name = name
 
a = Spam("jin")
b = Spam("jin")

.14 捕獲類(lèi)的屬性定義順序 (todo)

2版本中沒(méi)有prepare ,可以看一下django中的字段順序form

http://stackoverflow.com/questions/350799/how-does-django-know-the-order-to-render-form-fields

9.17 類(lèi)上強(qiáng)制使用編程規(guī)約

在元類(lèi)中定義,類(lèi)方法或者屬性不能用大寫(xiě)

class NoMixedCaseMeta(type):
    def __new__(mcs, name, bases, attrs):
        for key in attrs:
            if key != key.lower():
                raise TypeError('Bad attirbute name' + name)
        return super(NoMixedCaseMeta, mcs).__new__(mcs, name, bases, attrs)
        
class Root(object):
    __metaclass__ = NoMixedCaseMeta
    pass
class A(Root):
    def foo_bar(self):
        pass
class B(Root):
    def Foo_bar(self):
        pass

9.18 以編程方式定義類(lèi)

無(wú)types.new_class

9.19 在定義的時(shí)候初始化類(lèi)的成員

帶有名稱(chēng)的tuple

import operator

class StructTupleMeta(type):
    def __init__(cls, *args, **kwargs):
        super(StructTupleMeta,cls).__init__(*args, **kwargs)
        for n, name in enumerate(cls._fields):
            # operator.itemgetter() return 一個(gè)切片函數(shù)火本,必須是描述器才能傳入實(shí)例
            setattr(cls, name, property(operator.itemgetter(n)))
            # 變成了類(lèi)屬性

class StructTuple(tuple):
    __metaclass__ = StructTupleMeta
    _fields = []
    # 繼承不可變類(lèi)型時(shí)危队,改寫(xiě)new
    def __new__(cls, *args):
        if len(args) != len(cls._fields):
            raise ValueError('{} arguments required'.format(len(cls._fields)))
        # 注意不是×args, tuple只能接受一個(gè)參數(shù)钙畔。tuple([1,2,3,4])
        return super(StructTuple,cls).__new__(cls,args)

class Stock(StructTuple):
    _fields = ['name', 'shares', 'price']


class Point(StructTuple):
    _fields = ['x', 'y']


s = Stock('ACME', 50, 91.1)
print s.__dict__  # {} 無(wú)實(shí)例屬性則調(diào)用類(lèi)屬性
print s.name, s.shares, s.price
print tuple([1,2,3,4])

9.20 略

9.21 批量的制造描述器

#制造函數(shù)茫陆,批量的return 描述器
def typed_property(name, expected_type):
    storage_name = '_' + name

    @property
    def prop(self):
        return getattr(self, storage_name)

    @prop.setter
    def prop(self, value):
        if not isinstance(value, expected_type):
            raise TypeError('{} must be a {}'.format(name, expected_type))
        setattr(self, storage_name, value)
    return prop


# Example use
class Person:
    name = typed_property('name', str)
    age = typed_property('age', int)

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

執(zhí)行文本格式的代碼 9.23-9.25

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市擎析,隨后出現(xiàn)的幾起案子簿盅,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挪鹏,死亡現(xiàn)場(chǎng)離奇詭異见秽,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)讨盒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)步责,“玉大人返顺,你說(shuō)我怎么就攤上這事÷希” “怎么了遂鹊?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蔗包。 經(jīng)常有香客問(wèn)我秉扑,道長(zhǎng),這世上最難降的妖魔是什么调限? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任舟陆,我火速辦了婚禮,結(jié)果婚禮上耻矮,老公的妹妹穿的比我還像新娘秦躯。我一直安慰自己,他們只是感情好裆装,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布踱承。 她就那樣靜靜地躺著,像睡著了一般哨免。 火紅的嫁衣襯著肌膚如雪茎活。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天琢唾,我揣著相機(jī)與錄音载荔,去河邊找鬼。 笑死慧耍,一個(gè)胖子當(dāng)著我的面吹牛身辨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芍碧,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼抽莱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼扫外!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤杠巡,失蹤者是張志新(化名)和其女友劉穎庇楞,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猪落,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了畴博。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笨忌。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖俱病,靈堂內(nèi)的尸體忽然破棺而出官疲,到底是詐尸還是另有隱情,我是刑警寧澤亮隙,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布途凫,位于F島的核電站,受9級(jí)特大地震影響溢吻,放射性物質(zhì)發(fā)生泄漏维费。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一促王、第九天 我趴在偏房一處隱蔽的房頂上張望犀盟。 院中可真熱鬧,春花似錦硼砰、人聲如沸且蓬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)恶阴。三九已至,卻和暖如春豹障,著一層夾襖步出監(jiān)牢的瞬間冯事,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工血公, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昵仅,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓累魔,卻偏偏與公主長(zhǎng)得像摔笤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子垦写,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理吕世,服務(wù)發(fā)現(xiàn),斷路器梯投,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法命辖,類(lèi)相關(guān)的語(yǔ)法况毅,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法尔艇,異常的語(yǔ)法尔许,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,639評(píng)論 18 399
  • 20170808如意分享妞兒優(yōu)點(diǎn): 1味廊,指派妞兒洗衣服,嗖嗖完成棠耕,掛好毡们。下班回家看到曬在陽(yáng)臺(tái)的衣服,我覺(jué)得很幸福很...
    隨如閱讀 288評(píng)論 0 1
  • 在上港突然發(fā)力的幾分鐘里,可以說(shuō)是從垂直最高點(diǎn)墜入了谷底框咙。 在這幾分鐘里咕痛,有人笑,有人哭喇嘱。有的人最后留下了悲傷的眼...
    倫廠長(zhǎng)萬(wàn)歲閱讀 759評(píng)論 0 51
  • 他和她的第一次相遇是什么時(shí)候呢者铜,她也記不清楚了腔丧,只是在后來(lái)的一次聊天中,他講到了他們的第一次見(jiàn)面作烟。他說(shuō)愉粤,他...
    蘑菇姓郭閱讀 315評(píng)論 0 0