目录
- 双下划线开头且结尾的方法我们称之为魔术方法,python底层帮我是实现了很多的魔术方法,在特定场景下自动触发
- 点击跳转Python官网文档
- 点击跳转大佬博客
一、魔术方法(魔术方法特殊方法)
__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 :整数
- mysql常用的数据类型
- 模型案例
- 描述器实现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())