• 面向对象之魔术方法



    一、魔术方法(魔术方法特殊方法)

    __int__ 和 __new__ 方法

    • __init__ 是在创建对象的时候自动调用,对创建的对象进行初始化设置的
    • __new__ 是实力化对象的时候自动调用的
    • __new__ 方法在__init__方法之前调用,先实例了对象,在给实例初始化属性
    class Mycalss(object):
        def __init__(self, name):
            print("这个是init方法")
            self.name = name
    
        # 重写 __new__方法
        def __new__(cls, *args, **kwargs):
            print("这个是new方法")
            # 创建对象是python底层帮我实现,重写之后需要返回父类的创建对象的方法,不然实例不出对象
            return object.__new__(cls)
    
    
    m = Mycalss("DaBai")  # 先进入new方法 在执行init方法
    
    • __init__大家知道用,不做研究
    • __new__方法的应用场景:重写new方法可以实现单例模式
      • 所有实例化操作都是实例一个对象,节约内存
      • 对象属性共用,全局化
    方式一:类中重写new方法实现
    class Mycalss(object):
        instance = None
    
        # 重写 __new__方法
        def __new__(cls, *args, **kwargs):
            # 如果 instance 为None 实例化对象,否则用第一次实例的对象
            if not cls.instance:
                cls.instance = object.__new__(cls)
                return cls.instance
            else:
                return cls.instance
    m1 = Mycalss()
    m2 = Mycalss()
    # id 一样 同一个对象
    print(id(m1))
    print(id(m2))
    
    # 所以m1创建的属性,m2一样有
    m1.name="DaBai"
    print(m2.name)
    
    
    方式二:单例装饰器
    # 装饰器单例模式
    def class_one_case(cls):
        # 空字典储存 类 和 类实例(key:value)
        instace = {}
    
        def inner(*args, **kwargs):
            # 如果类不在字典中实例化对象储存,否者用字典中的对象
            if cls not in instace:
                instace[cls] = cls(*args, **kwargs)
                return instace[cls]
            else:
                return instace[cls]
        return inner
    
    
    @class_one_case
    class TestClass(object):   # TestClass=class_one_case(TestClass) 调用的时候执行的装饰器内部inner方法,返回实例
        name = ""
    
        def run(self):
            print(self.name)
    
    
    t1 = TestClass()
    t2 = TestClass()
    print(id(t1))
    print(id(t2))
    t1.name="Dabai"
    # t2 就是t1  name 属性也都公共也变成"DaBai"
    t2.run()
    

    __srt__方法和__repr__方法

    • __srt__ 输出的内容可以理解为是给用户看的,用户不关心是说明数据类型,只关心内容
    • __repr__ 可以理解为是给开发看的,开发看到这个一眼就能确认是字符转类型
    • 交互环境代码演示
    >>> a = "1"
    >>> print(a)
    1
    >>> a
    '1'
    >>>
    
    
    • 问题思考:交互环境下print打印内容和直接输入变量,返回的内容为什么会不一样?
      • 因为底层触发的魔术方法不一样
      • print方法触发的__srt__方法
      • 直接输出是触发的__repr__方法
    • Pycharm演示
    a = "123"
    print(str(a))  # 123
    print(format(a))  # 123
    print(repr(a))  # '123'
    
    
    • 重写__srt__方法和__repr__方法
      • 一定要有return
      • 一定要返回字符串类型
    class MyStrRepr(object):
        def __init__(self, name, gender):
            self.name = name
            self.gender = gender
    
        def __str__(self):
            print("出发__str__方法")
            return self.name # 不返回 或者返回的类型不是字符串的时候会报错
    
        def __repr__(self):
            print("出发__repr__方法")
            return "MySrtRepr.object.name-%s" % self.gender
    
    
    s = MyStrRepr("DaBai", "男")
    print(s)  # print 触发__str方法
    str(s)  # srt 触发__srt__
    format(s)  # format 触发__srt__
    res = repr(s)  # repr 触发__repr__   程序员输出方式
    print(res)
    
    
    • 注意
      • 1、如果__srt__方法没有重写,print、str、format方法会去触发__repr__,__repr__没有就去找父类的__srt__方法
      • 2、使用repr方法时,会先找自身__repr__方法,如果没有就直接去父类里找
      • 可以理解为__repr__是__str__备胎-见下图

    __call__方法

    • 问题一:在Python中万物皆对象,函数也是对象,为什么函数可以调用,而其他的对象不行呢?
    • 如果想让类创建出来的对象,可以像函数一样被调用可以实现么?
      • 那么我们只需要在类中定义__call__方法即可
    
    # __call__ 可以被调用的方法 像函数一样加() 可以被调用,
    # 实例不能被调用是因为 实例和函数底层实现的方法不一样
    # 函数底层实现的call方法
    def fun():
        print("函数")
    
    
    class My(object):
        def __init__(self, name):
            print("这个是init方法")
            self.name = name
    
    
    print("函数内部实现的方法", dir(fun))  # 实现了'__call__'
    m1 = My("DaBai")
    print("实例实现的方法", dir(m1))  # 没有实现__call
    m1()  # 被执行会报错
    
    class My(object):
        def __call__(self, *args, **kwargs):
            print("__实例被执行了__")
    
    
    m = My()
    m()  # 不会报错 会执行类中__call__方法内的代码块
    
    
    • __call__方法应用小案例:利用类实现装饰器
    # 类装饰器
    class MyCall(object):
        def __init__(self, fun_cls):
            # print("这个是init方法")
            self.fun_cls = fun_cls
    
        def __call__(self, *args, **kwargs):
            print("call方法")
            return self.fun_cls(*args, **kwargs)
    
    
    @MyCall
    def fun(a):  # fun = Mycall(fun)  此时的fun 是 Mycall的实例对象了,被调用时执行call方法
        print("函数%s" % a)
    
    
    @MyCall
    class My(object):  # My = Mycall(My)  此时的My 是 Mycall的实例对象了,被调用时执行call方法
        def __init__(self, name):
            self.name = name
            
    print(fun)  # <__main__.MyCall object at 0x0000022ECE480320> MyCall的实例对象
    fun(1)  # 实例被执行 执行的call方法,call方法里面执行了run()函数
    
    print(My)  # <__main__.MyCall object at 0x0000012B8FDB03C8> MyCall的实例对象
    m = My("DaBai")  # MyCall的实例对象执行call方法 返回 My类的实例对象
    print(m)  # <__main__.My object at 0x0000012B8FDB0470> My的实例对象
    

    上下文管理器

    • 问题思考:打开文件加上with关键字 文件会自动化关闭?

    上下文管理器的概念:上下文管理器是一个Python对象,为操作提供了上下文信息;这种额外的信息,在使用with语句初始化上下文,以及完成with语句的所有代码时,采用可调用的形式。该场景主要自动化触发两个方法:

    • object.__enter__(self)

    输入于对象相关的运行时上下文,如果存在的话,with语句将绑定该方法的返回值到 as 子句钟指定目标

    • biject.__exit__(self,exc_type, exc_val, exc_tb)
      • exc_type : 异常类型
      • exc_val : 异常值
      • exc_tb : 异常回溯追踪

    退出此对象相关上下文的操作方法,参数是导致上下文退出时异常报错时,捕获到相关异常信息,如果该上下文退出时没有异常,三个参数都将为None

    • 下面看代码自己实现一个上文管理操作文件的类
    # 上下文管理
    
    with open("1.txt", "r+", ) as f:
        print(f)  # 默认gbk
    
    # with 不是上下文管理器
    # 在with 场景下会自动触发 上下文管理器的__enter__ 方法  和最后执行的__exit__方法
    
    
    class MyOpen:
        """
         实现打开文件的上下文管理器,默认utf-8 内置的open默认gbk
        """
    
        def __init__(self, file_path, open_method, encoding="utf-8"):
            self.file_path = file_path
            self.open_method = open_method
            self.encoding = encoding
    
        def __enter__(self):
            print("__enter__")
            self.file = open(self.file_path, self.open_method, encoding=self.encoding)
            return self.file
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print("最后退出with的执行__exit__")
            self.file.close()
    
    
    with MyOpen("1.txt", "r") as f:  # 这里就进入__inter__方法了
        print(f.read())
    
    print(f.closed)  # with语句全部执行完毕的实话,在执行__exit__, >>> True
    
    
    • 同理封装一个操作数据库上下文管理器
    class OpenMysql(object):
        """
        数据库上下文管理器
        config : 数据库配置文件
        """
    
        def __init__(self, config):
            self.config = config
            self.connect = connector.connect(**self.config)
            self.cursor = self.connect.cursor()
    
        def __enter__(self):
            return self.cursor
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            self.cursor.close()
            self.connect.close()
    

    算数运算的实现

    • 思考:Pyton钟不仅数值之间能相加,字符串、列表、元组 也可以相加,这是怎么实现的?
      • 同类型对象之间使用+号这种场景的情况下,实际是自动触发了__add__ 魔术方法
    # 算数运算自己封装类
    class MyStr(object):
        def __init__(self, data):
            self.data = data
    
        def __str__(self):
            return self.data
    
        def __add__(self, other):
            print("触发了魔术方法")
            # 打印对象触发 __str__ 拿对象返回值
            print("self:{}".format(self))
            print("other:{}".format(other))
            # 实现字符串加法
            return self.data + other.data
    
        # 减法,python字符串没有实现减法,我们自己可以简单实现以下
        def __sub__(self, other):
            # 把减号后面的字符传替换成空
            return self.data.replace(other.data, "")
    
    
    s1 = MyStr("11111")
    s2 = MyStr("22222")
    # 执行可以看出:加号前面的对象 触发的add方法,加号后面的对象当作参数传到add方法中other
    print(s1 + s2)  # 此时直接触发了add 方法
    
    s3 = MyStr(s1 + s2)
    print(s3 - s1)  # 简单实现了减法
    
    • 其他的运算魔术方法
      • __mul__(self, other) : 定义乘法行为:*
      • __truediv__(self, other) : 定义除法行为:/
      • __floordiv__(self, other) : 定义整数触发行为 : //
      • __mod__(self, other) : 定取余行为 : %
      • 更多请移步开头的大佬博客中

    二、多态

    面向对象三大特征

    • 封装 : 将数据和方法放在一个类中就构成了封装
    • 继承 : Python中一个类可以继承于一个类也可以继承多个类,被继承的类叫父类(或者叫基类,base class),继承的类叫子类(可以获得父类的属性和方法)
    • 多态(Polymorphism):指的是一类事物有多种形态,一个抽象类有多个子类(因而多态的概念依赖于继承),不同的子类对象调用相同的方法,产生不同的执行结果,多态可以增加代码的灵活程度
    • Python因为是弱类型语音,不需要声明数据类型,固Python中严格上来说不存在多态,而是更加强大的--鸭子模式

    多态的实现步骤

    • 1、定义一个父类(base),实现某个方法(比如:run)
    • 2、定义多个子类,在子类中重写父类的方法(run),每个子类run方法实现不通的功能
    • 3、假设我们定义了一个函数,需要一个Base类型的对象作为参数,那么调用函数的时候,传入Base类的不同的子类,那么这个函数就会执行不同的功能,这就是多态的提现
    • 4、因为Python中对函数的参数没有数据类型限制,不是Base类型的数据,一样可以传进函数中,我们只要随便写一个类哪怕不继承Base类,类中也实现了run方法,此时传进函数中也能实现不同的run功能,所以才说Python实现的是伪多态,也就是鸭子模式
    # 实现一个Base类,子类继承重写run方法
    class Base:
        def run(self):
            print("_____base___run___:会跑")
    
    
    class Cat(Base):
        def run(self):
            print("_____cat___run___:能抓老鼠")
    
    
    class Dog(Base):
        def run(self):
            print("_____dog___run___:能抓兔子")
    
    
    b_obj = Base()
    
    c_obj = Cat()
    
    d_obj = Dog()
    
    # 问题一:子类都属于父类类型
    print(isinstance(c_obj, Cat))  # >>>True 子类属于自己的类型
    print(isinstance(c_obj, Base))  # >>>True 子类也属于父类类型
    
    # 函数的参数在Python中是没有类型限制的
    # 其他如java c 等语言,定于参数的时候是先强制声明类型的
    # 假设我们只能传Base类型的参数--即叫多态
    def func(base_obj):
        base_obj.run()
    
    
    # 调用函数只能传Base类型的数据 就是其他语言所说的多态
    # 传入同一个Base类型的不同的子类,实现了不同的功能
    func(b_obj)
    func(c_obj)
    
    # 我们在实现一个没有继承Base类的,也实现了run方法
    class Pig:
        def run(self):
            print("_____pig___run___:好吃懒做")
    
    
    p_obj = Pig()
    print(isinstance(p_obj,Base))  # >>> Flase 不是Base类型的
    
    # 其实语言强制了数据类型,但是Python不需要
    # 传入没有继承Base类型的 Pig类型的实例,只要它自己实现了run方法
    # 传入该函数一样可以实现不同功能的run方法 --即叫伪多态(鸭子模式)
    func(p_obj)
    
    
    • 鸭子类型概念:

      • 它不需要言责的继承体系,关注的不是对象类型本身,而是它如何使用,一个对象只要"看起来像鸭子,走起路来像鸭子,那它就可以被看做是鸭子"
    • 鸭子类型的体现:

      • 静态语言:对于静态语言(java,#c)来讲上面传入的对象必须是Base类型或者它的子类(子类是Base类型),否者函数功能将无法实现run()方法----多态
      • 动态语言:对于动态语言Python来说,上面传入的并不一定是Base类型,也可以是其他类型,只要内部也实现了run()方法就可以了,这就是鸭子类型的体现
    • 多态意义:开放扩展、封闭修改原则

      • 对于一个变量,我们只需要知道他是Base类型,无需确切的知道它的子类,就可以放心的调用run()方法了(自己有执行自己的,自己没有也会执行Base里的)--调用方只管调用,不管细节
      • 当需要新增功能时,只需要一个Base的子类,实现run()方法,就可以在原来的基础上进行功能扩展,这个就是"开放封闭"原则:
        • 对扩展开发:允许新增Base子类
        • 对修改封闭:不需要修改Base类里面的run()方法

    三、数据和自省

    类私有属性,私有方法,实例私有属性,私有方法

    • "_name" 被单下滑线标识的名称,意位类中的私有产物,虽然不影响外界的调用,算是口头协约,外部别用,此属性或者方法仅为类内部用
    • "__name" 被双下滑线标识的,也是类中的私有产物、外部不能直接调用,实现了伪私有,外界通过"_类名__name" 一样可以访问
    class MyTest(object):
        __age = 18  # 类私有属性,外界不能直接调用,伪私有,调用_MyTest__age 就等于调用__age
        _Age = 18  # 口头私有,外界可以调用
    
        def __init__(self, name):
            self.__name = name
    
        @classmethod  # 类方法
        def __add(cls):
            print("add方法")
            # print(cls)
    
        def __run(self):
            # 类内部私有的可以直接使用 ,外部不能
            self.__add()
            print(self.__name)
    
    
    # 类私有属性
    # 单下滑线口头协约的可以调用
    print(MyTest._Age)
    # 双下滑不能直接被调用
    # print(MyTest.__age)   # AttributeError: type object 'MyTest' has no attribute '__age'
    # print(MyTest.__dict__)  # 查看对象属性的方法 '_MyTest__age': 18, 名字内部做的转换
    print(MyTest._MyTest__age)  # 调用转换的后的名字即可,所以叫做伪私有
    
    # 如下都一个道理
    
    # 私有类方法
    MyTest._MyTest__add()
    
    # 实例私有属性,方法
    t = MyTest("DaBai")
    print(t._MyTest__name)
    
    t._MyTest__run()
    
    

    __dict__

    • 调用__dict__属性,返回调用对象的内部属性和实现的方法,字典的形式储存
      • 类调用,返回类属性和方法的字典
      • 实例调用,返回实例相关的属性和方法
    class MyClass:
        name = "DaBai"
        age = 18
    
    
    class A(MyClass):
        name = "Bai"
    
    
    # 类
    print(MyClass.__dict__)  # 查看类属性
    print(A.__dict__)  # 继承的类 会少一些东西子类不在重复
    
    # 实例
    m = MyClass()
    m.gender = "男"  # 创建一个实例属性
    print(m.__dict__)  # 实例默认创建一个字典保存属性 {'gender': '男'}
    

    内置属性__slots__

    • 默认情况下,类的实例有一个字典用于存属性(可以让实例调用__dict__查看),这对于很少实例变量的对象会浪费空间,当创建大量实例的时候,空间消耗可能会变得尖锐
    • 可以通过在类中定义__slots__来覆盖默认的__dict__行为,__slots__声明接收一个实例变量序列,并在每个实例中保留足够保存每个变量的空间,就不会为每个实例都创建一个__dict__保存实例实行,大家共用类设定好的__slots__里的属性变量,等于把属性写死了,从而节省了空间
    # 限定实例属性,实例不创建__dict__
    class Base:
        # 指定实例所能绑定的属性
        # 实例的时候不在创建__dict__,节约内存
        # 限制实例属性
        # 类本身不受限制
        __slots__ = ["name", "ag[图片]e", "gender"]
    
        def __init__(self):
            self.name = "DaBai"  # __slots__中有的可以创建实例属性
            # self.height = "175cm"  # __slots__ 中没有的不能在创建,不然会报错
    
    
    m = Base()  # 实例
    # 类
    print(Base.__dict__)  # 类本身还有__dict__属性
    Base.geight = "185cm"  # 还能增加类属性
    # 类和实例都能取到
    print(Base.geight)
    print(m.geight)
    
    # 实例
    # 实例没有__dict__实行了,节省空间
    print(m.__dict__)  # 报错 'Base' object has no attribute '__dict__'
    # 实例也不能添加除了__slots__以为的属性名
    m.geight = "185cm"  # 报错 m.geight = "185cm"  # 报错 'Base' object attribute 'geight' is read-only
    
    
    • 总结
      • 类内部实现了__slots__,实例会去掉__dict__属性
      • 实例属性被限制死在__slots__里了,不能在添加__slots__以外的属性
      • 类属性没有被限制,可以通过给类添加属性,实例去获取类的属性

    自定义属性访问

    可以通过下面的方法来自定义类实例的属性访问的含义(访问,赋值或者删除属性)

    • object.__getattr__(self, item)
      • 找不到属性的时候触发
    • object.__getattribute__(self, item)
      • 查找属性的时候触发
    • object.__setattr__(self, key, value)
      • 设置属性的时候触发
    • object.__delattr__(self, item)
      • 在del 删除属性的时候触发
    • 详情请查看官方文档-自定义属性访问
    class Test:
        def __init__(self):
            self.age = 18
    
        # 官方文档提示:当找不到属性的时候要么抛出异常
        # 要么返回一个特定的数值
        def __getattr__(self, item):
            # 当我们访问属性的时候,属性不存在的时候触发
            print("----这个是__getattr__方法----")
            # return super().__getattribute__(item)
            return 100
    
        def __getattribute__(self, item):
            # 访问属性的时候第一时间触发
            print("----__getattribute__----")
            # 返回父类查看属性的功能方法
            return super().__getattribute__(item)
    
        def __setattr__(self, key, value):
            # print("__setattr__=", key)  # 属性名称
            # print("__setattr__=", value)  # 属性值
            # 可以重写这个设置一些干扰操作
            if key == "age":
                # 这样属性在外界对age的修改不会生效
                return super().__setattr__(key, 18)
            # 返回父类的设置属性的方法
            return super().__setattr__(key, value)
    
        def __delattr__(self, item):
            print("__delete__被触发了")
            # 我们可以控制哪个属性不能被外界删除
            print(item)
            if item == "age":
                print("不能被删除")
            else:
                return super().__delattr__(item)
    
    
    t = Test()
    # 设置属性的时候 触发__setattr__
    t.name = "DaBai"
    # 先触发查找的方法,找到了不会在去触发__getattr__方法
    print(t.name)
    # 先触发查找方法,找不到才去触发__getattr__方法
    print(t.name1)
    
    # 设置修改age属性,触发__setattr__
    t.age = 1111111
    t.name = "2222222"
    print(t.age)  # >>>在 __setattr__方法中过滤了,还是18
    print(t.name)  # 会被修改
    
    # 删除的时候触发__delattr__
    del t.name
    print(t.name)  # 属性删除了
    del t.age
    print(t.age)  # 过滤了这个属性不能在被外界删除了
    
    

    描述器

    描述器时一个具有"绑定行为"的对象属性,该对象的属性访问通过描述其协议覆盖:__set__()和__get__()和__delete__(),如果一个对象定义了这些方法中的任意一个,它就被成为描述器

    • object.__get__(self,instance,owner)
      • 获取属主类的属性(类属性访问),或者该类的一个实例的属性(实例属性访问),owner始终是主,instance是属性访问的实例,当属性通过owner访问是则为None,这个方法应该返回(计算后)的属性值,或者引发一个AttributeError异常
    • object.__set__(self,instance,value)
      • 设置属主类的实例instance的属性为一个新值value
    • object.__delete__(self,instance)
      • 删除属主类的实例instance的属性
    • 详情请访问官方文档
    class Field:
        """
         一个类中只要出现了以为下面三个方法,那么该类
         就是一个描述器类;
         这个类不会直接使用,而是定义在别的类的属性
        """
    
        def __get__(self, instance, owner):
            """
            :param self:StrField类的实例
            :param instance: 调用了描述器类的,那个类的实例 这里是Model类的实例
            :param owner:  调用了描述器类的,那个类本身,这是Model类
            :return: 返回设置的属性值
            """
            # print(owner)
            print("__get__方法触发了")
            return self.value
    
        def __set__(self, instance, value):
            """
            :param self:StrField类的实例  这里是--name
            :param instance: 调用了描述器类的,那个类的实例 这里是Model类的实例--m
            :param value:   属性值
            """
            # print(self)
            # print(instance)
            # print(value)
            print("__set__方法触发了")
            # 设置属性值,这里不用返回,返回在__get__中处理
            self.value = value
    
        def __delete__(self, instance):
            print("__delete__方法触发")
            # del self.value  # 外界del属性,触发这里实现删除
            self.value = None  # 删除的时候返回None 就可以了
    
    
    class Model:
        # 属性是描述器对象的时候
        # 会覆盖类中的查找,设置,删除的属性的方法
        # 去执行描述器类中的的方法
        name = Field()
        age = Field
    
    # 设置描述器类型的属性值
    m = Model()
    m.name = "DaBai"  # 设置属性,触发描述器类的set、get方法
    print(m.name)
    
    # 删除
    del m.name
    print(m.name) # 触发描述器的__delete__方法,重置None
    

    ORM模型

    • O(object): 类和对象
    • R(Relation): 关系,关系数据库中的表格
    • M(Mapping): 映射
    • ORM框架的功能
      • 建立模型类和表直接的对应关系,允许我们通过对象的方式来操作数据类
      • 根据设计的模型类生成数据库中的表格
      • 通过方便的配置就可以进行数据可的切换
    • 数据库中的类型字段
      • mysql常用的数据类型
        • 整数:int,bit
        • 小数:decimal(表示浮点数,如decimal(5,2)表示共存5位数,小数占2位
        • 字符串:varcahar(可变长度),char(不可变长度)
        • 日期时间:date,time,detetime
        • 枚举类型: enum
      • ORM模型中的对应的的字段(以django的ORM模型中选取的几个字段)
        • BooleanField :布尔类型
        • CharField(max_length:最大长度):字符串
        • IntegerField :整数
    • 模型案例
    • 描述器实现ORM模型中的字段类型
      • 字符串类型字段
      • int类型字段
      • 布尔字段
      • 可以用描述器简单实现ORM模型字段,但是ORM模型并不是这么实现的,ORM模型是利用元类实现的
    # str 字段
    class CharField:
        """
        设置一个str属性值,别的类在引用这个描述器
        给属性赋值的时候限定了属性类型为str
        """
    
        # 传入字符串长度
        def __init__(self, max_length=20):
            self.max_length = max_length
    
        def __get__(self, instance, owner):
            return self.value
    
        def __set__(self, instance, value):
            if isinstance(value, str):
                if len(value) <= self.max_length:
                    self.value = value
                else:
                    raise ValueError("str Length should not exceed {}".format(self.max_length))
            else:
                raise TypeError("Must be a string type not{}".format(type(value)))
    
        def __delete__(self, instance):
            self.value = None
    
    
    # int字段
    class IntField:
        """
        设置一个Int属性值,别的类在引用这个描述器
        给属性赋值的时候限定了属性类型为int
        """
    
        # 传入字符串长度
        def __init__(self, max_value=40):
            self.max_value = max_value
    
        def __get__(self, instance, owner):
            return self.value
    
        def __set__(self, instance, value):
            if isinstance(value, int):
                if value <= self.max_value:
                    self.value = value
                else:
                    raise ValueError("The value should not be greater than  {}".format(self.max_value))
            else:
                raise TypeError("Must be a int type not{}".format(type(value)))
    
        def __delete__(self, instance):
            self.value = None
    
    
    # 布尔类型
    class BooleanField:
        """
        设置一个Bool属性值,别的类在引用这个描述器
        给属性赋值的时候限定了属性类型为Bool
        """
    
        def __get__(self, instance, owner):
            return self.value
    
        def __set__(self, instance, value):
            if isinstance(value, bool):
                self.value = value
            else:
                raise TypeError("%s  Not Boolean " % type(value))
    
        def __delete__(self, instance):
            self.value = None
    
    
    class Model:
        # 设置描述其类的属性 字符串类型
        name = CharField(max_length=20)
        paw = CharField(max_length=40)
        age = IntField(30)
        status = BooleanField()
    
    
    m = Model()
    # 赋值字符串类型
    m.name = "DaBai"
    # m.paw = "sfsdfsfsdfasfsadfsdafsdsfasffsfasfdsafsdfsafs"  # 超长为空
    print(m.name)  # 可以设置字符串类型
    # m.name = 123  # 设置非字符串类型报错
    
    # 赋值int
    m.age = 18
    # m.age = 60 # 超大报错
    # m.age = "111"  # 类型报错
    
    # 布尔类型
    m.status = False
    # m.status = 111  # 类型报错
    
    

    四、元类

    说明

    • 元类比99%的用户所忧虑的东西具有更深的魔法
    • 如果你犹豫考虑是否需要它们,那么你根本不需要使用元类
    • 实际需要他们的人确信他们的需要,并且不需要进行任何解释

    旧式类VS新式类

    • 旧式lei
      • 对于就是类,类(class)和类型(type)并不完全相同,一个旧式类的实例总是继承一个名为instance的内置类型
      • 如果object是旧式类的实例,那么object.class就表示该类,但是type(object)始终是instance类型
    • 新式类
      • 新式类统一了类(class)和类型(type)的概念,如果obj是新式类的实例,obj.class和type(obj)相同
    • 注意
      • 在Python2中,默认所有的类都是旧式类,在Python2.2之前,根本不支持新式类,从Python2.2开始,可以创建新式类,但是必须明确声明它是新式类
    • 总结
      • 旧式类继承intance: python2 默认继承intance
      • 新式类继承object : python3中默认全部继承顶级父类object,没有旧式类的说法了,全部是新式类

    类(class)和类型(type)

    • 在python中,一切都是对象,类也是对象,所有一个类(class)必须有一个类型(type)
    • 实例的类型
      • 类的类型
    • 类的类型
      • 元类(type)
    class Test:
        pass
    
    
    print(type(Test))  # 类的类型 >>> <class 'type'>
    print(type(Test()))  # 实例的类型 >>> <class '__main__.Test'>
    
    • 元类(type)

      • 功能一 :查看类型属性
      • 功能二 :创建类,所有类的创建都是依赖于元类创建的
      • 元类也是继承于顶级父类,靠父类的方法创建对象
      • object类也是靠元类创建出来的
      • 他们是两个平行线,和先有鸡还是先有蛋一个道理
    • 利用元类创建类

      • type(name,bases,dict)
      • name:指定类名称
      • bases:指定继承的基类元组
      • dict:指定包含类主体定义的类属性和方法
    # 类中的方法
    def func():
        print("test")
    
    
    # 通过元类创建对象
    # 三个参数 name : 创建的类的类型名字,bases:继承的类,必须是一个元组,dict类内的属性和方法
    Test = type("Test", (object,), {"name": "Dabai", "test": func})
    
    print(Test)  # <class '__main__.Test111'>
    print(Test.name)  # 打印属性
    Test.test()  # 调用类方法
    
    • 自定义元类
      • 元类就是创建类这种对象的东西,type是Python中唯一的一个内置元类
      • 自定义元类必须继承于type,否者无法创建对象
        • 类中创建对象是调用的new方法
        • 需要重写new方法
      • 使用自定义元类创建类的时候,必须在创建类的指定用哪里元类创建类,默认是type,用metaclass参数指定
    # type 创建类需要三个参数 name,bases,dict
    # 简单做一点点应用处理
    class MyMetaClass(type):
        # 将类的属性名变成大写,操作attr_dict即可
    
        def __new__(cls, name, bases, attr_dict, *args, **kwargs):
            print("最基础的自定义元类")
            # 遍历属性名成
            for k, v in list(attr_dict.items()):
                attr_dict.pop(k)  # 删除原来的k
                attr_dict[k.upper()] = v  # 名称大写重新赋值
            # 默认给类设置一个__slots__属性
            attr_dict["__slots__"] = ["name","age","gender"]
            return super().__new__(cls, name, bases, attr_dict)
    
    
    # metaclass指定创建类的元类
    class Test(metaclass=MyMetaClass):
        name = "DaBai"
        age = 99
        gender = "男"
    
    
    print(type(Test))  # >>><class '__main__.MyMetaClass'>
    print(Test.__dict__)  # 属性名称变成大写
    # print(Test.name)  # 找不到了 因为做了大写处理
    print(Test.NAME)
    # 通过自定义的元类创建的类自动绑定了__slots__属性
    # 那这种类的实例都默认去掉了__dict__属性
    # print(Test().__dict__)  # 报错 没有__dict__实行了
    
    # 元类支持继承
    class MyTest(Test):
        pass
    
    
    # 子类的类型也是父类所定义的MyMetaClass元类类型
    print(type(MyTest))  # <class '__main__.MyMetaClass'>
    
    

    ORM模型实现思路

    在我们Python的Django中已经Flask.SQLAlchmey,中操作数据是会用到ORM模型,通常元类用来创建API是非常好的选择,使用元类的编写很复杂但是使用者可以非常简洁的调用API即可

    • 实现技术点分析
      • 1、类对象表,创建类的时候需要自动生成对应的数据表
      • 实例对象对应的一条数据,创建一个对象,需要在数据表中添加一条数据
      • 属性对象字段,修改对象属性的同时需要修改数据库中对应的字段
    # 创建父类用于,统一字段的类型
    # 用于元类创建类的时候判断属性类型
    class BaseField:
        pass
    
    
    # 定义的好的字段类型
    # str 字段
    class CharField(BaseField):
        """
        设置一个str属性值,别的类在引用这个描述器
        给属性赋值的时候限定了属性类型为str
        """
    
        # 传入字符串长度
        def __init__(self, max_length=20):
            self.max_length = max_length
    
        def __get__(self, instance, owner):
            return self.value
    
        def __set__(self, instance, value):
            if isinstance(value, str):
                if len(value) <= self.max_length:
                    self.value = value
                else:
                    raise ValueError("str Length should not exceed {}".format(self.max_length))
            else:
                raise TypeError("Must be a string type not{}".format(type(value)))
    
        def __delete__(self, instance):
            self.value = None
    
    
    # int字段
    class IntField(BaseField):
        """
        设置一个Int属性值,别的类在引用这个描述器
        给属性赋值的时候限定了属性类型为int
        """
    
        # 传入字符串长度
        def __init__(self, max_value=40):
            self.max_value = max_value
    
        def __get__(self, instance, owner):
            return self.value
    
        def __set__(self, instance, value):
            if isinstance(value, int):
                if value <= self.max_value:
                    self.value = value
                else:
                    raise ValueError("The value should not be greater than  {}".format(self.max_value))
            else:
                raise TypeError("Must be a int type not{}".format(type(value)))
    
        def __delete__(self, instance):
            self.value = None
    
    
    # 布尔类型
    class BooleanField(BaseField):
        """
        设置一个Bool属性值,别的类在引用这个描述器
        给属性赋值的时候限定了属性类型为Bool
        """
    
        def __get__(self, instance, owner):
            return self.value
    
        def __set__(self, instance, value):
            if isinstance(value, bool):
                self.value = value
            else:
                raise TypeError("%s  Not Boolean " % type(value))
    
        def __delete__(self, instance):
            self.value = None
    
    
    # 第一步创建元类
    class FieldMateClass(type):
        """模型类的元类"""
    
        def __new__(cls, name, bases, attrs, *args, **kwargs):
            # 模型类的父类不需要创建表名和字段关系,只有模型类才需要
            # 过滤一下
            if name == "BaseModel":
                return super().__new__(cls, name, bases, attrs)
            else:
                # 类名对应数据类表名,通常为转成小写
                table_name = name.lower()
                # 生成字段和表的映射关系-属性都保存在attrs中
                # 定义一个字典储存,建立字段映射关系
                fields = {}
                for k, v in list(attrs.items()):  # 遍历所有的属性
                    if isinstance(v, BaseField):  # 判断所有的属性是不是字段类型的
                        fields[k] = v  # 是字段类型的添加字段对应关系字典中
                # print(fields)
                # 属性字典中添加标名和字段映射关系
                attrs["table_name"] = table_name
                attrs["fields"] = fields
                return super().__new__(cls, name, bases, attrs)
    
    
    # 第二步 定义一个模型类的基类
    # 重写init方法,方便实例的时候赋值
    # 好处一:不然模型类每次实例属性都要一个个添加
    # 好处二:每个模型类都能继承这个基类,不用每个模型类都写一个init,或者生成sql的方法
    class BaseModel(metaclass=FieldMateClass):
        def __init__(self, **kwargs):
            for key, value in kwargs.items():
                setattr(self, key, value)  # 设置属性的内置方法,
    
        # 保存一条数据,生成一条对应的sql语句
        def save_data(self):
            # 获取表名
            # 获取字段对应关系字典
            table_name = self.table_name
            fields = self.fields
            print(fields)  # # 这里面存储的是字段 和字段对象的关系
            field_dict = {}  # 创建一个字典存储字段和字段值
            # 遍历字段映射关系遍历key,获取字段值加入到field_dict字段中
            for field in self.fields:
                try:  # 处理非必填,  没有的字段不收集
                    field_dict[field] = getattr(self, field)  # 内置方法,通过key 获取值
                except AttributeError:
                    pass
            # 生成sql
            print(field_dict)
            sql = "INSET INTO {} {} VALUE{}".format(table_name, tuple(field_dict.keys()), tuple(field_dict.values()))
            return sql
    
        def select_data(self):
            # 查询数据
            pass
    
    
    # 第三步,先自己定义模型类,类对应数据库中的表
    # 继承模型类基类-实现元类继承和init初始化操作
    class User(BaseModel):
        """用户模型类"""
        # 模型类对应的字段--属性
        user_name = CharField()
        pwd = CharField()
        age = IntField()
        status = BooleanField()
    
    
    class Oder(BaseModel):
        id = IntField()
        money = CharField()
    
    
    # print(User.table_name)  # 类属性中就能拿到表名
    # print(User.fields)  # 拿到字段的映射关系
    
    # 一个模型类对象就对应一条数据
    # 实例的时候一次性传入实例属性
    xiao_ming = User(user_name="小明", pwd="123456", age=17, status=False)
    oder_1 = Oder(id=1, money="1.22")
    
    print(xiao_ming.user_name)
    print(oder_1.id)
    
    print(xiao_ming.save_data())
    
    
  • 相关阅读:
    Linux下的搜索查找命令的详解(locate)
    Linux下的搜索查找命令的详解(whereis)
    Linux下的搜索查找命令的详解(which)
    Linux下的awk文本分析命令实例(二)
    Linux下的awk文本分析命令实例(一)
    Linux下的awk文本分析命令详解
    Linux下的at定时执行任务命令详解
    六. 元素修改与空值处理
    七. 高级方法
    八. Pandas的轴
  • 原文地址:https://www.cnblogs.com/jiangmingbai/p/10909449.html
Copyright © 2020-2023  润新知