ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

面向对象的元类

2022-04-12 02:00:48  阅读:139  来源: 互联网

标签:__ obj name self 元类 面向对象 print def


面向对象的元类

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

标签:__,obj,name,self,元类,面向对象,print,def
来源: https://www.cnblogs.com/WH101/p/16133337.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有