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__
  • 无论在元类和普通类中者三者都存在,无论meta类还是普通类new, super()调用父类方法并return .
__call__
  • 在元类中必须调用super(),才能使子类正常。

三种特殊方法的作用

new: 它是创建对象时调用,会返回当前对象的一个实例;
init: 它是创建对象后调用,对当前对象的一些实例初始化,无返回值
call: 子类(不严谨)调用中起作用,

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 # 类属性
        super(Singleton, cls).__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        # Spam()触发
        print "meta__call__"
        if cls.instance is None:
            # 触发 spam 中的__mew__,__init__,完成实例化
            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()()触发
        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

元编程的一点理解:

  • 元类是实例化出类的.因此在元类里面定义普通方法,相当于类方法
  • 元类中的new 方法和init 不同,init方法不能直接操作name, bases, attrs. 以及solt属性.而new则无所不能(所有起作用的都是通过type类).
  • 执行顺序,类定义好方法以后,再去跑对应元类里面的 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):
        # 分配物理地址,准备构建类的材料(类有哪些元素组成,父类,类名称是什么,属性字典有木有),创建类
        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):
        # 初始化类,不能直接修改类的基类,名字.字典等
        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:
        # 只能修改类的属性
        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()

$关于装饰器$

不管是类装饰器还是函数装饰器
func = warp(func) # 两层 
func = warp(*agrs,**kwargs )(func)  # 三层

用wraps可以保留被包装函数的原信息,如函数的name 和doc的()

函数装饰器

  • 最基础装饰器
def decorator(func):
    print '定义装饰器时执行'
    @wraps(func)
    def wrapper(*args, **kwargs):
        print '调用原函数执行'
        return func(*args, **kwargs)
    return wrapper
# @decorator
def add(x, y):
    return x + y
add = decorator(add) ## add就是wrapper了
  • 带参数装饰器
    对于函数装饰器来说,装饰方法和类是一样的
def decorator_args(*args,**kwargs):
    print '定义装饰器时执行1'
    a = args
    b = kwargs
    def decorator(func):
        print '定义装饰器时执行2'
        @wraps(func)
        def wrapper(*args, **kwargs):
            print a,b
            print '调用原函数执行'
            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)

类装饰器

如何将外部函数,绑定为类的方法??
如果将函数自己赋值给类的属性,这样是不行的

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)
##会出错,属性指向一个函数,无法隐式传入self

正确的方法

import types
obj.method = types.MethodType(new_method, obj, MyObj)  #将一个函数和实例,类绑定为方法
obj.method(5)

进一步解释 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)
## 调用方法obj_method时,隐形传入instance
print obj_method(100)  #(<__main__.MyObj1 object at 0x0000000002FA1160>, 100)

下面开始解释类的装饰器举例

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

    def __call__(self,*args,**kwargs):
        # self为Decorator()实例,而arg里面有instance即h
        print "调用时执行2"
        return self.func(*args,**kwargs)

    def __get__(self, instance, cls):
        print "调用时执行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 一个对象,这个对象调用的时候,隐形的传入实例
## return self ,self()调用 触发Decorator的__call__,
## 因此Decorator()()则会访问__call__
## 因此Decorator()会访问meta类中的__call__
  • 带参数的类装饰器 加一层类的嵌套
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 使用元类控制实例的创建

限制类实例化,clall函数定义普通类里面,实例调用的时候触发,call函数定义在元类里面,则在类实例化时调用。

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 # 类属性
        super(Singleton,self).__init__(*args, **kwargs)
    def __call__(self, *args, **kwargs):
        if self.instance is None:
# 类里面的调用Spam()触发元类中的__call__,默认的元类中__call__方法(应该是再次触发类中的__init__方法).在这里被override了.
# 所以,无法进行下一步的操作,需要调用父类的__call__正常实例化即为Spam().
#元类中的__call__相当于实例化的开关.
            self.instance = super(Singleton,self).__call__(*args, **kwargs)
            return self.instance
        else:
            return self.instance
# Example
class Spam(object):
    __metaclass__ = Singleton
    # 实例的a()触发
    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()

甚至可以做到,一次实例化,化出来多个实例,即元类可以花样定制类的实例化过程

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

用元类的方法缓存实例8.25小节
缓存意思是指,有一样的东西,就去调用存在的,不一样就再生成

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 捕获类的属性定义顺序 (todo)

2版本中没有prepare ,可以看一下django中的字段顺序form

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

9.17 类上强制使用编程规约

在元类中定义,类方法或者属性不能用大写

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 以编程方式定义类

无types.new_class

9.19 在定义的时候初始化类的成员

带有名称的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 一个切片函数,必须是描述器才能传入实例
            setattr(cls, name, property(operator.itemgetter(n)))
            # 变成了类属性

class StructTuple(tuple):
    __metaclass__ = StructTupleMeta
    _fields = []
    # 继承不可变类型时,改写new
    def __new__(cls, *args):
        if len(args) != len(cls._fields):
            raise ValueError('{} arguments required'.format(len(cls._fields)))
        # 注意不是×args, tuple只能接受一个参数。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__  # {} 无实例属性则调用类属性
print s.name, s.shares, s.price
print tuple([1,2,3,4])

9.20 略

9.21 批量的制造描述器

#制造函数,批量的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

执行文本格式的代码 9.23-9.25

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • 20170808如意分享妞儿优点: 1,指派妞儿洗衣服,嗖嗖完成,挂好。下班回家看到晒在阳台的衣服,我觉得很幸福很...
    随如阅读 273评论 0 1
  • 在上港突然发力的几分钟里,可以说是从垂直最高点坠入了谷底。 在这几分钟里,有人笑,有人哭。有的人最后留下了悲伤的眼...
    伦厂长万岁阅读 747评论 0 51
  • 他和她的第一次相遇是什么时候呢,她也记不清楚了,只是在后来的一次聊天中,他讲到了他们的第一次见面。他说,他...
    蘑菇姓郭阅读 307评论 0 0