面向对象的魔法方法、元类

反射补充

反射实战案例

  1. 加载配置文件纯大写的配置
import settings
new_dict = {}
"""
settings的内容为如下:
NAME = 'xunfei'
GENDER ='male'
age = 18
"""
for i in dir(settings):  # dir() 获取括号中对象可以调用的名字
    if i.isupper():
        val = getattr(settings,i)
        new_dict[i] = val
print(new_dict)  # {'GENDER': 'male', 'NAME': 'xunfei'}
  1. 模拟操作系统cmd终端执行用户命令
class WinCmd(object):
    def dir(self):
        print('dir获取当前目录下所有的文件名')
    def ls(self):
        print('ls显示当前目录的所有文件夹')
    def ipconfig(self):
        print('ipconfig获取当前计算机的网卡信息')
obj = WinCmd()
while True:
    cmd_name = input('请输入命令>>>:').strip()
    if hasattr(obj,cmd_name):
        res = getattr(obj,cmd_name)
        res()
    else:
        print(f'{cmd_name}不是内部或外部指令,也不是可运行的程序或批处理文件')

面向对象魔法方法

魔法方法其实就是类中定义的双下方法,之所以会叫魔法方法,原因是这些方法都是到达某个条件自动触发,无需调用

eg: __init__方法再给对象设置独有数据的时候自动触发(实例化)

魔法方法详述

下列讲解的魔法方法都必须明确的知道的触发条件

class MyClass(object):
    def __init__(self,name):
        """
            实例化对象的时候自动触发
        """
        print('__init__')  # __init__

    def __str__(self):
        """
            对象被执行打印操作的时候会自动触发
            该方法必须返回一个字符串
            返回什么字符串打印对象之后就展示什么字符串
        """
        return '__str__'  # __str__

    def __call__(self, *args, **kwargs):
        """对象加括号调用,自动触发该方法"""
        print('__call__方法')
        print(args,kwargs)

    def __getattr__(self, item):
        """
            当对象获取一个不存在的属性名,自动触发
            该方法返回什么,对象获取不存在的属性名就会得到什么
            行参item就是对象想要获取的不存在的属性名
        """
        return f'你想要获取的属性不存在:{item}'

    def __setattr__(self, key, value):
        """
            对象操作属性值的时候自动触发>>>:对象.属性名 = 属性值

        """
        print('__setattr__')
        super().__setattr__(key,value)

    def __del__(self):
        """
            对象在被删除(主动、被动)的时候自动触发(垃圾回收)
        """
        pass
    def __getattribute__(self, item):
        """
            对象获取属性的时候自动触发,无论这个属性存不存在
            当类中既有__getattr__又有__getattribute__的时候只会走后者
        """
        print('__getattribute__')
    def __enter__(self):
        """
            对象在被with语法执行的时候自动触发,该方法返回什么,as后面的变量名就能得到什么
        """
        print("__enter__")
    def __exit__(self, exc_type, exc_val, exc_tb):
        """
            对象被with语法执行并运行完with子代码之后,自动触发
        """
        print('__exit__')
obj = MyClass('jason')
print(obj)
obj()
print(obj.age)
obj.age = 12
obj.age
with obj as f:
    pass

魔法方法笔试题

"""
将下列代码补全,使其不报错
class Context:
with Context() as f:
    f.do_something()
"""
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 f:
    f.do_something()

元类

元类简介

基础阶段我们使用type来查找数据的数据类型,但是学了面向对象之后,发现查看的不是数据类型,而是数据所属的类

s1 = '1234'
l2 = [1,2,3,4]
d = {'name':'jason','pwd':123}
print(type(s1))  # <class 'str'>
print(type(l2))  # <class 'list'>
print(type(d))  # <class 'dict'>

我们定义的数据类型,其实本质还是通过各个类产生了对象

class str:
    pass
h = 'hello'    str('hello')

我们也可以理解为type用于查看当前对象的类是谁

class MyClass(object):
    pass
obj = MyClass()
print(type(obj))  # 查看当前对象所属的类 :<class '__main__.MyClass'>
print(type(MyClass))  # 查看当前类所属的类:<class 'type'>

产生类的两种方式

1.class 关键字
    class MyClass:
	pass

2.利用元类type
    type(类名,类的父类,类的名称空间)

学习元类其实就是掌握了类的产生过程,我们就可以在类的产生过程中高度定制化类的行为
eg:类名必须授字母大写


class MyClass(type):
  def __init__(cls,what, bases=None,dict=None):
      if not what.istitle():
          raise Exception('类名首字母必须大写!')
      super().__init__(what, bases=None, dict=None)

class a(metaclass=MyClass):
    pass

启动如上代码,就会报如下异常,做到了类产生的定制化
image-20201212190410230

元类的基本使用

class MyMetaClass(type):
    pass
""" 只有继承了type的类才可以称之为是元类"""
class MyClass(metaclass=MyMetaClass):
    pass
"""
    如果想要切换产生类的元类不能使用继承,必须使用关键字metaclass声明
"""
"""
思考:
    类中的__init__用于实例化对象
    元类中__init__用于实例化类
"""
"""
    如果想要切换产生类的元类不能使用继承,必须使用关键字metaclass声明
"""
class MyClass(metaclass=MyMetaClass):
    pass

元类进阶

元类不单单可以控制类的产生过程,其实也可以控制对象的!

  1. 对象加括号执行产生该对象类里面的双下call
  2. 类加括号执行产生该类的元类里面的双下call
# 需求: 实例化对象,所有的参数都必须采用关键字参数的形式
class MyMetaClass(type):
    def __call__(self, *args, **kwargs):
        if args:
            raise Exception('只能关键字传参')
        super().__call__(*args, **kwargs)

class MyClass(metaclass=MyMetaClass):
    def __init__(self,name,age):
        self.name = name
        self.age = age

obj = MyClass('jason','18')
# 关键字参数的话就正常执行了,位置参数报错
image-20201212190410230

总结:

  1. 如果我们想高度定制对象的产生过程,可以操作元类的__call__
  2. 如果我们想高度定制类的产生过程,可以操作元类的__init__

双下new方法

类产生对象的步骤
1.产生一个空对象
2.自动触发__init__方法实例化对象
3.返回实例化好的对象

__new__方法专门用于产生空对象                 骨架
__init__方法专门用于给对象添加属性             血肉

作业

"""
1.自定义字典并且让字典具备
	d.key = value  修改键值对
	d.key = value  添加键值对
"""
d = {
    'username':'jason',
    'password':123
}

class MyDict(dict):
    
    def __getattr__(self, item):
        return self.get(item)

    def __setattr__(self, key, value):
        self[key] = value

obj = MyDict(d)
print(obj.username)  # jason

# 修改键值对
obj.username = 'xunfei'
print(obj)  # {'username': 'xunfei', 'password': 123}
# 添加键值对
obj.hobby ='read'
print(obj)  # {'username': 'xunfei', 'password': 123, 'hobby': 'read'}

posted @ 2022-07-29 19:34  荀飞  阅读(28)  评论(0编辑  收藏  举报