Discuss / Python / Application.make_handler(...)方法过时解决方法

Application.make_handler(...)方法过时解决方法

Topic source

按廖老师的代码,运行后出现警告:

DeprecationWarning: loop argument is deprecated app = web.Application(loop=loop)
DeprecationWarning: Application.make_handler(...) is deprecated, use AppRunner API instead

我用的Python版本是3.6.6,虽然运行没问题,但是看着IDE中标记app.make_handler(),强迫症表示不能忍。而且这个方法在高版本或者将来可能会被移除,因此最好使用官方推荐的最新方法,那么我们就来尝试来解决它。

查看make_handler()方法的源码:

def make_handler(self, *, loop=None, access_log_class=AccessLogger, **kwargs):
    warnings.warn("Application.make_handler(...) is deprecated, use AppRunner API instead", DeprecationWarning, stacklevel=2)
    return self._make_handler(loop=loop, access_log_class=access_log_class, **kwargs)

意思就是这段代码过时了,推荐使用AppRunner类的API来代替make_handler()方法,于是查看AppRunner源码:

class AppRunner(BaseRunner):
    """Web Application runner"""

    ...

    def __init__(self, app, *, handle_signals=False, **kwargs):
        super().__init__(handle_signals=handle_signals, **kwargs)
        if not isinstance(app, Application):
            raise TypeError("The first argument should be web.Application instance, got {!r}".format(app))
        self._app = app

    ...

    async def _make_server(self):
      loop = asyncio.get_event_loop()
      self._app._set_loop(loop)
      self._app.on_startup.freeze()
      await self._app.startup()
      self._app.freeze()
      return self._app._make_handler(loop=loop, **self._kwargs)

AppRunner的构造__init__()方法中,需要传递一个app参数,这个app必须是Application实例,而_make_server()方法中,正好返回了我们想调用的app._make_handler(),即app.make_handler()!那么直接调用_make_server()可以吗?别忘了,以“_”开头的方法或属性,虽然可以调用,但并不是官方推荐的方式,所以让我们继续往下找。

我们发现在AppRunner类源码中,_make_server()并没有被调用,但却惊奇地发现_make_server()方法其实是重载了父类BaseRunner的_make_server(),那么可以肯定的是,_make_server()必定是被从父类继承而来的方法调用了。让我们看看在BaseRunner中,_make_server()被谁调用了:

class BaseRunner(ABC):
    
    ...

    def __init__(self, *, handle_signals=False, **kwargs):
        self._handle_signals = handle_signals
        self._kwargs = kwargs
        self._server = None
        self._sites = []

    @property
    def server(self):
        return self._server

    ...

    async def setup(self):
        loop = asyncio.get_event_loop()

        if self._handle_signals:
            try:
                loop.add_signal_handler(signal.SIGINT, _raise_graceful_exit)
                loop.add_signal_handler(signal.SIGTERM, _raise_graceful_exit)
            except NotImplementedError:  # pragma: no cover
                # add_signal_handler is not implemented on Windows
                pass

        self._server = await self._make_server()

    ...

    @abstractmethodasync
    def _make_server(self):
        pass  # pragma: no cover

在BaseRunner类中,通过注解可以看到_make_server()仅仅是一个抽象方法,没有实际意义。这正好验证了我们的猜想!一般抽象类的方法,在父类方法中都有重要用途,但是希望子类去实现它,这样子类实例对象调用的时候,实际上是调用了子类实现的_make_server()。

setup()方法中,正好调用了_make_server()方法,并把结果绑定到了_server属性,而通过server()方法,我们可以轻松调用到这个属性!这里复习一下装饰器@property,调用的时候是“对象.server”形式,而不是“对象.server()”。

重新梳理一下,调用思路是:

实例化Application类(app) -->  实例化AppRunner类(apprunner)并将app传递给apprunner  -->  调用AppRunner从父类BaseRunner继承而来的setup()方法  -->  调用server()方法得到handler对象

经过这么层层分析,我们终于找到了官方推荐的调用方式,让我们改写一下程序:

async def init(loop):
    host = '127.0.0.1'
    port = 9000
    app = web.Application(loop=loop)
    app.router.add_route("GET", "/", index)
    ############################################################################################################
    # srv = await loop.create_server(app.make_handler(), '127.0.0.1', 9000) # 廖老师的方法                      #
    ############################################################################################################
    # 新的方法
    apprunner = web.AppRunner(app) # 构造AppRunner对象
    await apprunner.setup()        # 调用setup()方法,注意因为源码中这个方法被async修饰,所以前面要加上await,否则报错
    srv = await loop.create_server(apprunner.server, host, port) # 将apprunner的server属性传递进去
    ############################################################################################################
    logging.info('server started at %s:%d...' % (host, port))
    return srv

make_handler()方法和loop参数都过时了,需要同步去掉。

这里虽然替换掉了make_handler()方法,但是,还是用到了loop参数,这个恐怕不行吧。


  • 1

Reply