常用魔法方法和元类

发布时间 2024-01-11 16:10:18作者: Formerly0^0

常用魔法方法和元类

1.常用魔法方法

__init__	:初始化类时触发
__del__		:删除类时触发
__new__		:构造类时触发
__str__		:str函数或者print函数触发
__repr__	:repr或者交互式解释器触发
__doc__		:打印类内的注释内容
__enter__	:打开文档触发
__exit__	:关闭文档触发
__getattr__ : 访问不存在的属性时调用
__setattr__ :设置实例对象的一个新的属性时调用
__delattr__ :删除一个实例对象的属性时调用
__setitem__	:列表添加值
__getitem__ :将对象当作list使用 
__delitem__	:列表删除值
__call__	:对象后面加括号,触发执行
# 面向中的魔法方法 就是 双下换线 __init__ 这种方法
# 魔法方法:因为这是在类中会自动触发的方法
class Person(object):
    # 1. __init__:初始化对象方法,在初始化对象的属性的时候会触发
    def __init__(self, name):
        self.name = name

    # 2. __str__ : 在打印对象的时候会触发,,返回值必须是字符串
    # 用处就是用来定制打印对象时候显示的对象的信息的
    def __str__(self):
        print(f'打印的时候触发')
        return f'当前是 {self.name} 的信息!'

    # 3. __getattr__ : 获取不存在的属性会触发,他不会报错
    def __getattr__(self, item):
    # item 就是我们在 对象.属性 的哪个属性名字
       	print(f'触发了')
    	  print(item)
    		return f'当前键 {item} 不存在 !'

    # 4. __getattribute__ : 如果类里面定义了这个方法,在获取属性,不存在的属性时会先触发 __getattribute__,再触发__getattr__
    # 不仅可以对不存在的键进行处理,还可以对存在的键进行处理
  	def __getattribute__(self, item):
        return getattr(self, item)
        print(item,type(item))
        if hasattr(self, item):
            return self.item
        else:
            raise ValueError(f"当前键 {item} 不存在!")
    # 5. __delattr__ : 在删除对象中的某个属性的时候会触发,item就是想要删除的属性名
    # 在这里我们可以对想要删除的属性名继续过滤,将敏感数据保留
    def __delattr__(self, item):
        # 这里你可以对敏感数据进行拦截 HOST PORT
        print(item)
        print(f'删除触发')
        if item == 'HOST':
            raise Exception("不能删除端口号!")

    # 6. __call__ 方法 : 当想把对象当函数代用的时候可以给我们的类加一个 __call__ 方法,让他能被调用
    def __call__(self, *args, **kwargs):
        print(args)
        print(kwargs)
        print(f'对象() 会触发我! ')
        ...

    # 7. __new__
    # 8.基于父类的方法重写父类的方法 :派生
    # 设置键会触发
    def __setattr__(self, key, value):
        ...

    # 删除键会触发
    def __delattr__(self, item):
        ...

    # __getattr__ : 没有才会触发
    def __getattr__(self, item):
        ...


p = Person(name='serein')
p(name='formerly')

2. 练习

# 基于所学知识实现字典可以 .属性取值和放值

class MyDict(dict):
    # 我不写:回加载父类中的方法
    # def __init__(self, seq=None, **kwargs):
    #     super().__init__(self, seq=seq, **kwargs)

    # 获取值
    def __getattr__(self, item):
        # item : 需要取的键
        # self.get(item): 根据键取值
        # print(item)
        return self.get(item)

    # 设置值
    def __setattr__(self, key, value):
        self[key] = value


data_dict = MyDict({'name': 'serein'})
print(data_dict, type(data_dict))
# print(data_dict)
print(data_dict.name)
# 这里放进去的是 数据属性而不是原来的 k:v 键值对了
data_dict.age = 18
print(data_dict.age)

# user_data = {"name":"serein"}
# # print(user_data.name)
# user_data.age = 18

3. 元类

  • 元类其实就是创建出类的类 -- type
class Student(object):
    ...


s = Student()
# 查看对象的类型
print(type(s))  # <class '__main__.Student'>
print(type(Student))  # <class 'type'>
print(type(object))  # <class 'type'>

# 创建类的两种方式
# 方式一:直接用关键字声明
class Student(object):
    school = "清华大学"

    def read(self):
        ...


print(type(Student))  # <class 'type'>
print(Student.__dict__) # {'__module__': '__main__', 'school': '清华大学', 'read': <function Student.read at 0x100eaf370>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
print(Student.__bases__)  # (<class 'object'>,)


# 方式二:直接用 type 这个类创建一个新的类
Student = type('Student', (object,), {'school': "清华大学"})
print(type(Student))  # <class 'type'>
print(Student.__dict__) #{'school': '清华大学', '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
print(Student.__bases__)  # (<class 'object'>,)

# 总结 type 语法 :
# 类名 = type(类名,(父类1,父类2...),{类里面的数据属性})

# 【四】如何使用元类?
# 【1】基本语法
# 最基础最开始的元类
class MyMeta(type):
    def __init__(cls, class_name, class_bases, class_dict):
        print(f'这是 MyMeta  中的init方法被触发了')
        if not class_name.istitle():
            raise f'{class_name}必须开头字母大写'
        # print(class_name)  # MyClass
        # print(class_bases)  # (<class 'object'>,)
        # print(class_dict)  # {'__module__': '__main__', '__qualname__': 'MyClass', '__init__': <function MyClass.__init__ at 0x000002A251540360>}
        super().__init__(class_name, class_bases, class_dict)

    def __call__(self, *args, **kwargs):
        print(f'这是 MyMeta  中的 __call__ 方法被触发了')
        print(f'这是 MyMeta  中的 {args}')
        print(f'这是 MyMeta  中的 {kwargs}')
        obj = super().__call__(*args, **kwargs)
        print(f'这是 MyMeta  中的 {obj}')
        return obj


# 创建一个新的类:继承元类区别于继承父类
# class MyClass(类名)
class Myclass(object, metaclass=MyMeta):
    def __init__(self, name):
        print(f'这是 MyClass  中的init方法被触发了')
        self.name = name

    def __call__(self, *args, **kwargs):
        print(f'这是 MyClass  中的 __call__ 方法被触发了')
        print(args)
        print(kwargs)


# 对象() ---> 触发了实例化得到自己的这个类里面的 __call__ 方法
p = Myclass(name='serein')
print(p)
# 【1】首先进入到 type __init__ 里面 ---> 将一个空的类创建出来 (类的名字、类的父类,类的名称空间)
# 【2】类() 触发了 type 中的 __call__ 方法


# 【需求】:定义一个类的时候,我这个类的名字只能大写字母开头,小写不行
# 【1】创建一个元类
# 【2】重写 type的 __init__ 方法
# 【3】类的名字、类的父类、类的名称空间
# 【4】对类的名字进行校验  调用父类 type 里面的 __init__ 方法 将需要的参数全都传进去
# 【5】创建一个自己的类 , metaclass=元类名字

print('-----')
class_name = 'Myclass'
if not class_name.istitle():
    raise Exception("类的名字必须大写!")
else:
    class_name = type(class_name, (), {})
print(class_name)

元类控制类初始化只能按关键字传参数

class MyMeta(type):
    def __init__(cls, class_name, class_bases, class_dict):
        print(f'这是 MyMeta  中的init方法被触发了')
        if not class_name.istitle():
            raise f'{class_name}必须开头字母大写'
        # print(class_name)  # MyClass
        # print(class_bases)  # (<class 'object'>,)
        # print(class_dict)  # {'__module__': '__main__', '__qualname__': 'MyClass', '__init__': <function MyClass.__init__ at 0x000002A251540360>}
        super().__init__(class_name, class_bases, class_dict)

    def __call__(self, *args, **kwargs):
        print(f'这是 MyMeta  中的 __call__ 方法被触发了')
        print(f'这是 MyMeta  中的 {args}')
        if args:
            raise Exception(f'参数只能按照关键字传参')
        print(f'这是 MyMeta  中的 {kwargs}')
        obj = super().__call__(*args, **kwargs)
        print(f'这是 MyMeta  中的 {obj}')
        return obj


# 创建一个新的类:继承元类区别于继承父类
# class MyClass(类名)
class Myclass(object, metaclass=MyMeta):
    def __init__(self, name):
        print(f'这是 MyClass  中的init方法被触发了')
        self.name = name

    def __call__(self, *args, **kwargs):
        print(f'这是 MyClass  中的 __call__ 方法被触发了')
        print(args)
        print(kwargs)


# 对象() ---> 触发了实例化得到自己的这个类里面的 __call__ 方法
p = Myclass(name='serein')
p1 = Myclass('formerly')
print(p)
print(p1)
# __new__ 方法 :如果在元类里面就是产生空对象
# __init__ 方法 :往里面添加血和肉

class MyMeta(type):
    # 填充血肉
    def __init__(cls, class_name, class_bases, class_dict):
        print(f'MyType 中的 init 方法 我被触发了')
        super().__init__(class_name, class_bases, class_dict)

    # 生成骨架
    def __new__(cls, *args, **kwargs):
        print(f'MyType 中的 new 方法 我被触发了')
        print(args)
        print(kwargs)
        obj = type.__new__(cls, *args, **kwargs)
        return obj

    def __call__(self, *args, **kwargs):
        print(f'MyType 中的 call 方法 我被触发了')
        print(f'args: {args}, kwargs: {kwargs}')
        # 创建对象并初始化参数
        obj = super().__call__(*args, **kwargs)
        # print(obj)
        # 将产生的对象返回出去
        return obj


class MyClass(object, metaclass=MyMeta):
    def __init__(self, name):
        print(f' MyClass 中的 init 方法 我被触发了')
        self.name = name

    def __call__(self, *args, **kwargs):
        print(f' MyClass 中的 call 方法 我被触发了')
        return 'call 方法返回的值'

    def __new__(cls, *args, **kwargs):
        print(f' MyClass 中的 new 方法 我被触发了')
        print(args, kwargs)
        # return super().__new__(cls)
        obj = object.__new__(cls)
        # obj.__init__(*args, **kwargs)
        return obj


p = MyClass(name='serein')
p()