面向对象之魔术方法


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

__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)
  • 鸭子类型概念:
    • 它不需要言责的继承体系,关注的不是对象类型本身,而是它如何使用,一个对象只要"看起来像鸭子,走起路来像鸭子,那它就可以被看做是鸭子"
      1411986-20190527003851899-769520270.png
  • 鸭子类型的体现:
    • 静态语言:对于静态语言(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): 映射
    1411986-20190527003954652-1290308871.png
  • ORM框架的功能
    • 建立模型类和表直接的对应关系,允许我们通过对象的方式来操作数据类
    • 根据设计的模型类生成数据库中的表格
    • 通过方便的配置就可以进行数据可的切换
  • 数据库中的类型字段
    • mysql常用的数据类型
      • 整数:int,bit
      • 小数:decimal(表示浮点数,如decimal(5,2)表示共存5位数,小数占2位
      • 字符串:varcahar(可变长度),char(不可变长度)
      • 日期时间:date,time,detetime
      • 枚举类型: enum
    • ORM模型中的对应的的字段(以django的ORM模型中选取的几个字段)
      • BooleanField :布尔类型
      • CharField(max_length:最大长度):字符串
      • IntegerField :整数
  • 模型案例
    1411986-20190527004017680-1723244207.jpg
  • 描述器实现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())

转载于:https://www.cnblogs.com/jiangmingbai/p/10909449.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值