zoukankan      html  css  js  c++  java
  • 装饰器、元类、元编程,基础概念分析。

    前面写过一次,但对于类的元编程也就是元类,其实掌握的并不彻底,虽然装饰器基本能完成元类的工作。但了解了元类可以对与OOP的学习有更深的认识。

    废话不多,开始了。

    首先这次参考的是书籍是《Python学习笔记》第九章,也是一个超级大神的书。

    所谓的元编程将程序当做数据,或在运行期完成编译期的工作。

    在利用装饰器(decorator),可在不侵入内部实现,甚至在不知道的情况下,插入扩展逻辑。

    装饰器的本质是一个函数,一般普通的装饰器也返回一个函数,原理的使用中调用了闭包,因为传入的参数为非全局变量。

    def log(fn):
        # 内部的装饰器函数
        def wrap(*args, **kwargs):
            print(f"log: {args},  {kwargs}")
            # 间接调用原函数
            return fn(*args, **kwargs)
    
        # 返回包装函数代替原函数
        return wrap
    
    
    @log
    def add(x, y):
        return x + y
    
    if __name__ == '__main__':
        add(1, 2)
        
    
    log: (1, 2),  {}
    

     @是Python的语法糖,其实每条@log执行的是xxx =log(xxx) xxx为被装饰的函数。所以其应用于多个目标函数没有问题。

    因此任何可调用的对象都可以用来做装饰器

    相比与函数,使用类可以创建更加复杂的装饰器。

    class Log2:
    
        def __init__(self,func):
            # print('__int__')
            # 其实运行了这个装饰器,实际就是self = wraps(func)(self)
            wraps(func)(self)
    
        def __call__(self, *args, **kwargs):
            print(f"log: {args},  {kwargs}")
            # 其实直接执行self也可以调用被装饰的函数,self()默认也是执行func(),但直接执行self,会调用self的__call__陷入死循环
            return self.__wrapped__(*args, **kwargs)
    
        # 定义了__get__改对象在类实例化过程中会被当做非数据型描述符,对该属性读取时,会首先被调用
        def __get__(self, instance, owner):
            # 这个可以判断是否时在类里面被调用
            # print(instance, owner)
            if instance is None:
                return self
            else:
                # 如果是直接将self绑定给instance,其实也可以将self.__wrapped__通过types.MethodType绑定到instance上面
                return types.MethodType(self, instance)

    class X:
    @Log2
    def test(self):
    print('my name is test')

    上面的 类装饰在执行过程中,在X实例过程中,将test函数通过wraps变成自身,后续实例调用test方式的时候,再通过__get__将该函数绑定到实例上面。

    上面我写的一个类装饰器,可以装饰函数与方法,本书中没有重写描述符协议,这个写法是从cookbook中看来,应该这里来看明显普通的函数装饰器更加适合写方法。

    因为函数对象默认实现了描述符协议和绑定的规则。

    嵌套使用多个装饰器

    ----> 1 @a
          2 @b
          3 def test():...
    
    
    
    In [4]: test = a(b(test))   
    

     上面演示了多个装饰器时候的实际情况,装饰器结构的是前一个装饰器的返回值,该返回值可能是包装对象,或是原函数。如此,就必须注意排列顺序,因为每个装饰器的返回值并不相同。

    我们必须确保类型方法的装饰器是最外面的一个,因为无法确定内层装饰器如何实现。

     [5]: class X: 
       ...:      
       ...:     @classmethod 
       ...:     @log 
       ...:     def test(cls):... 
       ...:                        
    

    参数,除被装饰的目录外,还可向装饰器传递其他参数,以实现更多定制特性。

    这个相对比较好理解,我不抄书了,有些书上面定义为装饰器工厂

    可以在用装饰器的时候,在@log(xxx)的时候给内部装饰器带去参数xxx,已实现不同的逻辑功能。

    属性

    我们应该让包装函数更像原函数一些,比如拥有某些相同的属性。

    In [6]: import functools                                                                                                                                           
    
    In [7]: def log(fn): 
       ...:      
       ...:     @functools.wraps(fn) 
       ...:     def wrap(*args, **kwargs): 
       ...:         return fu(*args, **kwargs) 
       ...:     print(f'wrap:{id(wrap)}, func:{id(fn)}') 
       ...:     return wrap 
       ...:                                                                                                                                                            
    
    In [8]: @log 
       ...: def add(x: int, y: int) -> int: 
       ...:     return x + y 
       ...:                                                                                                                                                            
    wrap:4372788576, func:4372788864
    
    In [9]: add.__name__                                                                                                                                               
    Out[9]: 'add'
    
    In [10]: add.__annotations__                                                                                                                                       
    Out[10]: {'x': int, 'y': int, 'return': int}
    
    In [11]: id(add), id(add.__wrapped__)                                                                                                                              
    Out[11]: (4372788576, 4372788864)
    
    In [12]: def log(fn): 
        ...:      
        ...:      
        ...:     def wrap(*args, **kwargs): 
        ...:         return fu(*args, **kwargs) 
        ...:     print(f'wrap:{id(wrap)}, func:{id(fn)}') 
        ...:     wrap=functools.wraps(fn)(wrap) 
        ...:     return wrap 
        ...:      
        ...:                                                                                                                                                           
    
    In [13]: @log 
        ...: def add(x: int, y: int) -> int: 
        ...:     return x + y 
        ...:                                                                                                                                                           
    wrap:4405430896, func:4405430176
    
    In [14]: id(add), id(add.__wrapped__)                                                                                                                              
    Out[14]: (4405430896, 4405430176)
    
    In [15]:  
    

     functools.wrap将原函数的__module__,__name__,__doc__,__annotations__等属性复制到包装函数,还用__wrapped__存储原始函数或上一装饰器返回值。

    可据此然开装饰器对单元测试的干扰,从代码中明显看到,functools.wraps是一个装饰器工厂。

    类型装饰器

    装饰器同样可用于类型,这里的区别无非是接收的参数为类型对象而已

    def log(cls):
    
        class wrapper:
    
            def __init__(self, *args, **kwargs):
                # 将cls实例返回给wrapper实例的属性inst
                self.__dict__['inst'] = cls(*args, **kwargs)
    
            def __getattr__(self, item):
                value = getattr(self.inst, item)
                print(f'get: {item} = {value}')
                return value
    
            def __setattr__(self, key, value):
                print(f'set: {key} = {value}')
                setattr(self.inst, key, value)
        # 返回类本身
        return wrapper
    
    @log
    class X:
        ...
    
    x = X()
    x.a=1
    print(x.a)
    

     上面通过装饰器内部定义一个类,装饰一个类,将被装饰的类的实例装饰成装饰器内部类实例的一个属性,完成被装饰的功能。

    前面的使用包装类比较麻烦,可以直接使用函数,间接调用目标构建方法创建实例。

    Press ENTER to continue...                                                                                                                                         
    In [16]: def log(cls): 
        ...:     functools.wraps(cls) 
        ...:     def wrap(*args,**kwargs): 
        ...:         o = cls(*args, **kwargs) 
        ...:         print(f'log: {o}') 
        ...:         return o 
        ...:     return wrap 
        ...:                                                                                                                                                           
    
    In [17]: @log 
        ...: class X:...                                                                                                                                               
    
    In [18]:                                                                                                                                                           
    
    In [18]: X()                                                                                                                                                       
    log: <__main__.X object at 0x106a8f590>
    Out[18]: <__main__.X at 0x106a8f590>
    
    In [19]:                                    
    

     这个直接就返回了实例化的对象。

    应用

    利用装饰器功能,我们可以编写各种辅助开发工具,完成诸如调用跟踪、性能测试、内存检测等任务。当然更多的时候用于模型设计,改善代码结构。

    调用跟踪

    记录目标调用参数、返回值,以及执行次数和执行时间等信息

    In [19]: def call_count(fn): 
        ...:      
        ...:     def counter(*args, **kwargs): 
        ...:         counter.__count__ += 1 
        ...:         return fn(*args, **kwargs)
            # 给函数属性复制,保持状态 ...: counter.__count__ = 0 ...: return counter ...: In [20]: @call_count ...: def a():... In [21]: @call_count ...: def b():... In [22]: In [22]: a();a();a.__count__ Out[22]: 2 In [23]: b();b();b();b.__count__ Out[23]: 3 In [24]: a();a.__count__ Out[24]: 3 In [25]:

     通过闭包的查看

    In [25]: a.__closure__                                                                                                                                             
    Out[25]: 
    (<cell at 0x106f92510: function object at 0x107035950>,
     <cell at 0x106f92450: function object at 0x107035a70>)
    
    In [26]: a.__closure__[0].cell_contents                                                                                                                            
    Out[26]: <function __main__.call_count.<locals>.counter(*args, **kwargs)>
    
    In [27]: a.__closure__[1].cell_contents                                                                                                                            
    Out[27]: <function __main__.a()>
    
    In [28]:      
    

     a里面有两个闭包元素,一个传入的函数,还有一个就是counter函数,因为counter函数有了属性的赋值。

    根据本书中闭包的定义,是指函数离开生成环境后,依然可记住,并持续引用语法作用域里的外部变量。

    这里有两个,一个是外部传入函数,一个是定义的函数的属性,都在函数离开生成环境后,依然可记住。

    在标准库中有类似的应用,通过缓存结果减少目标执行次数。

    @functools.lru_cache

    这个我用的很少,暂时就记一笔

    属性管理

    给目标添加额外属性

    In [43]: def pet(cls): 
        ...:     cls.dosomeing = lambda self:None 
        ...:     return cls 
        ...:                                                                                                                                                           
    
    In [44]: @pet 
        ...: class Parrot:...                                                                                                                                          
    
    In [45]:                                                                                                                                                           
    
    In [45]: Parrot.__dict__                                                                                                                                           
    Out[45]: 
    mappingproxy({'__module__': '__main__',
                  '__dict__': <attribute '__dict__' of 'Parrot' objects>,
                  '__weakref__': <attribute '__weakref__' of 'Parrot' objects>,
                  '__doc__': None,
                  'dosomeing': <function __main__.pet.<locals>.<lambda>(self)>})
    
    In [46]:    
    

     实例管理

    书中写了一个单例

    class Singleton:
    
        def __init__(self, cls):
            self.cls = cls
            self.inst = None
    
        def __call__(self, *args, **kwargs):
            if not self.inst:
                self.inst = self.cls(*args, **kwargs)
            return self.inst
    
    def singleton(cls):
        inst = None
        def wrap(*args, **kwargs):
            nonlocal inst
            if not inst:
                inst = cls(*args, **kwargs)
            return inst
        return wrap
    
    @Singleton
    class My:
        ...
    
    @singleton
    class My1:
        ...
    
    m1 = My1(); m2= My1()
    m3 = My(); m4 = My()
    print(m1 is m2, m3 is m4)
    
    True True
    

    部件注册

    class App:
        def __init__(self):
            # 初始化路由表
            self.routers = {}
    
        def route(self, url):
            # 通过route将地址与函数对应起来,最外层接收参数,该层接收被装饰的函数,写入路由表
            def register(fn):
                self.routers[url] = fn
                return fn
            return register
    
    app = App()
    
    @app.route('/')
    def index():
        ...
    
    @app.route('/help')
    def help():
        ...
    print(index)
    print(app.routers)
    
    <function index at 0x107990290>
    {'/': <function index at 0x107990290>, '/help': <function help at 0x107990320>}
    

    描述符

    函数就是一个非数据描述符,函数能够变方法就是因为__get__的作用。

    描述符属性必须定义为类型成员,所以其自身不适合存储实例相关的状态。

    在创建属性时,__set_name__方法被调用,并可通过参数获知目标类型(owner),以及属性名称

    定一个完整的描述符

    class descriptor:
    
        # 前面的学习中都是通过__init__来使用初始值的赋值,__set_name__第一次接触
        # 前期流畅的Python需要通过__init__需要通过定义不同的变量名才可以,定制唯一的self.name,非常不方便
        # 这里通过__set_name可以非常方便的在一开始就传入属性复制的时候变量名,后续的操作,直接操作self.name非常方便
        # 查了一下这个是Python3.6以上才有的,难怪流畅的Python没有,用这个方便制作描述符多了
        def __set_name__(self, owner, name):
            print(f'name: {owner.__name__}-------{name}')
            # 通过__set_name__复制实例name
            self.name = f"__{name}__"
    
        def __get__(self, instance, owner):
            print(f"get: {instance}, {owner}")
            return getattr(instance, self.name, None)
    
        def __set__(self, instance, value):
            print(f"set: {instance}, {value}")
            return setattr(instance, self.name, value)
    
        def __delete__(self, instance):
            print(f'del: {instance}')
            raise AttributeError("delete is disabled")
    
    class X:
        data = descriptor()
        data2 = descriptor()
    
    x = X()
    x.data=22
    x.data2 = 55
    print(x.data)
    print(x.data2)
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/Python学习笔记/第九章元编程/9_2.py
    name: X-------data
    name: X-------data2
    set: <__main__.X object at 0x10b99b990>, 22
    set: <__main__.X object at 0x10b99b990>, 55
    get: <__main__.X object at 0x10b99b990>, <class '__main__.X'>
    22
    get: <__main__.X object at 0x10b99b990>, <class '__main__.X'>
    55
    
    Process finished with exit code 0
    

    数据描述符

    这个前期流畅的Python也有介绍,这里的介绍更加精简,当做回忆,前面的忘的差不多了

    如果定义了__set__或__delete__方法,我们变称呼数据描述符(data descriptor)而仅有__get__的则是非数据描述符(non-data descriptor)。

    这两者的区别在于,数据描述符属性的优先级高于实例名字空间中的同名成员

    class descriptor2:
    
        def __get__(self, instance, owner):
            print('__get___')
    
        def __set__(self, instance, value):
            print('__set__')
    
    class X2:
        data = descriptor2()
    
    x = X2()
    # 与描述符同名属性,如果想传入实例属性,只能通过__dict__传入
    x.__dict__['data'] = 200
    # setattr(x, 'data' , 200)
    print(vars(x))
    print(x.data)
    
    {'data': 200}
    __get___
    None
    
    class descriptor2:
    
        def __get__(self, instance, owner):
            print('__get___')
        #
        # def __set__(self, instance, value):
        #     print('__set__')
    
    class X2:
        data = descriptor2()
    
    x = X2()
    # 与描述符同名属性,如果想传入实例属性,只能通过__dict__传入
    x.__dict__['data'] = 200
    # setattr(x, 'data' , 200)
    print(vars(x))
    print(x.data)
    
    name: X-------data
    name: X-------data2
    {'data': 200}
    200
    

     可以看到当仅有__get__实例属性能够覆盖非数据描述符

    属性(property)是属于数据描述符,因为其定义了__get__与__set__还有__delete__

    方法绑定

    因为函数默认实现了描述符协议,所以当以实例或类型访问方法时,__get__首先被调用

    类型和实例作为参数被传入__get__,从而截获绑定目标(__self__),如此就将函数包装或绑定对象返回。实际被执行的,就时这个会隐藏传入第一参数的包装品

    说的很精简,让我对函数有了更加充分的认识。

    In [48]: class X: 
        ...:     def test(self, o): 
        ...:         print(o) 
        ...:                                                                                                                                                           
    
    In [49]: x= X()                                                                                                                                                    
    
    In [50]: x.test                                                                                                                                                    
    Out[50]: <bound method X.test of <__main__.X object at 0x1072fa750>>
    
    In [51]: x.test.__get__(x,X)                                                                                                                                       
    Out[51]: <bound method X.test of <__main__.X object at 0x1072fa750>>
    
    In [52]: m = x.test.__get__(x,X)                                                                                                                                   
    
    In [53]: m.__self__, m.__func__                                                                                                                                    
    Out[53]: (<__main__.X at 0x1072fa750>, <function __main__.X.test(self, o)>)
    

     书中说的很形象,在我们执行一个方法

    比如x.test(123)

    可以实际分为两步

    第一步将函数包装成方法返回m = x.test.__get__(x, X)

    第二步用类去执行函数X.test(m.__self__, 123)

    这样的话,里面的self就自动填入,因为我的理解都时错的,以为是有了self再去找方法,其实是通过__get__找到方法,通过方法里面的__self__传入自身。

    元类。

    元类(metaclass)制造了所有的类型对象,并将其与逻辑上的父类关联起来。

    可自定义元类,以控制类型对象的生成过程。通常自type继承,以Meta为后缀名

    In [78]: class DemoMeta(type): 
        ...:     pass 
        ...:                                                                                                                                                           
    
    In [79]: class X(metaclass=DemoMeta): 
        ...:     ... 
        ...:                                                                                                                                                           
    
    In [80]: X.__class__                                                                                                                                               
    Out[80]: __main__.DemoMeta
    
    In [81]: x = X()                                                                                                                                                   
    
    In [82]: x.a = 12                                                                                                                                                  
    
    In [83]:     
    

     对照书中样子,写一个标准版的元类

    # 本示例中的CLS为元类,self为被继承后创建的类
    class DemoMeta(type):
    
        # 使用Python3引入的特殊方法,,这个特殊方法只在元类中有用,而且必须声明为类方法。(要使用@classmethod)
        # 解释器调用元类的__new__方法之前会先调用__prepare__返回,使用类定义体中的属性创建映射。
        # 由于为类方法,所以第一个参数必须为元类、本案例中也就是(DemoMeta)
        # 随后两个参数分别是要构建的类的名称和基类组成的元祖,返回值必须是映射。
        # 简单理解就是返回的必须是一个字典类型的,后续需要创建的类的相关属性,都装在这个字典里面。
        @classmethod
        def __prepare__(cls, name, bases):
            print(f"__prepare__: {name},{bases}")
            p_dict = OrderedDict()
            # 直接这里给后续准备创建的类对象属性里面添加了__make__属性
            p_dict['__make__'] = "Make in DemoMeta"
            return p_dict
    
        # __new__很多元类的相关设置都在这里操作,因为这里是创建类的地方
        # 这下一步就是__init__初始化类的属性,所以这里的操作很重要,相关条件的设置
        # 一般都可以在这里设置,是否允许创建这个类,可以根据条件修改attrs里面继承类传递过来的属性
        def __new__(cls, name, bases, attrs):
            print(f"__new__: {cls}, {name}, {bases}, {attrs}")
            return super().__new__(cls, name, bases, attrs)
    
        # 流畅的Python中,元类对创建类属性的调试是在__init__中操作,相对来说
        # 在这个时候,类已经创建完成,这是对继承类属性操作的最后一关了
        # 这个时候,主要是对已经创建好的类进行属性初始化,所以一般的操作在__init__完成既可
        def __init__(self, name, bases, attrs):
            print(f"__init__: {self},{name},{bases},{attrs}")
            super().__init__(name, bases, attrs)
    
        # 这个是在被继承类实例化的时候进行的操作,为什么类能够被实例话,就因为元类是带有__call__属性的
        # 一般元类里面很少写这个,一般不会在这里修改或者添加参数。但可以在__call__设置报错
        # 避免直接继承元类的类能够直接实现实例化
        def __call__(self, *args, **kwargs):
            print(f"__call__: {self}, {args}, {kwargs}")
            return super(DemoMeta, self).__call__(*args, **kwargs)
    
    
    class X(metaclass=DemoMeta):
        data = 100
    
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def test(self):
            ...
    
    
    if __name__ == '__main__':
        x = X(1, 2)
        print(X.__dict__)
        print(vars(x))
        print(x.__make__)
    

    也可以用函数或者其他可调用对象代替,个人觉的这是非常方便的一种简单操作方式,不必一定要去写元类,直接在函数就可以对需要创建的的类的属性进行拦截,修改。

    # 质押返回的试一个类,函数也可以充当元类。也可以拦截类的产生。
    # 并可以对attrs进行相关设置,我自己来看,还试函数看起来更加简单。
    def demo_meta(name, bases, attrs):
        print(f"{name}, {bases}, {attrs}")
        return type(name, bases, attrs)
    
    class X2(metaclass=demo_meta):
        data = 100
    
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def test(self):
            ...
    
    X2, (), {'__module__': '__main__', '__qualname__': 'X2', 'data': 100, '__init__': <function X2.__init__ at 0x108749ef0>, 'test': <function X2.test at 0x108749f80>}
    

    参数

    这里还可向元类传递参数,实现功能的定制

    class Demo2Meta(type):
    
        def __new__(cls,name,bases,attrs, **kwargs):
            print(kwargs)
            return type.__new__(cls, name, bases, attrs)
    
    class X3(metaclass=Demo2Meta, a=1,b=2):
        ...
    
    {'a': 1, 'b': 2}
    

    继承的话

    可以通过metaclass显示继承,或者基类继承。

    应用

    基与元类,我们可以实现很多魔法,让对象拥有很高的隐式"智能",但这会大大提升代码的复杂度。除非有必要,否则不建议这么做。

    另外,元类虽然能像普通类型那样为自己的实例提供共享成员,但这里依旧要避免使用。就让元类专注与类型的创建和管理,不要掺和逻辑为好。

    静态类

    阻止类型创建实例对象

    这里直接用__call__拦截实例的穿件就行了

    class StaticClassMeta(type):
        # 一旦创建的对象实例话就报错。
        def __call__(self, *args, **kwargs):
            raise RuntimeError("can't create object for static class")
    
    class X4(metaclass=StaticClassMeta):
        ...
    
    x= X4()
    
    Traceback (most recent call last):
      File "/Users/shijianzhong/study/Python学习笔记/第九章元编程/9_3.py", line 93, in <module>
        x= X4()
      File "/Users/shijianzhong/study/Python学习笔记/第九章元编程/9_3.py", line 89, in __call__
        raise RuntimeError("can't create object for static class")
    RuntimeError: can't create object for static class
    

    密封圈

    阻止类型被继承

    class SealedClassMeta(type):
        # 设置类属性,一个集合
        types = set()
        def __init__(self, name, bases, attrs):
            if self.types & set(bases):
                raise RuntimeError("can't create object for sealed class")
            # 首相将第一个制作的类放入集合,第二个被继承的类就不行了
            self.types.add(self)
    
    class A(metaclass=SealedClassMeta):
        ...
    
    class B(A):
        ...
    

    注解

    注解(annotation)为函数参数、返回值,以及模块和类型属性添加额外的元数据

    In [96]: def add(x:int,y:int)->int: 
        ...:     return x+y 
        ...:                                                                                                                                                           
    
    In [97]: add.__annotations__                                                                                                                                       
    Out[97]: {'x': int, 'y': int, 'return': int}
    
    In [98]:  
    

     其本质上仅是一种可编程和可执行的注释,在编译期被提取,并与对象相关联。

    在运行期间,其对解释器执行没有任何影响与约束

    In [98]: add([1],[2])                                                                                                                                              
    Out[98]: [1, 2]
    
    In [99]:           
    

     简单来说,这个东西就是拿来看的,一种比较高大上的注释

    注释内容可以是任何对象或表达式。其可用于变量,但不能用于lambda函数

    In [101]: x: int =12                                                                                                                                               
    
    In [102]: x                                                                                                                                                        
    Out[102]: 12
    
    In [103]: __annotations__                                                                                                                                          
    Out[103]: {'x': int}
    
    In [104]: def test(x:{'type':int, 'range':(0,10)}=5): 
         ...:     pass 
         ...:                                                                                                                                                          
    
    In [105]: test.__annotations__                                                                                                                                     
    Out[105]: {'x': {'type': int, 'range': (0, 10)}}
    
    In [106]: test.__defaults__                                                                                                                                        
    Out[106]: (5,)
    
    In [107]: class X: 
         ...:     data: int = 10 
         ...:     def test(self, o: str) ->str: 
         ...:         ... 
         ...:                                                                                                                                                          
    
    In [108]: X.__annotations__                                                                                                                                        
    Out[108]: {'data': int}
    
    In [109]: X.test.__annotations__                                                                                                                                   
    Out[109]: {'o': str, 'return': str}
    
    In [110]:  
    

     这种注释除了lambda函数,另外的格式好像都能写。

    最后,我再次拿上廖大的orm模型对元类进行最后一次的注释,希望以后能够记住了。

    class Field(object):
    
        def __init__(self, name, column_type):
            self.name = name
            self.column_type = column_type
    
        def __str__(self):
            return '<%s:%s>' % (self.__class__.__name__, self.name)
    
    class StringField(Field):
    
        def __init__(self, name):
            super(StringField, self).__init__(name, 'varchar(100)')
    
    class IntegerField(Field):
    
        def __init__(self, name):
            super(IntegerField, self).__init__(name, 'bigint')
    
    
    # 定义元类
    class ModelMetaclass(type):
    
        def __new__(cls, name, bases, attrs):
            # 所有继承ModelMetaclass如果制作的类的名字是Model直接无条件制作。
            if name=='Model':
                return type.__new__(cls, name, bases, attrs)
            print('Found model: %s' % name)
            mappings = dict()
            # 检查类的属性值,是不是Field的实例,是的话,放入mapping
            for k, v in attrs.items():
                if isinstance(v, Field):
                    print('Found mapping: %s ==> %s' % (k, v))
                    mappings[k] = v
            for k in mappings.keys():
                attrs.pop(k)
            # 重新定义属性__table__=类名,__mappings__为所有字段k,v的字典
            attrs['__mappings__'] = mappings # 保存属性和列的映射关系
            attrs['__table__'] = name # 假设表名和类名一致
            return type.__new__(cls, name, bases, attrs)
    
    # 继承与dict可以通过=的方式进行对象实例化
    class Model(dict, metaclass=ModelMetaclass):
    
        def __init__(self, **kw):
            super(Model, self).__init__(**kw)
    
        # 因为通过=的方式进行实例化,进行.读取属性的时候,会没有发现该属性,进入这里
        # 通过字典的方式读取key的方式返回属性
        def __getattr__(self, key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError(r"'Model' object has no attribute '%s'" % key)
    
        # 定义了写入属性的方法,一定写入该属性后,会覆盖字典=号复制的属性
        def __setattr__(self, key, value):
            self[key] = value
    
        # 定义了保存对象,也就是执行sql语句的逻辑,这里是关键
        def save(self):
            fields = []
            params = []
            args = []
            # 读取__mappings__也就是元类中修饰以后所有的字段字段
            for k, v in self.__mappings__.items():
                # 字段名放入列表
                fields.append(v.name)
                params.append('?')
                # 就是这里读取对象的属性,如果元类不删除原丝的类属性,会优先读取类属性。
                args.append(getattr(self, k, None))
            # 拼接sql语句
            sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
            print('SQL: %s' % sql)
            print('ARGS: %s' % str(args))
    
    
    class User(Model):
        # 定义类的属性到列的映射:
        id = IntegerField('id')
        name = StringField('username')
        email = StringField('email')
        password = StringField('password')
    
    u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
    u.name = 'sidian'
    u.save()
    
  • 相关阅读:
    android--------Popupwindow的使用
    virtualbox+vagrant学习-3-Vagrant Share-3-SSH Sharing
    virtualbox+vagrant学习-3-Vagrant Share-2-HTTP Sharing
    virtualbox+vagrant学习-3-Vagrant Share-1-简介
    virtualbox+vagrant学习-4-Vagrantfile-2-Configuration Version
    virtualbox+vagrant学习-4-Vagrantfile-1-简介
    virtualbox+vagrant学习-3-Vagrant Share-6-Custom Provider
    virtualbox+vagrant学习-3-Vagrant Share-5-Security
    virtualbox+vagrant学习-3-Vagrant Share-4-Vagrant Connect
    AOP
  • 原文地址:https://www.cnblogs.com/sidianok/p/12670915.html
Copyright © 2011-2022 走看看