【11.0】魔法方法和元类总结

【一】常用的魔法方法

【0】什么是魔法方法

  • 在类定义阶段定义,但是会根据特定条件自动触发的方法就叫魔法方法,又称为内置方法

【1】__init__

(1)触发场景

  • 初始化对象的属性

  • 在类实例化得到具体对象时自动触发

(2)示例

# 创建一个类:类名为 Person,继承 object
class Person:
    def __init__(self, name, age):
        print(f"我在被初始化对象时触发!")
        self.name = name
        self.age = age


person = Person(name='dream', age=18)

# 我在被初始化对象时触发!

【2】__str__

(1)触发场景

  • 对象被执行打印操作的时候会自动触发
  • 并且该方法必须返回一个字符串

(2)示例

# 创建一个类:类名为 Person,继承 object
class Person:
    def __init__(self, name, age):
        print(f"我在被初始化对象时触发!")
        self.name = name
        self.age = age

    def __str__(self):
        print(f"我在打印对象时被触发!")
        return f'Person(name={self.name}, age={self.age})'


person = Person(name='dream', age=18)
# 我在被初始化对象时触发!
print(person)
# 我在打印对象时被触发!
# Person(name=dream, age=18)

【3】__del__

(1)触发场景

  • 对象被删除(被动或者主动)之后自动触发

(2)示例

# 创建一个类:类名为 Person,继承 object
class Person:
    def __init__(self, name, age):
        print(f"我在被初始化对象时触发!")
        self.name = name
        self.age = age

    def __del__(self):
        # 对象被删除是指,在Python中,对象不再被使用时,垃圾回收器会自动调用__del__方法
        print(f'我在对象被删除时触发!')
        print(f'{self.name} deleted')


person = Person(name='dream', age=18)
# 我在被初始化对象时触发!
print(person.name)
# dream
# 我在对象被删除时触发!
# dream deleted

【4】__getattr__

(1)触发场景

  • 对象在查找不存在的属性时会自动触发

(2)示例

# 创建一个类:类名为 Person,继承 object
class Person:
    def __init__(self, name, age):
        print(f"我在被初始化对象时触发!")
        self.name = name
        self.age = age

    def __getattr__(self, attr):
        # attr 为获取属性时不存在的那个属性名
        if attr == 'gender':
            # 当不存在 gender 这个属性的时候会返回 male
            return 'male'
        # 不存在其他属性时会直接报错
        raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'")


person = Person(name='dream', age=18)
# 我在被初始化对象时触发!
print(person.gender)
# male
print(person.sex)
# AttributeError: 'Person' object has no attribute 'sex'

【5】__setattr__

(1)触发场景

  • 对象在设置属性(obj.属性名=属性值)的时候会自动触发

(2)示例

# 创建一个类:类名为 Person,继承 object
class Person:
    def __init__(self, name, age):
        print(f"我在被初始化对象时触发!")
        self.name = name
        self.age = age

    def __setattr__(self, key, value):
        if key == 'age':
            # 对年龄进行限制,如果年龄不是数字类型,则抛出异常
            if not isinstance(value, int):
                raise TypeError(f"{key} must be of type int")
        # 符合规定,则将对象自己的属性值进行替换
        super().__setattr__(key, value)


person = Person(name='dream', age=18)
# 我在被初始化对象时触发!
person.age = 19
print(person.age)
# 19
person.age = '19'
# TypeError: age must be of type int

【6】__delattr__

(1)触发场景

  • 尝试删除对象的属性时自动触发。

(2)示例

# 创建一个类:类名为 Person,继承 object
class Person:
    def __init__(self, name, age):
        print(f"我在被初始化对象时触发!")
        self.name = name
        self.age = age

    def __delattr__(self, attr):
        # 限制当前删除的属性名为 age
        if attr == 'age':
            # 若是删除的age属性,则打印这句话
            print(f'Deleting {attr}')
        else:
            # 否则调用父类中的 delattr 方法
            super().__delattr__(attr)


person = Person(name='dream', age=18)
# 我在被初始化对象时触发!
del person.age
# Deleting age
del person.sex
# AttributeError: sex

【7】__setitem__

(1)触发场景

  • 当使用索引操作符(obj[key] = value)为对象的字段赋值时自动触发。

(2)示例

# 创建一个类:类名为 Person,继承 object
class Person:
    def __init__(self, name, age):
        print(f"我在被初始化对象时触发!")
        self.name = name
        self.age = age

    def __setitem__(self, key, value):
        print(f'正在设置属性 :>>>> {key} = {value}')
        # 向对象自己中初始化值
        # setattr(self, key, value)
        # 向自己的名称空间字典中增加新的属性
        self.__dict__[key] = value

person = Person(name='dream', age=18)
# 我在被初始化对象时触发!

person["sex"] = "男"
# 正在设置属性 :>>>> sex = 男

print(person.sex)
# 男

【8】__getitem__

(1)触发场景

  • 当使用索引操作符(obj[key])获取对象的字段值时自动触发。

(2)示例

# 创建一个类:类名为 Person,继承 object
class Person:
    def __init__(self, name, age):
        print(f"我在被初始化对象时触发!")
        self.name = name
        self.age = age

    def __getitem__(self, key):
        # 对象本身是没有 属性值 = obj[属性名] 这个方法的,但是通过 __getitem__ 可以让对象具有该方法
        return self.__dict__[key]


person = Person(name='dream', age=18)
# 我在被初始化对象时触发!

print(person['name'])
# dream

【9】__delitem__

(1)触发场景

  • 当使用删除操作符(del obj[key])删除对象的字段时自动触发。

(2)示例

# 创建一个类:类名为 Person,继承 object
class Person:
    def __init__(self, name, age):
        print(f"我在被初始化对象时触发!")
        self.name = name
        self.age = age

    def __delitem__(self, key):
        # 对象本身是没有 del obj[属性名] 这个方法的,但是通过 __delitem__ 可以让对象具有该方法
        print(f'我在del删除时触发 删除属性 :>>>> {key}')
        del self.__dict__[key]


person = Person(name='dream', age=18)
# 我在被初始化对象时触发!

del person['name']
# 我在del删除时触发 删除属性 :>>>> name

print(person.name)
# AttributeError: 'Person' object has no attribute 'name'

【10】__call__

(1)触发场景

  • 当对象调用函数(obj())时自动触发。

(2)示例

# 定义一个计算机模拟类
class Calculator:
    def __init__(self, num1, num2):
        # 初始化需要计算的参数
        self.num1 = num1
        self.num2 = num2

    # 加法
    def add(self):
        return self.num1 + self.num2

    def __call__(self):
        print(f"我在对象调用时被调用,计算结果为:{self.add()}")
        return self.add()


cal = Calculator(9, 6)

print(cal())
# 我在对象调用时被调用,计算结果为:15
# 15

【11】__enter____exit__

(1)触发场景

  • 使用with语句执行代码块时,先执行__enter__方法,然后执行代码块,再执行__exit__方法。

(2)示例

# 定义一个上下文处理函数
class ContextManager:
    def __init__(self, data):
        self.data = data

    # 当被 with 打开时触发
    def __enter__(self):
        print(f'Enter context with data: {self.data}')

        return self

    # 当 with 语句块执行完毕后触发
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f'Exit context with data: {self.data}')


with ContextManager('Hello World') as cm:
    print(cm.data)

# Enter context with data: Hello World
# Hello World
# Exit context with data: Hello World

【二】元类

【1】什么是元类

  • 一切源于一句话:Python中一切皆对象
  • 八大基本数据类型是对象
  • 类实例化得到的对象也是对象
  • 其实类本身也是一种对象
class Student(object):
    def __init__(self, name):
        self.name = name


# 【1】实例化类得到类的对象
student = Student(name='dream')
# 【2】查看这个对象的数据类型
print(f'student的数据类型:{type(student)}')
# student的数据类型:<class '__main__.Student'>

# 【3】查看产生这个类的数据类型
print(f'Student的数据类型:{type(Student)}')
# Student的数据类型:<class 'type'>

# 【4】查看产生字典的数据类型
print(f'dict 的数据类型: {type(dict)}')
# dict 的数据类型: <class 'type'>
  • 通过查看每一种数据的数据类型,我们会发现都共同拥有一个类
  • 这个类就是 type 我们也称之为元类

【2】产生类的两种方式

(1)直接关键字创建

  • 语法
关键字(class) 类名(Student)继承的父类(默认是object):
    # 类体代码
  • 示例
class Student(object):
    school = '清华大学'

    def __init__(self, name):
        self.name = name

    def read(self):
        print(f'{self.name}is reading!')


# 【一】查看创建当前类的类
print(type(Student))
# <class 'type'>

# 【二】查看当前类的名称空间
print(Student.__dict__)
# {'__module__': '__main__', 'school': '清华大学', '__init__': <function Student.__init__ at 0x10480b880>, 'read': <function Student.read at 0x12a3975b0>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}

(2)通过 type 创建

  • 语法
# 初始化 type 这个类的 __init__ 方法
def __init__(cls, what, bases=None, dict=None):  # known special case of type.__init__
    """
    type(object) -> the object's type
    type(name, bases, dict, **kwds) -> a new type
    # (copied from class doc)
    """
    pass

# 类名 = type('类名',(父类1,父类2,...),{数据属性名:数据属性值,函数属性名:函数属性值,...})
  • 示例
def read(self):
    ...


Student = type('Student', (object,), {'name': 'dream', 'read': read})

# 【一】查看创建当前类的类
print(type(Student))
# <class 'type'>

# 【二】查看当前类的名称空间
print(Student.__dict__)
# {'name': 'dream', 'read': <function read at 0x1041f3f40>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}


# 细心地你可能会发现,当我们使用关键字创建类内的方法的时候,查看他的内存空间是带着类名字的
# 而我们当前创建的这个类,初始化进去的只是一个普普通通的函数,也就是非绑定方法

【3】为什么要使用元类

  • 元类可以控制类的创建,也就意味着我们可以高度定制类的具体行为
    • 比如,当我们掌控了食品的生产过程,我们就可以在里面随便动手脚

【4】元类的基本使用

(1)需求

  • 要求所有类的名字必须是大写的

(2)思考

  • 我们应该在哪里定制这些代码
    • 类的产生过程也许我们还不熟悉
    • 但是对象的产生过程,是通过类内的 __init__ 方法实现的
    • 那我们猜,在元类里面也有一个 __init__方法

(3)基本使用

class MyType(type):
    def __init__(cls, class_name, class_bases, class_dict):
        print("MyType的__init__被调用,class_name是:", class_name)
        print("MyType的__init__被调用,class_bases是:", class_bases)
        print("MyType的__init__被调用,class_dict是:", class_dict)
        super().__init__(class_name, class_bases, class_dict)


# 元类的使用区别于我们继承中使用父类的那种形式
# 元类的使用采用  metaclass 关键字声明
# 在前面学习 abc 模块的时候,我们也使用过类似的语法

# 【1】创建一个类
class MyClass(object, metaclass=MyType):
    ...


# 【2】初始化类
MyClass()

# MyType的__init__被调用,class_name是: MyClass
# MyType的__init__被调用,class_bases是: (<class 'object'>,)
# MyType的__init__被调用,class_dict是: {'__module__': '__main__', '__qualname__': 'MyClass'}

(4)进阶使用

  • 限制类名必须首字母大写
class MyType(type):
    def __init__(cls, class_name, class_bases, class_dict):
        print("MyType的__init__被调用,class_name是:", class_name)
        print("MyType的__init__被调用,class_bases是:", class_bases)
        print("MyType的__init__被调用,class_dict是:", class_dict)
        if not class_name.istitle():
            raise TypeError(f'类名 {class_name} 必须首字母大写')
        super().__init__(class_name, class_bases, class_dict)


# 元类的使用区别于我们继承中使用父类的那种形式
# 元类的使用采用  metaclass 关键字声明
# 在前面学习 abc 模块的时候,我们也使用过类似的语法

# 【1】创建一个符合规范的类
# (1)创建类
class Myclass(object, metaclass=MyType):
    ...


# (2)初始化类
Myclass()


# 符合规范会被正常调用
# MyType的__init__被调用,class_name是: Myclass
# MyType的__init__被调用,class_bases是: (<class 'object'>,)
# MyType的__init__被调用,class_dict是: {'__module__': '__main__', '__qualname__': 'Myclass'}

# 【二】创建一个不符合规范的类
# (1)创建类
class myclass(object, metaclass=MyType):
    ...


# (2)初识化类
myclass()

# 不符合规范会直接报错
#     raise TypeError(f'类名 {class_name} 必须首字母大写')
# TypeError: 类名 myclass 必须首字母大写

【5】元类的进阶使用

(1)引入

  • 回想我们刚刚学过的 __call__方法
    • 在类内部定义一个 __call__方法,对象加括号会自动执行产生该对象的类里面的 __call__方法,并且可以得到对应的返回值
    • 类加括号调用,同理类加括号也会触发元类中的__call__方法,从而获得返回值,而这个返回值正是我们实例化类所得到的对象
class MyType(type):
    def __init__(cls, class_name, class_bases, class_dict):
        print(f'MyType 中的 init 方法 我被触发了')
        super().__init__(class_name, class_bases, class_dict)

    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(metaclass=MyType):
    def __init__(self, name):
        print(f' MyClass 中的 init 方法 我被触发了')
        self.name = name

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


obj = MyClass(name='张三')
print(obj)
print(obj())

# MyType 中的 init 方法 我被触发了
# MyType 中的 call 方法 我被触发了
# args: (), kwargs: {'name': '张三'}
#  MyClass 中的 init 方法 我被触发了
# <__main__.MyClass object at 0x1287b3dc0>
#  MyClass 中的 call 方法 我被触发了
# call 方法返回的值

(2)定制对象的产生过程

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

    def __call__(self, *args, **kwargs):
        print(f'MyType 中的 call 方法 我被触发了')
        print(f'args: {args}, kwargs: {kwargs}')
        # 定制对象的产生条件:只能通过关键字传参创建对象,不允许通过位置传参
        if args:
            raise TypeError(f'{self.__name__} 产生对象只能通过关键字传参创建')
        # 创建对象并初始化参数
        obj = super().__call__(*args, **kwargs)
        # print(obj)
        # 将产生的对象返回出去
        return obj


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

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


# 正确的
obj = MyClass(name='张三')
print(obj)
print(obj())

# MyType 中的 init 方法 我被触发了
# MyType 中的 call 方法 我被触发了
# args: (), kwargs: {'name': '张三'}
#  MyClass 中的 init 方法 我被触发了
# <__main__.MyClass object at 0x1287b3dc0>
#  MyClass 中的 call 方法 我被触发了
# call 方法返回的值

# 错误的,会被拦截并报错
obj = MyClass('张三')
print(obj)
print(obj())

#     raise TypeError(f'{self.__name__} 产生对象只能通过关键字传参创建')
# TypeError: MyClass 产生对象只能通过关键字传参创建

【6】总结

  • 元类属于面向对象中比较高阶的用法,无特殊需求和特殊情况外不建议使用

    • 如果单纯为了装逼,当我没说,嘿嘿
  • 如果你想高度定制类的产生过程

    • 那么编写元类里面的__init__方法
  • 如果你想高度定制对象的产生过程

    • 那么编写元类里面的__call__方法

【补充】__new__方法

  • __new__用于产生空对象(类),相当于人的骨架
  • __init__用于实例化对象(类),相当于人的血肉

(1)元类中的__new__直接调用

# 注意:并不是所有的地方都可以直接调用__new__ 该方法过于底层
# 如果是在元类的__new__里面 可以直接调用
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)


p = MyClass(name='dream')

# MyType 中的 new 方法 我被触发了
# ('MyClass', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'MyClass', '__init__': <function MyClass.__init__ at 0x120897640>, '__call__': <function MyClass.__call__ at 0x1208b96c0>})
# {}
# MyType 中的 init 方法 我被触发了
# MyType 中的 call 方法 我被触发了
# args: (), kwargs: {'name': 'dream'}
#  MyClass 中的 init 方法 我被触发了

(2)元类中的__call__间接调用

# 注意:并不是所有的地方都可以直接调用__new__ 该方法过于底层
# 如果是在元类的__new__里面 可以直接调用
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='dream')
posted @ 2024-02-28 16:34  Chimengmeng  阅读(40)  评论(0编辑  收藏  举报
/* */