面向对象的元类
- 反射实际案例3
- 面向对象的双下方法
- 元类
- 元类进阶
- 设计模式之单例模式
- 选课系统项目分析
反射实际案例
利用面向对象编写系统终端功能
class WinCmd(object):
def ls(self):
print('windows系统正在执行ls命令')
def dir(self):
print('windows系统正在执行dir命令')
def cd(self):
print('windows系统正在执行cd命令')
obj = WinCmd()
while True:
cmd = input('请输入您的指令>>>:')
if hasattr(obj, cmd):
func_name = getattr(obj, cmd)
func_name()
else:
print('cmd command not found')
上述代码是模拟的是一个windows系统的终端,括号里使用'object'是为了使兼容性更好一些,然后我们定义几个功能。然后生成一个'obj'用终端去调用windows系统里的功能,因为是外界操作,所以我们不能自己去调,只能让用户去调,所以需要使用'input'来获取用户的指令,获取指令之后我们要用'if '来判断这个指令是否存在,如果有就用'getattr'拿出来用,拿出来之后给它左边加上变量名,直接用变量名执行。没有就打印'cmd command not found'
总结就是:反射提供了一种不需要考虑代码的前提下,操作数据和功能
面向对象的双下方法
面向对象中的双下方法也有一些人称之为是魔法方法
有些双下方法不需要刻意调用 到达某个条件会自动触发
eg: init 对象实例化的时候自动触发
__str__方法:
对象被执行打印(print、前端展示)操作的时候自动触发,该方法必须返回字符串类型的数据,很多时候用来更加精准的描述对象。(类打印的时候不会触发'str'方法)
# 1.__str__方法
class MyClass(object):
def __init__(self, name):
self.name = name
def __str__(self):
print('我什么时候触发')
return '嘿嘿'
obj = MyClass('jason')
print(obj)
正常去定义类,用类产生对象,不打印就不会触发'str',使用了打印但不使用'str'方法的话,只会打印出对象所在的地址,使用了打印跟'str'方法,没有加'return',返回的数据不是字符串的话,也会报错。
__del__方法:
对象被执行(主动、被动)删除操作之后自动运行
# 2.__del__方法
class MyClass(object):
def __init__(self, name):
self.name = name
def __del__(self):
print('dir啥时候执行')
obj = MyClass('jason')
print('哈哈哈')
运行结果:
哈哈哈
del啥时候执行
先打印’哈哈哈‘是因为在程序运行完之后,Python的垃圾回收机制会把前面的都删除,这叫被动删除
class MyClass(object):
def __init__(self, name):
self.name = name
def __del__(self):
print('del啥时候执行')
obj = MyClass('jason')
del obj
print('哈哈哈')
运行结果:
del啥时候执行
哈哈哈
使用关键字'del'主动删除后,自动执行
__getattr__方法:
对象查找不存在名字的时候自动触发
class MyClass(object):
def __init__(self, name):
self.name = name
def __getattr__(self,item):
print('__gettarr__方法,item')
return '不好意思没有%s这个名字'%item
obj = MyClass('jason')
print(obj.name) # 不会触发
print(obj.age) # __getattr__方法,age None
不打印或者打印有的数据,都不会报错,因为'item'是字符串,所以可以使用'ruturn'。
__setattr__方法:
对象在执行添加属性操作的时候自动触发 >>> obj.变量名=变量值
class MyClass(object):
def __init__(self, name):
self.name = name
def __setattr__(self,key,value):
print('__setattr__方法') # __setattr__
print(key,value) # name,jason
obj = MyClass('jason')
上述代码中因为有'init',所以会自动运行,然后对象加.就会自动触发'setattr'方法,'k'代表的是对象名字,'v'代表的是对象的值,只要是对象给自己的名称空间里面添加名字都会触发该方法
__call__方法:
对象被加括号调用的时候自动触发
class MyClass(object):
def __init__(self, name):
self.name = name
def __call__(self,*args,**kwargs):
print('__call__方法',args,kwargs)
obj = MyClass('jason')
obj()
上述代码表示的是,对象不能加括号使用,不用'call'方法的话会直接报错,使用了该方法的话,对象加括号就会自动触发,括号里传入什么就会打印出什么
__enter__方法与__exit__方法:
__enter__方法:对象被执行with上下文管理语法开始自动触发,该方法返回什么as后面的变量名就会得到什么
__exit__方法:对象被执行with上下文管理语法结束之后自动触发
class MyClass(object):
def __init__(self, name):
self.name = name
def __enter__(self):
print('__enter__方法')
def __exit__(self,exc_type,exc_val,exc_tb):
print('__exit__方法')
obj = MyClass('jason')
with obj as f:
print(222)
print(111)
结果是:
__enter__方法
222
__exit__方法
111
以上就是子代码运行完之后,会自动触发‘exit'方法
__getattribute__方法:
只要对象查找名字无论名字是否存在都会执行该方法
如果类中有__getattribute__方法 那么就不会去执行__getattr__方法
class MyClass(object):
def __init__(self, name):
self.name = name
def __getattribute__(self):
print('__getattribute__方法',item)
笔试题讲解
# 需求:让字典具备句点符查找值的功能
# 先定义一个类继承字典
d = {'name':'jason','age':18}
class MyDict(dict):
def __getattr__(self,itme):
return self.get(item)
print(obj.name) # jason
print(obj,age) # 18
使用'getattr'方法,对象.名字,名字没有就会自动触发'getattr','item'是当前对象想拿的名字,没有就'return self.get(item)'就可以了,一开始对象通过句点符获取的是对象名称空间里的名字,而这里的句点符拿的是字典里面的键值对的数据
# 需求:让字典具备句点符设置k:v的功能
# 先定义一个类继承字典
d = {'name':'jason','age':18}
class MyDict(dict):
def __setattr__(self,key,value):
self[key] = value
obj.pwd = 123
print(obj) # {'name':'jason','age':18,'pwd':123}
使用'setattr'方法,不使用k跟v的话就添加到了字典名称空间中去了,'self'代表当前字典对象
# 补全下列代码 使其运行不报错
class Context:
pass
with Context() as ctx:
ctx.do_something()
看到with要么是文件就是上下文管理,这里一看就不是文件,所以需要使用'enter'和'exit',它们中间必须要返回当前对象,然后'Context'(类)加括号就成了一个对象,对象再调用一个方法就是一个绑定方法,结果如下:
class Context:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
pass
def do_something(self):
pass
with Context() as ctx:
ctx.do_something()
元类简介
元类:产生类的类
print(type(123)) # 'int'
print(type([12, 33, 44])) # 'list'
print(type({'name':'jason','pwd':123})) # 'dict'
'int','list','dict'都是属于类,而'type'就是产生这些类的类,type就是元类
产生类的两种表现形式
1.class关键字
class C1(object):
pass
print(C1) # <class '__main__.C1'>
2.type元类
type(类名,父类,类的名称空间)
res = type('C1', (), {})
print(res) # <class '__main__.C1'>
元类能够控制类的创建,所以我们可以定制类的行为,但是不推荐使用这种方法。就像掌握了物品的生产过程 就可以在过程中做任何的额外操作。
元类的基本使用
因为我们不能修改'type'里面的源码,所以我们先写一个类继承'type',这样就拥有了'type'的所有功能,底下的子类不能直接继承元类,想要修改一个类的元类,就要在继承括号里加关键字'metaclass',注意,如果没有继承'type'会直接报错,所以一个类继承元类,'type'是不能少的
# 要求类的名字必须首字母大写
class MyTypeClass(type):
def __init__(cls, cls_name, cls_bases, cls_dict):
# print(cls, cls_name, cls_bases, cls_dict)
if not cls_name.istitle():
raise Exception("类名的首字母必须大写 你个SD")
super().__init__(cls_name, cls_bases, cls_dict)
class C1(metaclass=MyTypeClass):
school = '清华大学'
class a(metaclass=MyTypeClass):
school = '清华大学'
对象在实例化的时候,由'init'来控制,那么类在实例化的时候也可以在元类的'init'里面做一些其它操作,在写的时候需要把'type'的其它三个参数也写入,'self'是类本身,'name'是类的名字,'bases'是类的父类们,没有的话可以用()代替,'dict'是名称空间,为了更好的对应,可以将'self'改为'cls',写完之后要再调一次类的'init',然后通过元类1强制性的限制一些类的行为
元类的进阶操作
对象加括号会自动执行产生该对象的类里面的__call__,并且该方法返回什么对象加括号就会得到什么
推导:类加括号会执行元类的里面的__call__该方法返回什么其实类加括号就会得到什么
class MyTypeClass(type):
def __call__(self, *args, **kwargs):
print('__call__ run')
super().__call__(*args, **kwargs)
class MyClass(metaclass=MyTypeClass):
def __init__(self, name):
print('__init__ run')
self.name = name
obj = MyClass('jason')
在执行'init'的实例化的时候会先执行'call',也就是说类里面的'init'并不是产生对象的唯一方法,
一个对象的产生不仅仅是要经历一个类里面的'init',还要先经历一个元类里的'call',如果被'call'拦截了就要重新唤起才会报错。'call'里面的'*args'是一个元祖,'**kwargs'是一个字典
# 强制规定:类在实例化产生对象的时候 对象的独有数据必须采用关键字参数
class MyTypeClass(type):
def __call__(self, *args, **kwargs):
if args:
raise Exception('必须全部采用关键字参数')
super().__call__(*args, **kwargs)
class MyClass(metaclass=MyTypeClass):
def __init__(self, name):
self.name = name
obj2 = MyClass(name='jason')
对象在类的实例化的时候使用关键字参数,'args'是用来接收类名加括号里面的数据的,那么就要'args'里面要是空的,如果不是空的就会报错,还可以用'call'控制类的行为。所以得出一个结论:如果你想高度定制类的产生过程,那么编写元类里面的__init__方法;如果你想高度定制对象的产生过程,那么编写元类里面的__call__方法
__new__方法
__new__用于产生空对象(类) 骨架
__init__用于实例化对象(类) 血肉
class Meta(type):
def __new__(cls, *args, **kwargs):
obj = type.__new__(cls,*args,**kwargs)
return obj
class Mate(type):
def __call__(self, *args, **kwargs):
obj = object.__new__(self) # 创建一个空对象
self.__init__(obj,*args,**kwargs) # 让对象去初始化
return obj
注意:并不是所有的地方都可以直接调用'new',如果是在元类的'new'里面,可以直接调用,如果是在元类的'call'里面,需要间接调用