isinstance和issubclass、元类、反射
一:isinstance和issubclass
判断某个对象是不是某个类的实例
isinstance():判断某个对象是不是某个类的实例
issubclass():判断是不是子类(返回布尔值),(还可以判断是不是父类的父类,所有类都是object的子类或子子类)
class Person: pass class Student(Person): pass stu=Student() #判断两个对象是不是同一个类型 print(type(1)==type(2)) #判断stu对象是不是Student类的实例 print(isinstance(stu,Student)) #判断是不是子类 isinstance() #判断一个类是不是另一个类子类(所有类都是object的子类或子子类) print(issubclass(Student,Person))
例如:isinstance(obj,cls)检查是否obj是否是类 cls 的对象
issubclass(sub, super)检查sub类是否是 super 类的派生类
二:反射
反射:指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。简单来说,就是一种修正错误的能力。
python面向对象的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)。
hasattr:是否存在某个属性的值
判断object中有没有一个name字符串对应的方法或属性
getattr:获取某个属性的值
def getattr(object, name, default=None): # known special case of getattr """ getattr(object, name[, default]) -> value Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn't exist; without it, an exception is raised in that case. """ pass
setattr:设置某个属性的值
def setattr(x, y, v): # real signature unknown; restored from __doc__ """ Sets the named attribute on the given object to the specified value. setattr(x, 'y', v) is equivalent to ``x.y = v'' """ pass
delattr:删除某个属性
def delattr(x, y): # real signature unknown; restored from __doc__ """ Deletes the named attribute from the given object. delattr(x, 'y') is equivalent to ``del x.y'' """ pass
这几个方法有一个共同点,都是通过字符串来操作属性,你可以理解为通过字符串来操作属性,就叫做反省。(当你获取到一个对象,但是并不清楚该对象的内部细节时,就需要使用反射)
class Student: def __init__(self,name,sex,age): self.name = name self.age = age self.sex = sex def study(self): print("学生正在学习...") stu = Student("Grace","Miss",18) # # print(stu.name) # # stu.name = "Haru" # # del stu.name # 当你获取到一个对象 但是并不清楚搞对象的内部细节时 就需要使用反射了 def test(obj): if hasattr(obj,"name"): print(getattr(obj,"name","没有name属性")) test(stu) setattr(stu,"school","beijing") delattr(stu,"school") print(getattr(stu,"school","没有学校属性"))
class BlackMedium: feature='Ugly' def __init__(self,name,addr): self.name=name self.addr=addr def sell_house(self): print('%s 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼' %self.name) def rent_house(self): print('%s 黑中介租房子啦,傻逼才租呢' %self.name) b1=BlackMedium('万成置地','回龙观天露园') #检测是否含有某属性 print(hasattr(b1,'name')) print(hasattr(b1,'sell_house')) #获取属性 n=getattr(b1,'name') print(n) func=getattr(b1,'rent_house') func() # getattr(b1,'aaaaaaaa') #报错 print(getattr(b1,'aaaaaaaa','不存在啊')) #设置属性 setattr(b1,'sb',True) setattr(b1,'show_name',lambda self:self.name+'sb') print(b1.__dict__) print(b1.show_name(b1)) #删除属性 delattr(b1,'addr') delattr(b1,'show_name') delattr(b1,'show_name111')#不存在,则报错 print(b1.__dict__) 四个方法的使用演示
反射的好处:
1.实现可插拔机制(即可以事先定义好接口,接口只有在被完全定义好之后才会真正执行,这就实现了即插即用,这其实是一种后期绑定,就是你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能)
2.动态导入模块(基于反射当前模块成员)
class Student: def study(self): print("学习中....") stu = Student() res = getattr(stu,"study",None) print(res) def eat(self): print("正在吃饭...") # # 可以通过反射的方式为对象增加一个方法 但是注意 这样增加的方法就是一个普通函数 不会自动传值 setattr(stu,"eat",eat) # print(getattr(stu,"eat",None)) ========================================== 运行结果: >>>: <bound method Student.study of <__main__.Student object at 0x000000229E1CE160>> <function eat at 0x000000229E1C8AE8>
# 需要编写一个CMD工具 这个工具可以支持两个命令 dir ,tasklist class CMD: def dir(self): print("列出当前文件夹目录") def tasklist(self): print("查看任务列表") cmd = CMD() res = input("请输入指令:").strip() if hasattr(cmd,res): func = getattr(cmd,res) print(func) func() else: print("输入的指令不正确") ========================================== 运行结果: >>>: 请输入指令:dir <bound method CMD.dir of <__main__.CMD object at 0x000000229E1CE1D0>> 列出当前文件夹目录
三:__str__
但我们需要自定义打印显示内容时,就需要实现__str__方法(该方法必须返回一个字符串,返回的是什么,打印出来就是什么)
class Test: def __init__(self,name): self.name = name def __str__(self): print("str run....") return self.name t = Test("Grace") print(int(1).__str__()) # print([1,2,3,5]) # print(t) # 在将一个对象转换字符串时 本质就是在调用这个对象 __str__方法 print(str(t))
四:__del__
1.当对象被从内存中删除时会自动执行:
一种情况是:程序执行完毕之后删除
另一种情况是:手动删除这个对象,也会自动执行
2.什么时候使用?
在python中,有自动内存管理机制,所以python自己创建的数据不需要我们做任何的操作,但是有一种情况,我们使用python打开了一个不属于python管理的数据:比如打开了一个文件,这个文件一定是操作系统在打开,会占用系统内存,而python解释器是无法操作系统内存的,所以,当你的python解释器运行结束之后,文件依然处于打开状态,这时候就需要使用__del__来关闭系统资源。
简单来说,当程序运行结束之后,需要做一些清理操作,就使用__del__。(__del__也称之为析构函数,分析构造并拆除这个对象)
class TextFile: def __init__(self,filepath,mode="rt",encoding="utf-8"): self.file = open(filepath,mode=mode,encoding=encoding) def read(self): return self.file.read() def write(self,text): self.file.write(text) # 该方法其实就是一个通知性质 仅仅是告诉程序员 对象即将被删除 def __del__(self): # 在这里关闭系统的文件 妥妥的 self.file.close() tf = TextFile("2.今日内容.txt") print(tf.read()) # tf.file.close() 不需要手动关闭了 在对象删除时会自动关闭 tf.read()
五:exec方法
exec是execute的缩写:
参数一:需要一个字符串操作,表示被执行的python语句
参数二:也是一个字典,表示全局名称空间
参数三:也是一个字典,表示局部名称空间
作用是帮你解释执行python代码,并且将得到的名称存储到指定的名称空间,解释器内部也是调用它来执行代码的。
globalsdic={} localsdic={} exec(''' zdsb=1 zdwbd=2 def zdsb(): print('zdswssb') ''',globalsdic,localsdic) print(localsdic) # 调用 localsdic.get('zdsb')()
如果同时制定了全局和局部则会将字符串中包含的名称解析后存到局部中。
六:元类
元类指的是用于产生类的类,type就是元类,所有的自定义类都是通过type实例化得来的。
学习元类是为了能够控制类的创建过程以及类实例化对象的过程
1.创建模块的过程:
1.创建一个空的名称空间
2.执行内部的代码
3.将得到的名字放到名称空间中
# class也是一个对象 class Student(object): school = "北京大学!" def study(self): print("学习中...") # 使用type可以发现 类其实是type类型的实例(对象) print(type(Student)) # 我们可以自己调用type来实例化产生一个类 # myclass 包含的代码 code = """ name = "张三" age = 18 def hello(self): print("hello %s" % self.name) """ #类的名字 class_name = "MyClass" #类的的父类们 base_classes = (object,) #类的名称空间 namespace = {} exec(code,{},namespace) res = type(class_name,base_classes,namespace) print(Student) print(res.name) print(res.age) print(res.hello) # 1.类是由type实例化产生的 # 2.我们可以使用type来产生一个类 # 3.一个类是由 类名字 类的父类元祖 类的名称空间 三个部分组成 class Test(object): #Test = type("Test",(object,),{}) pass
2.控制类的创建过程
(1)创建一个元类(需要继承type)
(2)覆盖__init__方法,该方法会将新建的类对象,类名,父类们,名称空间都传进来,可以利用这些信息再做处理。
(3)对于需要被控制的类,需要指定metaclass为上面的元类
# 要控制类的创建过程 只要找到类所属的类 中的__init__即可 class MyMeta(type): # self 刚建出来的类 # 第二个 类的名字 # 第三个 类的父类们 元组 # 第四个 这个类传进来的名称空间 def __init__(self,class_name,bases,namespace): print("============================") #print(self.__dict__) # 我要控制 类的名字 必须 是大写开头 if not class_name.istitle(): print("类名 必须大写开头...... ") # 该代码是主动抛出异常 raise TypeError("类名 必须大写开头...... ") #要空类的创建 必须包含__doc__这个属性 if not self.__doc__: raise TypeError("类中必须有文档注释.....") pass class Student(metaclass=MyMeta): # Student = MyMeta("Student",(object,),{}) """ 这是文档注释 可以通过__doc__来获取 这是一个学生类 """ # 在类的__init__中可以控制该类对象的创建过程 def __init__(self,name): print("-----------------------") print(self.__dict__) self.name = name print(Student.__doc__)
3.控制类实例化对象的过程:
(1)创建一个元类(需要继承type)
(2)覆盖__init__方法,该方法会将新建的类对象,类名,父类们,名称空间都传进来,可以利用这些信息再做处理。
(3)在__call__方法中,必须要先编写模板代码
3.1创建新对象
3.2调用类的__init__方法俩初始化这个空对象
3.3返回该对象
(4)加入你需要控制的逻辑
# 自定义一个元类 元类也是一个类 但是需要继承type class MyMeta(type): # self 表示要创建对象的那个类(Person) *args是调用Person类时传入的参数 def __call__(self, *args, **kwargs): print("MyMte中的 call run'") print(self,*args,**kwargs) # 下面的三步是固定写法 一个模板 只要你需要控制对象的创建过程 就应该先把模板写出来 # 1.创建空对象 obj = object.__new__(self) # 2.调用初始化方法 self.__init__(obj,*args,**kwargs) # self.__init__(obj) # 3.得到一个完整的对象 return obj # 修改Person类的元类为MyMeta class Person(metaclass=MyMeta): def __init__(self,name,age): self.name = name self.age = age def __call__(self, *args, **kwargs): print("call run...") #调用Person这个对象时 执行的是 Person的类(type)中__call__ 方法 p = Person("张三疯",80) print(p) # 当调用对象时 会执行该对象所属类中的__call__方法 # p() print(p.name) print(p.age)
4.元类—>实例化产生——>类——>实例化产生——>对象
七:单例模式
一种设计模式(套路),一个类如果只有一个实例,那么该类称之为单例。
class MyMeta(type): obj = None def __call__(self, *args, **kwargs): if not MyMeta.obj: obj = object.__new__(self) self.__init__(obj,*args,**kwargs) MyMeta.obj = obj return MyMeta.obj #打印机类 class Printer(metaclass=MyMeta): """ 这是一个单例类 请不要直接实例化 使用get方法来获取实例 """ obj = None def __init__(self,name,brand,type): self.name = name self.brand = brand self.type = type def printing(self,text): print("正在打印 %s" % text) # 通过该方法来获取对象 可以保证只有一个对象 # 但是这还不够 因为 还是可以通过调用类产生新对象 # 就应该使用元类 来控制实例化的过程 __call__ # 在__call__ 中编写代码 保证每次调用call 都返回同一个实例 即可 @classmethod def get_printer(cls): if not cls.obj: obj = cls("ES005","爱普生","彩色打印机") cls.obj = obj print("创建了新的对象") return cls.obj # 以下三个对象 的数据完全相同 但是却 占用三分内存空间 # p1 = Printer("ES005","爱普生","彩色打印机") # p2 = Printer("ES005","爱普生","彩色打印机") # p3 = Printer("ES005","爱普生","彩色打印机") # 现在要处理问题就是 如何能够限制该类 只能实例化一个对象 p = Printer.get_printer() print(p) p = Printer.get_printer() print(p) p = Printer.get_printer() print(p) p = Printer.get_printer() print(p) p1 = Printer("ES005","爱普生","彩色打印机") p2 = Printer("ES005","爱普生","彩色打印机") print(p1) print(p2) # print(p1,p2,p3) # p1.printing("一本小说....")
通过该方法来获取对象,可以保证只有一个对象,但是这样还是可以通过调用类产生新对象,就应该使用元类来控制实例化过程__call__,在__call__中编写代码保证每次调用call都返回同一个实例即可。