面向对象的元类

  • 反射实际案例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'里面,需要间接调用