04 -- 元类和ORM

Posted littlefivebolg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了04 -- 元类和ORM相关的知识,希望对你有一定的参考价值。

 

  本篇主要介绍元类,为什么说一切皆对象;如何动态的创建类等;以及ORM,即什么是ORM等知识

一、元类

  1.1 在Python中一切皆对象

  在学习元类中我们首先需要了解一个概念-- python中一切皆对象,那么为什么这么说呢?

  扩展:通过globals()--查看所有的全局变量,当我们定义全局变量时,python会将这些全局变量存放至一个字典中;而其中包含 __builtin__内嵌模块,当我们 globals()["__builtin__"].__dict__,可以看到内嵌模块中的所有内置函数或者内置模块即其对应的引用指针,例如:(只截取了小部分)

  技术分享图片

  类也是对象,当我们定义一个类,其实也是通过另外一个类(type)类创建一个实例对象,只是这个对象比较特殊,拥有着创建实例对象的特殊功能。而可能你就会问了,那么type是由什么类实例化得到的呢? type就是元类,其自身实例对象了其自身。

  技术分享图片

 

  1.2  使用type创建类

  通常我们定义类有使用以下的方式创建类:

>>> class ObjectCreator(object):
…       pass

  而同时 我们也可以通过type实例化得到类对象,其效果是一摸一样的,这种创建类的方式我们称为动态的创建类;

class Test1(object):
    name="alex"
    age = 30

Test2 = type("Test2",(object,),{"name":"alex","age":30})

  以上两种形式定义的类是完全一样的(除了类名),前者是通过关键字定义的类对象,而后者是通过type类实例化得到的类对象;

  当然也可以为该类定义实例方法、类方法、以及静态方法等,例如:

# 析构方法
def __init__(self,name,age):
    self.name = name
    self.age = age

# 实例方法
def foo(self):
    print("in the foo",self)

# 类方法
@classmethod
def bar(cls):
    print("in the foo",cls)

#静态方法
@staticmethod
def cal():
    print("in the cal")

Test =type("Test",(object,),{"info":"这是一个type实例化的类","foo":foo,"bar":bar,"cal":cal,"__init__":__init__})

test = Test("alex",22)
test.bar()
test.foo()
test.cal()

 

  1.3  什么是元类?

  元类:通俗来将 元类就是可以创建类的"东西";准确来说:元类就是可以实例化得到类对象的一种类,上述的type类就是元类;

MyClass = MetaClass()  # 使用元类创建出一个对象,这个对象称为“类”
my_object = MyClass()  # 使用“类”来创建出实例对象

 即:

  1、可以这样理解:元类通过实例化得到类对象,类通过实例化得到实例化对象。

  例如上述你看到的:

MyClass = type(MyClass, (), {})

  这种方式便是:通过元类type传入参数(参数1-- 得到类对象的名称 , 参数二----该类对象的父类们的父类名,参数三-- 存储该类对象的类属性、类方法),即便是通过这种方式得到一个类对象。

  Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type。

 

  1.4  自定义元类

  在上述我们知道可以通过type动态的创建类,那么我们是否可以定义一个类,利用继承或者组合的方式,通过派生来为创建的类对象增设一些新的功能呢?

   1.4.1 metaclass

  在自定义元类之前我们需要了解以下 metaclass:

class MyType(type):
    def __new__(cls,name,prants,attrs):
        print("in the MyType..")
        return type(name,prants,attrs)
    pass

class Foo(object,metaclass=MyType):
    pass

print(Foo.__class__)

  Python做了如下的操作:

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

  1.4.2 自定义元类

  自定义元类的主要目的就是为了当创建类时能够自动地改变类。接下来我们实现 元类把所有的属性都改成大写形式,即相当于为原来通过type类创建类对象添加或修改其方法,而不改变源代码的效果,优点类似于装饰器的效果;

class UpperAttrMetaClass(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‘

 其本质是:

  就是这样,除此之外,关于元类真的没有别的可说的了。但就自定义元类本身而言,它们其实是很简单的:

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

 

二、ORM

 2.1 什么是ORM?

  ORM 是 python编程语言后端web框架 Django的核心思想,“Object Relational Mapping”,即对象-关系映射,简称ORM。

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

  技术分享图片

  

  1. 所谓的ORM就是让开发者在操作数据库的时候,能够像操作对象时通过xxxx.属性=yyyy一样简单,这是开发ORM的初衷;
  2. 只不过ORM的实现较为复杂,Django中已经实现了 很复杂的操作,本节知识 主要通过完成一个 insert相类似的ORM,理解其中的道理就就可以了;例如:
class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        mappings = dict()
        # 判断是否需要保存
        for k, v in attrs.items():
            # 判断是否是指定的StringField或者IntegerField的实例对象
            if isinstance(v, tuple):
                print(Found mapping: %s ==> %s % (k, v))
                mappings[k] = v

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

        # 将之前的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")
    name = (username, "varchar(30)")
    email = (email, "varchar(30)")
    password = (password, "varchar(30)")
    # 当指定元类之后,以上的类属性将不在类中,而是在__mappings__属性指定的字典中存储
    # 以上User类中有 
    # __mappings__ = {
    #     "uid": (‘uid‘, "int unsigned")
    #     "name": (‘username‘, "varchar(30)")
    #     "email": (‘email‘, "varchar(30)")
    #     "password": (‘password‘, "varchar(30)")
    # }
    # __table__ = "User"
    def __init__(self, **kwargs):
        for name, value in kwargs.items():
            setattr(self, name, value)

    def save(self):
        fields = []  # 用来存储获取的字段名
        args = []  # 用来存储值
        for k, v in self.__mappings__.items():
            fields.append(v[0])
            args.append(getattr(self, k, None))
        
        # 由于传入的值需为字符串类型则进行转换
        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))
        print(SQL: %s % sql)


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

  优化如下:

技术分享图片
class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        mappings = dict()
        # 判断是否需要保存
        for k, v in attrs.items():
            # 判断是否是指定的StringField或者IntegerField的实例对象
            if isinstance(v, tuple):
                print(Found mapping: %s ==> %s % (k, v))
                mappings[k] = v

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

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


class Model(object, metaclass=ModelMetaclass):
    def __init__(self, **kwargs):
        for name, value in kwargs.items():
            setattr(self, name, value)

    def save(self):
        fields = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v[0])
            args.append(getattr(self, k, None))

        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))
        print(SQL: %s % sql)


class User(Model):
    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)
# print(u.__dict__)
u.save()
View Code

 

  over~~~ 

 

  

 

以上是关于04 -- 元类和ORM的主要内容,如果未能解决你的问题,请参考以下文章

参数化类和元类有啥区别(请使用 Python 中的代码示例)?

python中通过元类(TYPE)简单实现对象关系映射(ORM)

Python之元类ORM

如何做类似 Django 模型的元类技巧

单例类和元类

元类实现ORM