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

你可能感兴趣的:(pythoncookbook 第9章 元编程)