(五)面向对象进阶----魔术方法(魔法方法、特殊方法)

(一)、什么是魔术方法?

在python中像__init__这类双下划线开头和结尾的方法,我们把它称为魔术方法。

 注意点: 魔术方法都是python内部定义的,自己不要去定义__init__这类双下划线开头的方法。

 (二)、例子

①、__new__

由打印结果我们可以看出来,我们只创建了一个对象,没有手动去调用这个__init__方法。由此可以说明,__init__方法在类去创建对象的时候可以自动去调用。

疑惑1:那么问题来了:是先创建对象然后再去调用__init__方法,还是先调用__init__方法再去创建对象?

答:答案很明显,肯定是先创建对象然后再去调用__init__方法,因为__init__魔术方法第一个参数是self,说明啥?说明这个init魔术方法是实例方法,self接收的是什么?就是实例对象。你只有先有了对象,你才能在初始化方法里面对对象进行初始化。

class MyClass(object):
    def __init__(self):
        """初始化方法"""
        print('--init--方法')


if __name__ == '__main__':
    MyClass()



# 打印结果
--init--方法

了解清楚之后,我们看下MyClass这个类的父类是什么?是不是一个叫object这个父类(object可以省略不写),因为我们所有的类都是集成这个object这个父类,那么object这个父类里面有什么呢?里面就有一个__new__这个方法,我们ctrl+鼠标左键object看下

 我们看下他的注释:创建并且返回一个新的对象。

那如果说我们自己定义一个__new__方法呢?我们执行看下打印结果,发现new方法调用了,但是init魔术方法没有调用。

class MyClass(object):
    def __init__(self):
        print('--init--方法')

    def __new__(cls, *args, **kwargs):
        print("--new方法--")


if __name__ == '__main__':
    m = MyClass()
    print(m)



# 打印结果
--new方法--
None

疑惑2:那么为什么init魔术方法没有调用呢?刚才不是说创建对象会自动调用init魔术方法的吗?

我们看打印结果对象m打印结果是None,没有创建出来对象。为什么没有创建出来对象?因为我们的new魔术方法里面没有返回出来对象。我们刚才看的源代码显示new方法是创建一个对象并且返回,所以找到问题了(必须要在__new__方法中实现类创建对象并且返回该对象的功能),我们改下代码试试,我们在我们创建的new魔术方法中取调用父类object里面的__new__魔术方法并且把他返回出去试试。我们看下修改后的代码:

class MyClass(object):
    def __init__(self):
        print('--init--方法')

    def __new__(cls, *args, **kwargs):
        print("--new方法--")
        return super().__new__(cls)


if __name__ == '__main__':
    m = MyClass()
    print(m)



# 打印结果
--new方法--
--init--方法
<__main__.MyClass object at 0x0000024D12748C70>

从上面打印结果看出来了,先调用new方法,然后创建一个对象并且返回之后,再去调用__init__方法去初始化对象。


疑惑3(经典实际运用场景之单例模式):__new__魔术方法在什么运用场景下才会去重写?

如果要控制类里面对象创建的过程,可以通过自定义new方法去实现,比方说:单例模式。

需求:类每次实例化的时候都会创建一个新的对象,如果要求类只能被实例化一次该怎么做呢?

 我们看下代码怎么实现:

先设置一个类的属性obj,默认值为None,然后判断如果obj为空,我们就去调用父类__new__方法去生成对象,然后重新赋值给类的属性obj并且return返回出去,如果obj不是None,说明他已经被赋值过了,不是第一次调用创建对象了,那我们就直接返回obj第一次创建对象。我们看打印结果两次创建出来的对象的内存地址指向同一个地方,说明他是同一个对象。

class Demo:
    obj = None

    def __new__(cls, *args, **kwargs):
        if cls.obj is None:
            instance = super().__new__(cls)
            cls.obj = instance
            return cls.obj

        else:
            return cls.obj


if __name__ == '__main__':
    d1 = Demo()
    print(d1)
    d2 = Demo()
    print(d2)



# 打印结果
<__main__.Demo object at 0x000002C0D7F68C70>
<__main__.Demo object at 0x000002C0D7F68C70>

这里还可以改进一下,这个单例模式不是很安全,为什么?因为我们看下obj这个类属性我们可以修改他的值。修改为None之后它第二次调用去创建对象的时候,又会重新生成一个对象。 

class Demo:
    obj = None

    def __new__(cls, *args, **kwargs):
        if cls.obj is None:
            instance = super().__new__(cls)
            cls.obj = instance
            return cls.obj

        else:
            return cls.obj


if __name__ == '__main__':
    d1 = Demo()
    print(d1)
    Demo.obj = None
    d2 = Demo()
    print(d2)




# 打印结果
<__main__.Demo object at 0x000001A648B58C70>
<__main__.Demo object at 0x000001A648B58700>

那这个时候我们怎么去处理呢?我们可以把这个obj这个类属性设置为私有属性。 提示同事这个是私有属性,不要去改。但是如果有人非要去改也是可以改的 当然了不会有人去做,除非你在公司跟同事结仇拉。

class Demo:
    __obj = None

    def __new__(cls, *args, **kwargs):
        if cls.__obj is None:
            instance = super().__new__(cls)
            cls.__obj = instance
        return cls.__obj


if __name__ == '__main__':
    d1 = Demo()
    print(d1)
    Demo.obj = None
    d2 = Demo()
    print(d2)




# 打印结果
<__main__.Demo object at 0x000002BD76978C70>
<__main__.Demo object at 0x000002BD76978C70>

 


疑惑4:那么,又有童鞋问了,这个单例模式实际运用场合又是什么叻?

import os
import logging
from logging.handlers import TimedRotatingFileHandler, BaseRotatingHandler
import colorama

colorama.init()


class Logger:
    __instance = None
    sh = logging.StreamHandler()

    def __new__(cls, path=None, level='DEBUG'):
        """
        :param path: report path
        :param args:
        :param kwargs:
        :return:
        """
        if not cls.__instance:
            cls.__instance = super().__new__(cls)
            log = logging.getLogger('apin-ui')
            log.setLevel(level)
            cls.__instance.log = log
        return cls.__instance

    def set_level(self, level):
        """设置日志输出的等级"""
        self.log.setLevel(level)

    def set_file_handle(self, level, path):
        if path:
            if not os.path.isdir(path):
                os.mkdir(path)
            fh = TimedRotatingFileHandler(os.path.join(path, 'apin-ui.log'), when='d',
                                          interval=1, backupCount=7,
                                          encoding="utf-8")
            fh.setLevel(level)
            self.log.addHandler(fh)
            # 定义handler的输出格式
            formatter = logging.Formatter("%(asctime)s | 【%(levelname)s】 | : %(message)s")
            fh.setFormatter(formatter)

    def debug(self, message):
        self.fontColor('\033[0;34m{}\033[0;34m{}\033[0;34m{}')
        self.log.debug(message)

    def info(self, message):
        self.fontColor('\033[0;32m{}\033[0;32m{}\033[0;32m{}')
        self.log.info(message)

    def warning(self, message):
        self.fontColor('\033[0;33m{}\033[0;43m{}\033[0;33m{}')
        self.log.warning(message)

    def error(self, message):
        self.fontColor('\033[0;31m{}\033[0;41m{}\033[0;31m{}')
        self.log.error(message)

    def exception(self, message):
        self.fontColor('\033[0;31m{}\033[0;41m{}\033[0;31m{}')
        self.log.exception(message)

    def critical(self, message):
        self.fontColor('\033[0;35m{}\033[0;45m{}\033[0;35m{}')
        self.log.critical(message)

    def fontColor(self, color):
        # 不同的日志输出不同的颜色
        formatter = logging.Formatter(color.format("%(asctime)s| ", "【%(levelname)s】", " | : %(message)s"))
        self.sh.setFormatter(formatter)
        self.log.addHandler(self.sh)


def print_info(msg):
    print('\033[0;32m{}'.format(msg))


def print_waring(msg):
    print('\033[0;33m{}'.format(msg))


def print_error(msg):
    print('\033[0;31m{}'.format(msg))


if __name__ == '__main__':
    logger = Logger()
    logger.debug('debu等级日志')
    logger.info('info日志')
    logger.warning('warning日志')
    logger.error('error日志')
    logger.critical('CRITICAL日志')

 打印结果:


 ②、上下文协议

问题思考:with打开文件为何会自动关闭?

上下文管理器的概念:上下文管理器是一个python对象,为操作提供了额外的上下文信息。这种额外的信息,在使用with语句初始化上下文,以及完成with块中的所有代码时,才用可调用的形式。

  • object.__enter__(self)
    输入与此对象相关的运行时上下文。如果存在的话,with语句将绑定该方法的返回值到该语句的as字句中指定的目标
  • object.__exit(self,exc_type,exc_val,exc_tb)     
    exc_type:  # 异常类型
    exc_val: # 异常值
    exc_tb: # 异常回溯追踪
    退出与此对象相关的运行时上下文。参数描述导致上下文退出的异常。如果该上下文退出时没有异常,三个参数都将为None。如果提供了一个异常,并且该方法希望抑制该异常(即防止他被传播),它应该返回一个真值。否则,在退出此方法后,异常将被正常处理。注意__exit__()方法不应该重新抛出传递进去的异常,这是调用者的责任。

上下文管理器实现:如果在一个类中实现了__enter__,__exit__这2个方法,那么这个类就可以当做一个上下文管理器来用。 

我们看下open的源代码,大概意思就是打开一个文件并且返回一个对象给你。所以当我们执行with 的时候,with后面的对象会先去执行 __enter__,然后执行完这个方法之后,把这个方法的返回值赋值给as后面的变量f。当with里面的代码全部执行完毕之后,他会执行__exit__这个魔术方法,这个exit魔术方法已经实现了关闭文件这个操作,所以通过with操作文件的时候,不需要手动去调用关闭文件这个操作。

with open("logger.py", 'r', encoding='utf-8') as f:
    print(f)

下面我们手动实现一个文件操作的上下文

class OpenFile(object):
    '''手动实现文件操作的上下文'''

    def __init__(self, filename, method):
        # 初始化打开文件
        self.file = open(filename, method)

    def __enter__(self):
        # 启动上下文时,将打开的对象返回出去
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        # 退出上下文时,将文件关闭
        self.file.close()

print()
with OpenFile("python.txt", 'r') as f:
    f.write('hello')

我们打个断点调试一下(print()这一行目的就是为了方便调试的,实际代码中这一行可以去掉),看看代码的运行流程是啥。

  1. with后面跟的是一个对象 OpenFile("python.txt", 'r'),所以第一步先初始化对象,代码执行__init__魔法方法,里面代码做了打开文件,并将文件对象赋值给了file。
  2. 因为实现了上下文协议,所以当打开文件的时候,代码执行到__enter__这个魔术方法,并且把打开的对象返回出去。(with代码执行之前先走enter魔术方法)
  3. 然后执行 f.write('hello') ,最后再执行__exit__这个魔术方法,关闭文件。(with代码执行完毕之后然后跑enter魔术方法)


注意:我们这里在with代码里故意写错了让他报错,看看代码执行结果。

 我们通过debug调试发现了,即使with代码里报错了,他还是会去执行exit魔术方法(只要不是报语法错误)。

我们打个断点调试一下exit这个魔术方法里传的三个参数是啥。

  • exc_type: NameError    with中的代码如果执行出错,则会将异常类型传递给exc_type
  • exc_val: name is not defined   with中的代码如果执行出错,则会将异常信息传递给exc_val
  • exc_tb:  异常的溯源   with中的代码如果执行出错,则会将异常溯源对象传递给exc_tb

 

 

 既然出错了,那么我们可以在exit魔术方法中干什么?当然是记录日志。

③、__call__

作用:__call__是用来实现类的对象 调用的行为

实际运用场景:通过类去实现装饰器

疑惑1:python中万物皆对象,函数也是对象,为什么函数可以调用,而其他的对象不行?

我们先上一个demo给大家看一下:

class Demo:
    pass


def func():
    print('-----func-----')


if __name__ == '__main__':
    c = Demo()
    print(callable(func))
    print(callable(c))




# 打印结果
True
False

由此看出来,func这个函数是可调用的,但是这个c是不能调用的,为什么呢?

答:是因为实现了可调用协议,就是魔术方法__call__   

那我们把代码改一下,在Demo这个类里面实现__call__这个魔术方法试试看。很明显,这个类所创建的对象可以调用了。

class Demo:

    def __call__(self, *args, **kwargs):
        print('---demo---call')


def func():
    print('-----func-----')


if __name__ == '__main__':
    c = Demo()
    print(callable(func))
    print(callable(c))
    c()



# 打印结果
True
True
---demo---call

疑惑2:如果想让类创建出来的对象,可以像函数一样被调用可以实现吗?

下面我们上demo:

因为Demo实例化对象不需要传参数,但是你给他传了一个参数。


那么我们就给他这个类定义一个init方法来接收我们这个func参数,

  • 我们这个时候打印出来这个init里面传的参数func是什么?很明显,是func函数。这个应该能理解。
  • 最外面print(func)打印出来的是什么?是不是Demo这个类的对象?很明显,是的。


这个时候我们在外面调用func(),还能不能正常去调用?很明显不能了(报错Demo这个类的对象不能被调用),为什么?因为这个类里面没有实现__call__魔术方法。

 那么问题来了,如果你想你的函数被类装饰器装饰之后,你还想你的函数func能够正常被调用,那我们该怎么办?答:实现__call__方法

我们该怎么做呢?上代码。

  • 既然我们init魔术方法中传进去的func是外面这个func函数,那我们先把他保存为属性。
  • 然后手写一个__call__方法,外部执行func()的时候,上述已经讲了这个func实际上就是Demo这个类创建出来的对象,我们现在已经实现了__call__方法,所以外部调用func()的时候实际上代码跑的是类里面这个__call__方法,那call魔术方法中执行的是什么呢?执行的是原功能函数(因为此时类里面func指向的是原功能函数名)

 下面上代码方便大家复制,自己实际操作一下

class Demo:
    def __init__(self, func):
        self.func = func
        print('init方法中接收到的func', func)

    def __call__(self, *args, **kwargs):
        print("---装饰器扩展的代码---start")
        self.func()
        print("---装饰器扩展的代码---end")


# 通过Demo这个类去实例化一个对象,然后返回给func这个函数
# func实际上是类的一个对象
@Demo  # func = Demo(func)
def func():
    print('-------func-------')


print(func)

func()



# 打印结果
init方法中接收到的func <function func at 0x0000024CA583DAF0>
<__main__.Demo object at 0x0000024CA5AB8280>
-------func-------

(三)实战题目练习

"""
1、通过装饰器实现单例模式,只要任意一个类使用该装饰器装饰,
那么就会变成一个单例模式的类。(面试真题)


2、请实现一个类,前五次创建对象,每次都可以返回一个新的对象,
第六次开始,每次创建,都随机返回前5个对象中的一个


3、通过类实现一个通用装饰器,既可以装饰函数,又可以装饰器类,不管函数和类需不需要传参都可以装饰

"""

# 第一题:
# 方式1:函数实现装饰器
import random


#
# def single(cls):
#     def wrapper(*args, **kwargs):
#         if not hasattr(cls, '__instance'):
#             cls.__instance = cls(*args, **kwargs)
#         return cls.__instance
#
#     return wrapper

class single:
    def __init__(self, cls):
        self.cls = cls

    def __call__(self, *args, **kwargs):
        if not hasattr(self.cls, '__instance'):
            self.cls.__instance = self.cls(*args, **kwargs)
        return self.cls.__instance


@single  # Musen = single(Musen)
class Musen:
    pass


# 第二题:
class MyTest(object):
    __obj_list = []

    def __new__(cls, *args, **kwargs):
        # 判断列表中对象的个数是否少于5
        if len(cls.__obj_list) < 5:
            obj = super().__new__(cls)
            cls.__obj_list.append(obj)
            return obj
        else:
            # 大于5则随机返回一个
            return random.choice(cls.__obj_list)


# 第三题:
class Decorator(object):

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

    def __call__(self, *args, **kwargs):
        res = self.item(*args, **kwargs)
        return res


def deco2(item):
    def wrapper(*args, **kwargs):
        res = item(*args, **kwargs)
        return res

    return wrapper


if __name__ == '__main__':
    m1 = MyTest()
    m2 = MyTest()
    m3 = MyTest()
    m4 = MyTest()
    m5 = MyTest()
    m6 = MyTest()
    print(m1, m2, m3, m4, m5, m6, sep='\n')

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值