如何获得对生成器发送方法的弱引用?

How can I get a weak reference to the send method of a generator?

weakref documentation 似乎没有提供创建对生成器的 send 方法的弱引用的方法:

import weakref

def gen(): yield

g=gen()
w_send=weakref.ref(g.send)
w_send() # <- this is None; the g.send object is ephemeral

我认为它行不通,但我确实尝试了 weakref.WeakMethod 以防万一:

>>> w_send=weakref.WeakMethod(g.send)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\ricky\AppData\Local\Programs\Python\Python37\lib\weakref.py", line 50, in __new__
    .format(type(meth))) from None
TypeError: argument should be a bound method, not <class 'builtin_function_or_method'>

如何在不将生成器包装在自定义 class 中的情况下完成此操作?像这样:

import weakref

def gen(): yield

class MyGenerator:
    def __init__(self):
        self._generator = gen()
    def send(self, arg):
        return self._generator.send(arg)

g = MyGenerator()
ref = weakref.WeakMethod(g.send)

我不想这样做。有没有更好的方法?


我想这样做的原因是我正在研究一个想法,为我可能构建的应用程序创建一个简单的消息传递协议。消息看起来像这样:

# messaging module

from typing import Generator
from functools import wraps
from collections import NamedTuple
import weakref

class Message(NamedTuple):
    channel: int
    content: str

_REGISTRY = {}

def _do_register(channel, route):
    # handle bound methods
    if hasattr(route,"__self__") and hasattr(route,"__func__"):
        route_ref = weakref.WeakMethod(route)
    # handle generators
    elif isinstance(route, Generator):
        route_ref = weakref.ref(route.send) # get weak ref to route.send here
    # all other callables
    else:
        route_ref = weakref.ref(route)
    try:
        _REGISTRY[channel].add(route_ref)
    except KeyError:
        _REGISTRY[channel] = {route_ref}

def register(obj=None, *, channel, route=None):
    """Decorator for registering callable objects for messaging."""
    if obj is None:
        def wrapper(callable):
            @wraps(callable)
            def wrapped(*args, **kwargs):
                nonlocal route
                obj_ = callable(*args, **kwargs)
                route_ = obj_ if route is None else route
                _do_register(channel, route_)
                return obj_
            return wrapped
        return wrapper
    else:
        if route is None:
            route = obj
        _do_register(channel, route)

def manager():
    msg_obj = None
    while True:
        msg_obj = yield _broadcast(msg_obj)

def _broadcast(msg_obj):
    count = 0
    if msg_obj:
        for route_ref in _REGISTRY[msg_obj.channel]:
            route = route_ref()
            if route is not None:
                count += 1
                route(msg_obj)
    return count

...这样使用:

@register(channel=1)
def listening_gen(name):
    while True:
        msg = yield
        print(f"{name} received message {msg.content} on channel {msg.channel}")


a = listening_gen("a")
b = listening_gen("b")
next(a)
next(b)
register(a, channel=2)
register(b, channel=3)

msg1 = Message(channel=1, content="foo")
msg2 = Message(channel=2, content="bar")
msg3 = Message(channel=3, content="baz")

m = manager()
next(m)
m.send(msg1)
m.send(msg2)
m.send(msg3)

a 在频道 1 和 2 上收听消息,b 在频道 1 和 3 上收听消息。

来自the docs

Not all objects can be weakly referenced; those objects which can include class instances, functions written in Python (but not in C), instance methods, sets, frozensets, some file objects, generators, type objects, sockets, arrays, deques, regular expression pattern objects, and code objects.

由于生成器是用 C 编写的内置类型,因此您不能创建对生成器的 send 方法的弱引用。正如您已经发现的那样,解决方法是将生成器包装在 python class.