前言:

  我们在操作数据库时候一般都是通过sql代码来操作mysql数据库中相关数据,这就需要懂得sql语句,那么怎么样才能在不懂sql语句的情况下通过我们所学的python代码来实现对mysql数据库的操作?

  当然有这种神奇的操作,其中之一就是今天深入了解的ORM对象关系映射(Object Relational Mapping),本文主要通过python代码来自己实现mysql数据库的对象关系映射,达到对前面所学知识的巩固与加深。

一、先来说说具体映射关系:(记住这个关系,在后面具体代码实现的时候会用到)

ORM:对象关系映射:
类     =======>     数据库的一张表
对象      =======>     表的一条记录
对象点属性    =======>    记录某一个字段对应的值
上面关系分析:
通过python中创建类来实现对数据库一张表结构的关系产生一种一一对应关系
通过python中对创建的类实例化的对象操作对数据库一张表进行表记录的操作的一一对应关系
通过python中对象点属性的方式来操作记录表中某一字段的对应值,的一一对应操作关系

首先来通过代码层面来映射数据库表字段的类型:

# 定义一个类,在映射数据库中的表结构:
class Field(object): 
    #  先定义一个表结构字段类,比如 字段名name、字段类型column_type、字段是否为主键primary_key、字段默认值default
    def __init__(self, name, column_type, primary_key, default):
        self.name = name
        self.column_type = column_type
        self.primary_key = primary_key
        self.default = default
    
#  当然字段的类型很多,可以单独设特殊的字段类:比如varchar、int字段类型,让它继承FIeld类就行
class StringField(Field):  #  定义字段类型varchar
    #  将字段类型指定为:varchar(255),主键默认为False,默认值为None
    def __init__(self, name, column_type='varchar(255)', primary_key=False, default=None):
        #  让它重写__init__的基础上其他地方继承它的基类Field里面的__init__方法
        super().__init__(name, column_type, primary_key, default)
        
class IntegerField(Field):  # 定义字段类型int
    def __init__(self, name, column_type='int', primary_key=False, default=None):
        super().__init__(name, column_type, primary_key, default)

暂时先创建2种常见类型的字段类型类

接着来看看如何映射数据库表的结构:

# 创建一个字典对象的过程:t1 = dict(name='sgt', age=18, sex = 'male')
# 让Models类继承字典这个类,这样Models类就继承了dict类的方法(把一堆关键字参数传进去,返回一个字典)的实例化过程
class Models(dict):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
    
    # 除了继承dict类的方法,我们还需要拥有更多方法,比如当传入的参数通过对象点(传入参数关键字名)的方法得到参数的
    # 关键字值,通过点参数名=参数值来新增传入的关键字参数
    # 继续分析:传入参数是关键字形式(name='sgt',age = 18...),但是参数不是类中属性,如果想要通过实例化出的对象点
    # 这个参数key的方式得到value值的目的,可以使用__getattr__来实现,也就是说,实例化出的对象在点这个key
    # 时候,触发了__getattr__方法,方法返回self.get(key),这里的self就是继承dict类通过传入关键字参数返回的字典
    # 类型的的对象,通过点get()就能获得对应的value值。
    def __getattr__(self, item):  # 在对象获取它没有的属性和方法的时候触发
        return self.get(item)  # item就是传入参数的k
    
    # 既然可以点k的方式得到value,那么还可以点新key=值的方法来增加传入的关键字参数
    def __setattr__(self, key, value):  # 在对象点属性=值的时候自动触发
        self[key] = value
        
    # 通过上面的__getattr__和__setattr__的方法实现了实例化出对象的方式让传入的参数返回给对象一个字典的
    # 同时又可以让这个对象点关键字中的key得到value值,点key=value值来新增或者设置值的目的
    # 这里插一嘴:为何要实现这个目的?因为我们通过pymysql模块实现操作数据库返回来的数据类型基本都是字典类型外面
    # 套列表的形式,那么如果想办法将查询的结果也变成一个字典对象,那么查询里面的key(字段名)和value(字段记录值)
    # 就特别方便了,同时在新增和插入数据时候会用到这个方法,达到更简单明了的目的。

  上面只是实现了我么在操作表记录方面的某些功能,但是我么知道还没有达到映射数据库表结构的目的

  怎么做呢?想想我们的目的:在映射表结构的时候这个表结构应该有哪些东西?

  回答:表的字段名们,表的主键是哪个字段,当然还有表名,好像有了这3个关键性的因素映射数据库表结构就差不多达到目的了。

  那么如何才能实现我们在创建一个映射表结构的一个类的同时这些我们想要的因素都能自动产生呢?

说到自动,又说道创建类的时候,我想我们可以往元类上面想了,前面学习元类的时候我们就可以拦截类的创建过程,在这个过程中加入或者修改,达到我们想要的目的。

  所以说拦截类的创建过程是关键,类创建过程会触发啥?答案是:元类的__new__方法

既然要拦截,肯定是不让元类的__new__生效,让我们自己定义一个__new__或者说在元类的__new__触发之前自己通过自定义__new__来加入一些我们需要的然后再走元类的__new__,此时就能达到目的了。

# 对指定Models的元类为MyMeta
class MyMeta(type):  # 自定义元类必须继承type才能算是元类,否则就是普通的类
    def __new__(cls, *args, **kwargs):
        print(cls)
        print(args)
        print(kwargs)

class Models(dict, metaclass=MyMeta):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def __getattr__(self, item):
        return self.get(item)

    def __setattr__(self, key, value):
        self[key] = value
    Myname = 'sgt'
# 这里创建了类Models的时候,就触发了我们自定义元类中的__new__方法,所以右键就会执行打印,结果依次是
# <class '__main__.MyMeta'>
# ('Models', (<class 'dict'>,), {'__module__': '__main__', '__qualname__': 'Models',
# '__init__': <function Models.__init__ at 0x0000025A17B19BF8>, '__getattr__':
# <function Models.__getattr__ at 0x0000025A17B19C80>, '__setattr__': <function Models.__setattr__ at
# 0x0000025A17B19D08>, 'Myname': 'sgt', '__classcell__': <cell at 0x0000025A17A87618: empty>})
# {}
# 第一行打印的是Models的类
# 仔细看第二行:第一个是Models--类名,第二个是dict这个类--也就是Models的基类,第三个是个字典,看看字典里的
# 内容,一眼瞅过去好像是一个类里面的内置属性和自定义属性(因为看到了Myname这个变量)
# 最后一行就{},关键字参数没传啥。
# 最后分析一下:创建类的时候我们拦截了类的创建过程,自定义了元类,在类创建的时候让它走了我们自定义元类里面的
# __new__方法,这样,Models这个类一'class'开始申明就开始准备走__new__方法,接着我们看了打印的各个参数:
# 分别是cls-创建的类自己、类名、类的基类们、类属性字典,所以既然类在创建时候会在__new__传入这些参数,那么我们
# 将这些参数进一步明了化一下:
class MyMeta(type):  # 自定义元类必须继承type才能算是元类,否则就是普通的类
    def __new__(cls, class_name, class_bases, class_attrs):
        print(cls)
        print(class_name)
        print(class_bases)
        print(class_attrs)

class Models(dict, metaclass=MyMeta):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def __getattr__(self, item):
        return self.get(item)

    def __setattr__(self, key, value):
        self[key] = value
    Myname = 'sgt'
# 右键再次运行一下,发现打印的结果一模一样,至此我们进一步明确化了__new__的实质了,接下来开始实现我们的初衷
# 在类创建的时候为这个类添加默认的属性:映射表名、映射表的主键字段名、映射表的自定义属性(字段名、对应字段值)
class MyMeta(type):
    def __new__(cls, class_name, class_bases, class_attrs):
        # 我们要知道一件事:我们只需要设置我们自己定义(创建类时候你写的属性)属性,其他建类时候默认的一些内置属性
        # 我们是不需要的,或者说我们可以将自己定义属性集中在一个字典中,这个字典我们起个名字:mappings
        #  # __new__拦截了哪些类的创建:Models、Models的子类,很显然Models类我们无需拦截,因为我们创建表结构映射的类
        # 并不是Models,而应该是继承了Models的一个类,所以需要排除Models
        if class_name == 'Models':
            return type.__new__(cls, class_name, class_bases, class_attrs)
        # 开始部署自定义的类属性:
        # 表名:我们在创建类体代码的时候会设置个属性叫table_name=***,如果没有设置,默认为类名
        table_name = class_attrs.get('table_name', class_name)
        primary_key = None  # 后面要找出主键是哪个字段名,这里先设置个空
        mappings = {}  #  这个mappings就是我们需要找出的自定义字段名和对应相关参数
# class_attr={'table_name':'user','id':IntegerField(name='id', primary_key=True),'name':StringField(name='name')...)
        for k, v in class_attrs.items():

 

内容来源于网络如有侵权请私信删除
你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!