【python进阶 笔记】元类Metaclasses、实现ORM

【python进阶 笔记】元类Metaclasses、实现ORM

目录

1. 元类

1.1. 类也是对象

1.2. 动态的创建类

1.3. 使用type创建类

1.4. 使用type创建复杂的类

1.4.1. type创建带有继承的类

1.4.2. 添加实例方法

1.4.3. 添加静态方法

1.4.4. 添加类方法

1.4.5. 较为完整的使用type创建类

1.5.元类定义

1.6.  __metaclass__属性

1.7. 元类应用

1.8. 更新:元类中的__call__方法

注:关于元类中的new、init、call

1.9. 元类的使用场景 

2.元类实现ORM (Object Relational Mapping)

2.1. ORM是什么

2.2. 通过元类简单实现ORM中的insert功能


1. 元类

元类就是用来创建类的“东西”,元类创建类,类创建实例对象

1.1. 类也是对象

在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:

以下在ipython3中,通过类生成了一个对象并输出:

>>>class ObjectCreator(object):
…       pass
…
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

 

但是,Python中的类还远不止如此。类同样也是一种对象。只要使用关键字class,Python解释器在执行的时候就会自动创建一个对象。

执行以下代码段:

class ObjectCreator(object):
    pass

python将在内存中自动创建一个对象,名字就是ObjectCreator。

这个对象(类对象ObjectCreator)拥有创建对象(实例对象)的能力。但是,它的本质仍然是一个对象,可以对它做针对对象的操作:

  • 赋值给一个变量
  • 拷贝它
  • 为它增加属性
  • 作为函数参数进行传递

注:

  • globals() 函数会以字典类型返回当前位置的全部全局变量,包括所有全局对象的引用。即返回的字典会包含了python定义好的变量,也包含了自己定义的变量,可以直接使用。
  • 想查看一个类或模块里面有什么,可以使用 类.__dict__ 或 模块.__dict__
  • globals()的返回有一个内建模块 __builtins__ ,里面包含了常用的print 等对象(__builtins__.__dict__可见 )。
  • 用一个变量名或函数名时,先直接到globals()返回的字典里面找,找不到再到字典的内建模块 __builtins__ 去找。

1.2. 动态的创建类

因为类也是对象,可以在运行时动态的创建它们,就像其他任何对象一样。可以在函数中创建类,使用class关键字。

def choose_class(name):
    if name == 'foo':
        class Foo(object):
            pass
        return Foo     # 返回的是类,不是类的实例
    else:
        class Bar(object):
            pass
        return Bar
MyClass = choose_class('foo')
print(MyClass)  # 函数返回的是类,不是类的实例

print(MyClass())  # 你可以通过这个类创建类实例,也就是对象

# 输出:
# <class '__main__.choose_class.<locals>.Foo'>
# <__main__.choose_class.<locals>.Foo object at 0x7fa8b5ac2f98>

根据传递的参数不一样,返回的类的引用不一样,然后导致创建的实例对象不一样。但这还不够动态,因为仍然需要自己编写整个类的代码。可以使用type动态的创建类。(:type函数功能之一能够返回一个对象的类型,如print(type(1)) 会打印<type 'int'>)
 

1.3. 使用type创建类

type还有一种完全不同的功能,即动态的创建类函数type实际上是一个元类。

type可以接受一个类的描述作为参数,然后返回一个类对象

type(类名, 由元组组成的父类名称(针对继承的情况,可以为空),包含属性的字典(名称和值))

以下分别用两种方法创建了类Test1和类Test2:

In [13]: class Test:
   ....:     num = 100
   ....:     num2 = 200
   ....:     

In [14]: Test2 = type("Test2", (), {"num":100, "num2":200})

使用help来测试这2个类:

In [16]: help(Test)  # 用help查看Test类

Help on class Test in module __main__:

class Test(builtins.object)
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  num = 100
 |  
 |  num2 = 200


In [18]: help(Test2)

Help on class Test2 in module __main__:

class Test2(builtins.object)
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  num = 100
 |  
 |  num2 = 200

可见,二者是一样的。

1.4. 使用type创建复杂的类

1.4.1. type创建带有继承的类

Test11继承于Test,Test22继承于Test2.

In [1]: class Test:
    num = 100
    num2 = 200
   ...:     

In [2]: Test2 = type("Test2", (), {"num":100, "num2":200})

In [3]: class Test11(Test):
   ...:     pass
   ...: 

In [4]: Test22 = type("Test22", (Test2,), {})

再使用help查看,两个类是一样的。

1.4.2. 添加实例方法

In [15]: class Foo(object):
   ....:     bar = True
   ....:     

In [16]: def echo_bar(self):   # 定义了一个普通的函数
   ....:     print(self.bar)
   ....:    
 
In [17]: FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})  # 让FooChild类中的echo_bar属性,指向了上面定义的函数

In [18]: hasattr(Foo, 'echo_bar')  # 判断Foo类中 是否有echo_bar这个属性
Out[18]: False

In [19]: hasattr(FooChild, 'echo_bar')  # 判断FooChild类中 是否有echo_bar这个属性
Out[19]: True

In [20]: my_foo = FooChild()

In [21]: my_foo.echo_bar()
True

1.4.3. 添加静态方法

在上面代码的基础上

In [36]: @staticmethod
    ...: def test_static():
    ...:     print("static method ....")
    ...:

In [37]: Foochild = type('Foochild', (Foo,), {"echo_bar": echo_bar, "test_static": test_static})

In [38]: fooclid = Foochild()

In [39]: fooclid.test_static
Out[39]: <function __main__.test_static>

In [40]: fooclid.test_static()
static method ....

In [41]: fooclid.echo_bar()
True

1.4.4. 添加类方法

在上面代码的基础上

In [42]: @classmethod
    ...: def test_class(cls):
    ...:     print(cls.bar)
    ...:

In [43]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "test_static": test_static, "test_class": test_class})

In [44]: fooclid = Foochild()

In [45]: fooclid.test_class()
True

1.4.5. 较为完整的使用type创建类

class A(object):
    num = 100

def print_b(self):
    print(self.num)

@staticmethod
def print_static():
    print("----haha-----")

@classmethod
def print_class(cls):
    print(cls.num)

# B继承于A,
B = type("B", (A,), {"print_b": print_b, "print_static": print_static, "print_class": print_class})
b = B()
b.print_b()
b.print_static()
b.print_class()
# 结果
# 100
# ----haha-----
# 100

1.5.元类定义

  • 元类就是用来创建类的“东西”。创建类是为了创建类的实例对象。
  • 但是Python中的类也是对象,元类就是用来创建这些类(对象)的,元类就是类的类
  • 元类创建类,类创建实例对象
MyClass = MetaClass() # 使用元类创建出一个对象,这个对象称为“类”
my_object = MyClass() # 使用“类”来创建出实例对象

前面用到的函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类

  • type就是创建类对象的类。可以通过检查__class__属性来验证这一点。

:实例调用__class__属性时会指向该实例对应的类(即那个类创建了该实例),还可以再去调用其它类属性)

  • Python中所有的东西,都是对象。包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type。
In [24]: class T(object):
   ....:     pass
   ....: 

In [25]: t = T()

In [26]: t.__class__
Out[26]: __main__.T

# 以上可见t由当前模块的T创建


In [27]: t.__class__.__class__
Out[27]: type

# 以上可见T由type创建 (注:type还是type创建的)

1.6.  __metaclass__属性

定义一个类时,可以为其添加__metaclass__属性。定义了__metaclass__就定义了这个类的元类

class Foo(object):  # python2
    __metaclass__ = something…
    ...省略...

class Foo(metaclass=something):  # python3
    __metaclass__ = something…

这样,Python就会用元类来创建类Foo。执行到语句class Foo(object):时,类Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类。

如,以下代码 :

class Foo(Bar):
    pass

Python做了如下的操作:

  1. Foo中有__metaclass__这个属性吗?如果是,Python会通过__metaclass__创建一个名字为Foo的类(对象)
  2. 如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。
  3. 如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。
  4. 如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。

问:在__metaclass__中放置些什么代码呢?

答:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的东东都可以。

1.7. 元类应用

元类的主要目的就是为了当创建类时能够自动地改变类

创建类时,metaclass用来标记元类是谁,不写metaclass会默认调用type来创建类。

元类所做的事情概括起来就是

  1. 拦截类的创建
  2. 修改类
  3. 返回修改之后的类

demo1:

假设,你决定在你的模块里所有的类的属性都应该是大写形式。方法之一就是通过在模块级别设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

幸运的是,__metaclass__实际上可以被任意调用,它并不需要是一个正式的类。

例子如下,以下也相当于自定义了一个元类

#-*- coding:utf-8 -*-
def upper_attr(class_name, class_parents, class_attr):

    #遍历属性字典,把不是__开头的属性名字变为大写
    new_attr = {}
    for name,value in class_attr.items():
        if not name.startswith("__"):  # 不是下划线开头时
            new_attr[name.upper()] = value

    #调用type来创建一个类
    return type(class_name, class_parents, new_attr)

class Foo(object, metaclass=upper_attr):
    bar = 'bip'

print(hasattr(Foo, 'bar'))
print(hasattr(Foo, 'BAR'))

f = Foo()
print(f.BAR)

以上,类名Foo传给了class_name,父类object传给了class_parents,把类内部的内容以字典的方式传给了class_attr。把传过来的字典里的key改为大写再传给type并返回。

上面传的upper_attr是一个函数,而不是一个类,并不十分符合元类这个名称。下面Demo2用一个真正的class来当做元类.

Demo2:

#coding=utf-8

class UpperAttrMetaClass(type):  # 继承了type类的子类,相当于是个元类
    # __new__ 是在__init__之前被调用的特殊方法
    # __new__是用来创建对象并返回之的方法
    # 而__init__只是用来将传入的参数初始化给对象
    # 你很少用到__new__,除非你希望能够控制对象的创建
    # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
    # 如果你希望的话,你也可以在__init__中做些事情
    # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用

    def __new__(cls, class_name, class_parents, class_attr):
        # 遍历属性字典,把不是__开头的属性名字变为大写
        new_attr = {}
        for name, value in class_attr.items():
            if not name.startswith("__"):
                new_attr[name.upper()] = value

        # 方法1:通过'type'来做类对象的创建
        return type(class_name, class_parents, new_attr)

        # 方法2:复用type.__new__方法
        # 这就是基本的OOP编程,没什么魔法
        # return type.__new__(cls, class_name, class_parents, new_attr)

# python3的用法
class Foo(object, metaclass=UpperAttrMetaClass):
    bar = 'bip'

# python2的用法
# class Foo(object):
#     __metaclass__ = UpperAttrMetaClass
#     bar = 'bip'


print(hasattr(Foo, 'bar'))
# 输出: False
print(hasattr(Foo, 'BAR'))
# 输出:True

f = Foo()
print(f.BAR)
# 输出:'bip'

思考:实现定义类时,无论是否写上object,都会继承object,该如何实现?

1.8. 更新:元类中的__call__方法

注:关于元类中的new、init、call

在元类中:

创建的对象是类,如果希望能够自定义它,一般改写__new__;
如果有需要的话,也可以在__init__中做些事情;
一些高级的用法会涉及到改写__call__特殊方法。

1.9. 元类的使用场景 

  • 若定义好了函数,需要给函数添加功能时,可以使用装饰器;
  • 若定义好了类,需要对类的功能进行修改,可以使用元类;

Python界的领袖 Tim Peters说过,“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” 

2.元类实现ORM (Object Relational Mapping)

2.1. ORM是什么

ORM( Object Relational Mapping ),即对象-关系-映射, 是 python编程语言后端web框架 Django的核心思想,用其操作数据库时可以不需要再用SQL语句。

一个句话理解就是:创建一个实例对象,用创建它的类名当做数据表名,用创建它的类属性对应数据表的字段,当对这个实例对象操作时,能够对应MySQL语句。

 

说明:

  1. 所谓的ORM就是让开发者在操作数据库的时候,能够像操作对象时通过xxxx.属性=yyyy 一样简单,这是开发ORM的初衷
  2. 只不过ORM的实现较为复杂,Django中已经实现了 很复杂的操作。本节知识 主要通过完成一个 insert相类似的ORM,理解其中的道理就就可以了
class User(父类省略):
    uid = ('uid', "int unsigned")
    name = ('username', "varchar(30)")
    email = ('email', "varchar(30)")
    password = ('password', "varchar(30)")
    ...省略...


u = User(uid=12345, name='Michael', email='[email protected]', password='my-pwd')
u.save()
# 对应如下sql语句
# insert into User (username,email,password,uid)
# values ('Michael','[email protected]','my-pwd',12345)

以上Demo的思想,定义一个类,类名对应于表名,类属性相关的信息对应着每一个字段。

通过创建实例对象,然后创建实例对象的save方法,来对表进行操作。通过操作实例对象,自动转换成操纵SQL语句,而不用写SQL语句

2.2. 通过元类简单实现ORM中的insert功能

Demo:

  • 定义了一个元类ModelMetaclass(继承自type)。
  • 定义了一个User类,有四个类属性、__init__方法和save方法。User类使用了 metaclass指定了ModelMetaclass,所以不会用默认的type去创建类,而是使用指明 ModelMetaclass 。
  • 会调用ModelMetaclass的__new__方法(类名User传给name、空元组传给bases、类属性作为字典传给attrs),最后返回 type.__new__ 。
  • u=User(uid=12345, name='Michael', email='[email protected]', password='my-pwd') 执行时,User的__init__方法会被调用;
  • 最后调用save()方法:self指向实例对象...
class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        mappings = dict()
        # 判断是否需要保存
        for key, values in attrs.items():  # 注:字典(Dictionary) items() 函数以列表返回可遍历的(键, 值) 元组数组
            # 判断是否是指定的StringField或者IntegerField的实例对象
            if isinstance(values, tuple):  # 判断values是否是元组
                print('Found mapping: %s ==> %s' % (key, values))
                mappings[key] = values  # 循环结束时传进来的类属性保存到了字典mappings

        # 删除这些已经在字典中存储的属性
        for key in mappings.keys():
            attrs.pop(key)

        # 将之前的uid/name/email/password以及对应的对象引用、类名字
        attrs['__mappings__'] = mappings  # 保存属性和列的映射关系
        attrs['__table__'] = name  # 假设表名和类名一致
        return type.__new__(cls, name, bases, attrs)


class User(metaclass=ModelMetaclass):
    uid = ('uid', "int unsigned")  # !!注意:在Django中等号右边是对象,而非元祖
    name = ('username', "varchar(30)")
    email = ('email', "varchar(30)")
    password = ('password', "varchar(30)")
    
    # 当指定元类之后,以上的类属性将不在类中,而是在__mappings__属性指定的字典中存储
    # 以上User类中有(即最终User类的类属性对应为下面的样子)
    # __mappings__ = {
    #     "uid": ('uid', "int unsigned")
    #     "name": ('username', "varchar(30)")
    #     "email": ('email', "varchar(30)")
    #     "password": ('password', "varchar(30)")
    # }
    # __table__ = "User"
    
    def __init__(self, **keywargs):
        for name, value in keywargs.items():
            setattr(self, name, value)  # setattr 往self指向的对象添加一个属性。

    def save(self):
        fields = []
        args = []
        for key, values in self.__mappings__.items(): # 注:实例对象没有__mappings__,但是类对象里面有
            fields.append(values[0])  # 取元组values第一个值添加(如:第一次循环取元组('uid', "int unsigned")的uid)
            args.append(getattr(self, key, None)) # getattr取对象的key的值,添加到列表args

        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join([str(i) for i in args]))
        print()
        print('SQL: %s' % sql)


u = User(uid=12345, name='Michael', email='[email protected]', password='my-pwd')
# print(u.__dict__)
u.save()

输出:

Found mapping: email ==> ('email', 'varchar(30)')
Found mapping: password ==> ('password', 'varchar(30)')
Found mapping: uid ==> ('uid', 'int unsigned')
Found mapping: name ==> ('username', 'varchar(30)')

SQL: insert into User (email,username,uid,password) values ([email protected],Michael,12345,my-pwd)

注意看,生成的SQL语句是有问题的:...values ([email protected],Michael,12345,my-pwd),括号内有些值应该加上引号。

改进版本

将语句

         sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join([str(i) for i in args]))

改成

        args_temp = list()
        for temp in args:
            # 判断入如果是数字类型
            if isinstance(temp, int):
                args_temp.append(str(temp))
            elif isinstance(temp, str):
                args_temp.append("""'%s'""" % temp)
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args_temp))
        # 例如,经过以上["12345", """'xxxx'""", """'[email protected]'""", """'567'"""]就会变成 12345,'xxxx','[email protected]','567'
       

最后一行输出变为:

SQL: insert into User (email,username,password,uid) values ('[email protected]','Michael','my-pwd',12345)

扩展:

  • 字典(Dictionary) 的   items() 函数以列表返回可遍历的(键, 值) 元组数组。
  • setattr(object, name, value)函数对应函数 getattr(),用于设置属性值,该属性不一定是存在的。往self指向的对象添加一个属性。
    • object -- 对象。
    • name -- 字符串,对象属性。
    • value -- 属性值。
  • getattr() 函数用于返回一个对象属性值。getattr(object, name[, default])
    • object -- 对象。
    • name -- 字符串,对象属性。
    • default -- 默认返回值,如果不提供该参数,在没有对应属性时,将触发 AttributeError
  • join() 方法用于将序列中的元素以指定的字符连接生成一个新的字符串。返回通过指定字符连接序列中元素后生成的新字符串。str.join(sequence)
    • str--指定的字符
    • sequence -- 要连接的元素序列。

最后一句,ORM做到了操作对象,就相当于操作数据表

-----end-----

发布了50 篇原创文章 · 获赞 10 · 访问量 6603

猜你喜欢

转载自blog.csdn.net/qq_23996069/article/details/104556135