python面向对象双下划线方法与元类

双下划线方法(__)

面向对象中的双下方法也有一些人称之为是魔法方法,有些双下方法不需要刻意调用,到达某个条件会自动触发,就比如我们在对象实例化中学的__init__方法。

image

__str__:对象被执行打印(print、前端展示)操作的时候自动触发,该方法必须返回字符串类型的数据。
例子:原本应该是打印对象的,但定义了该方法后,打印的值变成了该方法的返回值。

# 没有__str__的情况:
class MyClass1(object):
    pass
obj1 = MyClass1()
print(obj1) # 输出:<__main__.MyClass object at 0x000001D5B350F208>

# 定义了__str__的情况:
class MyClass2(object):
    def __str__(self):
        """这里可以自定义其他操作"""
        return '这里是返回的内容'
obj2 = MyClass2()
print(obj2)  # 输出:这里是返回的内容

__del__:对象被执行(被动、主动)删除操作之后自动执行。
例子:程序运行结束python会自动删除(回收)定义的名称,所以程序结束MyClass被删除了,就执行了该方法。

class MyClass(object):
    def __del__(self):
        """这里可以自定义其他操作"""
        print("MyClass被删除了")
obj = MyClass()
"""
执行结果:
MyClass被删除了
"""

__call__:对象被加括号调用的时候自动触发。
例子:

class MyClass(object):
    def __call__(self, *args, **kwargs):
        """自定义操作"""
        print(args, kwargs)
        return '执行的返回值'
obj = MyClass()
res = obj('aaa', a=1)  # 输出:('aaa',) {'a': 1}
print(res)  # 输出:执行的返回值

__enter__:对象被执行with上下文管理语法开始自动触发,该方法返回什么as后面的变量名就会得到什么。
__exit__:对象被执行with上下文管理语法结束之后自动触发。
例子:

class MyClass(object):
    def __enter__(self):
        """自定义操作"""
        print('__enter__方法')
        return '这是给as后面的变量的'

    def __exit__(self, exc_type, exc_val, exc_tb):
        """对象被执行with上下文管理语法结束之后自动触发"""
        print('__exit__方法')

obj = MyClass()
with obj as o:
    print(o)
"""
执行结果:
__enter__方法
这是给as后面的变量的
__exit__方法
"""

__setattr__:对象在执行添加属性操作的时候自动触发。
例子:因为执行了:对象.变量名=变量值的赋值操作,触发了该方法。

class MyClass(object):
    def __setattr__(self, key, value):
        """这里可以自定义其他操作"""
        print('添加键为%s, 值为%s' % (key, value))
obj = MyClass()
obj.name = 'tom'  # 输出:添加键为name, 值为tom

__getattr__:对象查找不存在名字的时候自动触发。
例子:查找到名字时按正常流程走,找不到名字执行该方法,并把该方法返回值替换成需要查找的值。

class MyClass(object):
    name = 'tom'
    def __getattr__(self, item):
        """这里可以自定义其他操作"""
        return '不好意思 没有%s这个名字' % item

obj = MyClass()
print(obj.name)  # 输出:tom
print(obj.age)  # 输出:不好意思,没有age这个名字

__getattribute__:只要对象查找名字无论名字是否存在都会执行该方法,如果类中有__getattribute__方法,那么就不会去执行__getattr__方法

class MyClass(object):
    def __getattribute__(self, item):
        print('不管有没有,我都执行')
        return '这是执行的返回值'

    def __getattr__(self, item):
        return '不好意思 没有%s这个名字' % item

obj = MyClass()
res = obj.name  # 输出:不管有没有,我都执行
print(res)  # 输出:这是执行的返回值

笔试题

1.让字典具备句点符查找值的功能

点击查看答案
# 1.定义一个类继承字典
class MyDict(dict):
    def __getattr__(self, item):
        return self.get(item)

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

'''要区别是名称空间的名字还是数据k:v键值对'''
obj = MyDict({'name': 'jason', 'age': 18})
obj.pwd = 123  # 给字典名称空间添加名字  不是数据k:v
print(obj)

2.补全下列代码 使其运行不报错

class Context:
    pass
with Context() as ctx:
    ctx.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 ctx:
    ctx.do_something()

image

元类简介

元类,即产生类的类。

在学类之前,我们都说type()是查看数据类型的方法,但在学了类之后,可以推导出type()查看的其实是当前对象所属的类名称。比如123属于int类、'asd'属于str类、[1,2,3]属于list类等等。

我们来看一段代码:

class MyClass(object):
    pass
obj = MyClass()
print(type(obj))  # 输出:<class '__main__.MyClass'>
print(type(MyClass))  # 输出:<class 'type'>

在上述例子中,用type()方法可以看到obj属于MyClass类,MyClass类属于的是type类,而且obj是MyClass类产生的,所以可以推导出MyClass类是type类产生的。

总结:type就是元类,而且是所有类默认的元类。

学习元类的目的:

元类能够控制类的创建,也就意味着我们可以高度定制类的行为,就好比掌握了物品的生产过程,就可以在过程中做任何的额外操作。

产生类的两种表现形式

产生类有两种方法,但其实本质上都是同一种。

产生类有两种方法:

  • class关键字产生类。

  • 用type元类产生类。

第一种我们已经熟知了,重点是第二种方法。

用type元类产生类:

书写方式:type(类名, 父类, 类的名称空间)

比如:

MyClass = type('MyClass', (), {'name': 'tom'})
obj = MyClass()
print(obj.name)  # 输出:tom

元类的基本使用

我们都知道,自定义列表的类需要创建一个类去继承list类,自定义元类也是一样,只需要创建一个类去继承type类,但是如果要让一个类去使用自定义元类就需要用关键字赋值的方式。比如:

# 必须是继承了type的类才是自定义元类
class Mymeta(type):
    pass

# 通过metaclass可以指定类的元类
class MyClass(metaclass=Mymeta):
    pass

print(type(MyClass))  # 输出:<class '__main__.Mymeta'>

问题:如何让一个类必须要大写字母开头。

解决:

class MyTypeClass(type):
    # 重写type元类中的方法
    def __init__(cls, cls_name, cls_bases, cls_dict):
        # 如果创建的类是大写,则报错
        if not cls_name.istitle():
            raise Exception("类名的首字母必须大写")
        # 如果创建的类是大写字母开头,则执行元类type中的方法
        super().__init__(cls_name, cls_bases, cls_dict)

class C1(metaclass=MyTypeClass):  # 正常执行
    school = '清华大学'

class a(metaclass=MyTypeClass):  # 在这里报错
    school = '清华大学'

程序执行到在定义a类的时候就会报错。

元类进阶操作

回想:对象加括号会自动执行产生该对象的类里面的__call__,并且该方法返回什么对象加括号就会得到什么。

推导:类加括号会自动执行元类的里面的__call__,并且该方法返回什么类加括号就会得到什么。

image

证明:

# 自定义元类
class Mymeta(type):
    def __call__(self, *args, **kwargs):
        print('类被调用了')

# 使用自定义元类创建类
class MyClass(metaclass=Mymeta):
    pass

MyClass()  # 输出:类被调用了

进阶:在创建对象的时候需要用类加括号的方式实例化,但是调用类也是用类加括号的方式,那么类里面的__init__方法和元类里面的__call__方法执行的先后顺序是怎么样的?。

# 自定义元类
class Mymeta(type):
    def __call__(self, *args, **kwargs):
        print('类被调用了')

# 使用自定义元类创建类
class MyClass(metaclass=Mymeta):
    def __init__(self):
        print('类被初始化了')

res = MyClass()  # 输出:类被调用了

由上述结果得知:如果同时存在类里面的__init__方法和元类里面的__call__方法,那么它们的执行优先级为:元类里面的__call__优先级更高。

那么该如何用MyClass类创建对象呢?其实只要返回type元类里面的__call__即可

# 自定义元类
class Mymeta(type):
    def __call__(self, *args, **kwargs):
        print('类被调用了')
        return super().__call__(*args, **kwargs)

# 使用自定义元类创建类
class MyClass(metaclass=Mymeta):
    def __init__(self):
        print('类被初始化了')

res = MyClass()
print(res)  # 输出:<__main__.MyClass object at 0x0000019FC14CC198>

由这个我们可以实现类在实例化产生对象的时候,对象的独有数据必须采用关键字参数。

# 自定义元类
class Mymeta(type):
    def __call__(self, *args, **kwargs):
        if args:
            raise Exception('必须全部采用关键字参数')
        return super().__call__(*args, **kwargs)

# 使用自定义元类创建类
class MyClass(metaclass=Mymeta):
    def __init__(self, name):
        self.name = name

obj1 = MyClass(name='tom')  # 正常执行
obj2 = MyClass('tom')  # 程序报错

元类总结:

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

__new__方法

__new__是用于产生空对象用的,它会返回一个空对象。

事实上,__init__和__call__执行的时候都会先产生一个空对象,而这个空对象就是由__new__产生的。

我们来看一个例子:

# 自定义元类
class Mymeta(type):
    def __new__(cls, *args, **kwargs):
        print('__new__ run')
        return super().__new__(cls, *args, **kwargs)
    def __call__(self, *args, **kwargs):
        print('__call__ run')
        return super().__call__(*args, **kwargs)

# 使用自定义元类创建类
class MyClass(metaclass=Mymeta):
    def __init__(self):
        print('__init__ run')

obj1 = MyClass()  
"""
执行结果:
__new__ run
__call__ run
__init__ run
"""

我们可以看出__new__方法是最优先的,并且如果__new__方法不返回空对象,程序就会报错,无法执行__init__和__call__。

所以__init__和__call__方法中的self形参其实是从__new__方法获得的。

image

posted @ 2022-04-11 19:13  Yume_Minami  阅读(178)  评论(0编辑  收藏  举报