简体   繁体   English

如何泛化可能是异步,龙卷风协程或正常的函数调用?

[英]How to generalize a function call which may be async, tornado coroutine, or normal?

I have an application which has a library in multiple configurations: 我有一个具有多个配置库的应用程序:

  • Python2.7 native Python2.7本机
  • Python2.7 tornado Python2.7龙卷风
  • Python3.5 asyncio Python3.5异步

Currently, I have code that is nearly identical against all three, but there are minor differences in how each function call are invoked. 目前,我的代码几乎对所有这三个都完全相同,但是在调用每个函数调用的方式上却有微小的差异。 This means I have a ton of code duplication, because I have stuff like the following in many places: 这意味着我有大量的代码重复,因为在许多地方都有类似以下的内容:

#Python2.7native.py
def main(client):
   client.foo(args)
   client.bar(args)

#Python2.7tornado.py
@gen.coroutine
def main(client):
    yield client.foo(args)
    yield client.bar(args)

#Python3.5asyncio.py
async def main(client):
    await client.foo(args)
    await client.bar(args)

where client is a language specific implementation, supporting native python, asyncio, and tornado respectively. 其中client是特定于语言的实现,分别支持本机python,asyncio和Tornado。 The API method calls are identical. API方法调用是相同的。

I am hoping to be able to somehow generalize this into a single method I can include in a shared file, which appropriately calls the various methods 我希望能够以某种方式将其概括为可以包含在共享文件中的单个方法,该方法可以适当地调用各种方法

I've thought about defining the methods in a separate file and using getattr to invoke the test properly, but this seems really messy. 我曾考虑过在一个单独的文件中定义方法,并使用getattr正确调用测试,但这似乎很混乱。

Is there a good way to do this? 有什么好方法吗?

You can't do all of this in one function - how is client.foo() supposed to know whether it's being called from a "normal" synchronous application, or whether its caller is going to use yield or await . 您不能在一个函数中完成所有这些操作client.foo()应该如何知道是从“常规”同步应用程序中调用它,还是其调用者将使用yieldawait However, as long as you're willing to have Tornado as a dependency, you can avoid duplicating all your code three times. 但是,只要您愿意将Tornado作为依赖项,就可以避免将所有代码重复三次。

In one module, client_async.py , implement your function(s) using Tornado's @gen.coroutine : 在一个模块client_async.py ,使用Tornado的@gen.coroutine实现您的功能:

@gen.coroutine
def foo(args):
    yield something()
    yield something_else()
    raise gen.Return(another_thing())

In another, client_sync.py , wrap each of the functions from client_async.py in IOLoop.run_sync() for a thread-local IOLoop like this: 在另一个client_sync.py ,将来自client_async.py每个函数包装在IOLoop.run_sync() ,以实现线程本地的IOLoop,如下所示:

import client_async
import threading
import tornado.ioloop

class _LocalIOLoop(threading.local):
    def __init__(self):
        self.value = tornado.ioloop.IOLoop()
local_ioloop = _LocalIOLoop()

def foo(args):
    return local_ioloop.value.run_sync(lambda: foo_async.my_func(args))

Now you can use this code from all three environments. 现在,您可以在所有三个环境中使用此代码。 From normal synchronous code: 从正常的同步代码:

import client_sync

def main():
    x = client_sync.foo(args)

From Tornado @gen.coroutine : 从龙卷风@gen.coroutine

import client_async

@gen.coroutine
def main():
    x = yield client_async.foo(args)

From async def and asyncio (note that the two are not synonymous - it is possible to use async def with Tornado without asyncio ): async defasyncio (请注意,这两个不是同义词-可以在不使用asyncio情况下将asyncioasync def一起使用):

# one-time initialization for Tornado/asyncio integration
import tornado.platform.asyncio
tornado.platform.asyncio.AsyncIOMainLoop().install()

import client_async

async def main():
    x = await client_async.foo(args)

Use @gen.coroutine and yield : This will work in all Python versions. 使用@gen.coroutineyield :这将在所有Python版本中工作。 A function decorated with gen.coroutine is a little slower than a native coroutine, but can be used in all the same scenarios. gen.coroutine装饰的功能比本地协程要慢一些,但是可以在所有相同的情况下使用。

For the synchronous case, use run_sync : 对于同步情况,请使用run_sync

result = IOLoop.current().run_sync(main)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM