我不知道为什么,但每当我尝试传递给共享对象共享自定义类对象的方法时,我都会遇到这个奇怪的错误。 Python版本:3.6.3
代码:
from multiprocessing.managers import SyncManager
class MyManager(SyncManager): pass
class MyClass: pass
class Wrapper:
def set(self, ent):
self.ent = ent
MyManager.register('MyClass', MyClass)
MyManager.register('Wrapper', Wrapper)
if __name__ == '__main__':
manager = MyManager()
manager.start()
try:
obj = manager.MyClass()
lst = manager.list([1,2,3])
collection = manager.Wrapper()
collection.set(lst) # executed fine
collection.set(obj) # raises error
except Exception as e:
raise
错误:
---------------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Program Files\Python363\lib\multiprocessing\managers.py", line 228, in serve_client
request = recv()
File "D:\Program Files\Python363\lib\multiprocessing\connection.py", line 251, in recv
return _ForkingPickler.loads(buf.getbuffer())
File "D:\Program Files\Python363\lib\multiprocessing\managers.py", line 881, in RebuildProxy
return func(token, serializer, incref=incref, **kwds)
TypeError: AutoProxy() got an unexpected keyword argument 'manager_owned'
---------------------------------------------------------------------------
这里的问题是什么?
答案 0 :(得分:8)
我也遇到了这个问题,如前所述,这是Python multiprocessing
(请参阅issue #30256)中的一个错误,而纠正该错误的pull request尚未合并。
除了手动修补本地安装之外,还有其他三个选项:
MakeProxyType()
可调用来指定您的代理类型,而不必依赖AutoProxy
代理生成器,在解释AutoProxy
的作用之后,我将在下面描述这些选项:
AutoProxy
类的意义是什么多处理Manager
模式通过将所有值置于相同的专用“规范值服务器”过程中来访问共享值。所有其他进程(客户端)都通过代理与服务器通信,然后与服务器来回传递消息。
服务器确实需要知道对象类型可以接受哪些方法,因此,客户端可以使用相同的方法来生成代理对象。这就是AutoProxy
对象的作用。每当客户端需要您的注册类的新实例时,客户端创建的默认代理就是AutoProxy
,然后它将要求服务器告诉它可以使用哪些方法。
一旦有了方法名称,它将调用MakeProxyType
来构造一个新类,然后为该类创建一个实例以返回。
所有这些都推迟到您实际上需要代理类型的实例为止,因此原则上AutoProxy
如果不使用已注册的某些类,则可以节省一点内存。但是,它的内存很少,缺点是该过程必须在每个客户端过程中进行。
这些代理对象使用引用计数来跟踪服务器何时可以删除规范值。就是AutoProxy
可调用部分中被破坏的部分;当在服务器进程(而不是客户端)中创建代理对象时,将新的参数传递给代理类型以禁用引用计数,但是AutoProxy
类型并未更新以支持此功能。
那么,如何解决这个问题?这是这3个选项:
MakeProxyType()
可调用对象如前所述,AutoProxy
实际上只是一个(通过服务器)获取该类型的公共方法的调用,以及对MakeProxyType()
的调用。注册时,您可以自己拨打这些电话。
所以,而不是
from multiprocessing.managers import SyncManager
SyncManager.register("YourType", YourType)
使用
from multiprocessing.managers import SyncManager, MakeProxyType, public_methods
# arguments: classname, sequence of method names
YourTypeProxy = MakeProxyType("YourType", public_methods(YourType))
SyncManager.register("YourType", YourType, YourTypeProxy)
可以随时在其中插入MakeProxyType()
呼叫。
如果您使用exposed
的{{1}}参数,则应将这些名称传递给SyncManager.register()
:
MakeProxyType
对于所有预注册的类型,您也必须这样做:
# SyncManager.register("YourType", YourType, exposed=("foo", "bar"))
# becomes
YourTypeProxy = MakeProxyType("YourType", ("foo", "bar"))
SyncManager.register("YourType", YourType, YourTypeProxy)
您可以不依靠多处理为您创建代理。您可以自己编写。对于特殊的“托管值”服务器进程,该代理用于所有进程 中,并且代理应来回传递消息。当然,这不是已经注册的类型的选项,但是我在这里提到它是因为对于您自己的类型,这提供了优化的机会。
请注意,对于所有需要返回到“规范”值实例的交互,您应该具有方法,因此您需要使用属性来处理常规属性或添加{{1} },from multiprocessing.managers import SyncManager, AutoProxy, MakeProxyType, public_methods
registry = SyncManager._registry
for typeid, (callable, exposed, method_to_typeid, proxytype) in registry.items():
if proxytype is not AutoProxy:
continue
create_method = hasattr(managers.SyncManager, typeid)
if exposed is None:
exposed = public_methods(callable)
SyncManager.register(
typeid,
callable=callable,
exposed=exposed,
method_to_typeid=method_to_typeid,
proxytype=MakeProxyType(f"{typeid}Proxy", exposed),
create_method=create_method,
)
和__getattr__
方法。
优点是您可以非常精确地控制与服务器进程实际交换数据的方法。在我的特定示例中,我的代理类缓存了不可变的信息(一旦创建对象,这些值就永远不会改变),但是经常使用。其中包括一个标志值,该标志值控制 other 方法是否会执行某些操作,因此代理可以只检查该标志值,如果未设置,则 not 与服务器进程对话。像这样:
__setattr__
由于__delattr__
返回一个class FooProxy(BaseProxy):
# what methods the proxy is allowed to access through calls
_exposed_ = ("__getattribute__", "expensive_method", "spam")
@property
def flag(self):
try:
v = self._flag
except AttributeError:
# ask for the value from the server, "realvalue.flag"
# use __getattribute__ because it's an attribute, not a property
v = self._flag = self._callmethod("__getattribute__", ("flag",))
return flag
def expensive_method(self, *args, **kwargs):
if self.flag: # cached locally!
return self._callmethod("expensive_method", args, kwargs)
def spam(self, *args, **kwargs):
return self._callmethod("spam", args, kwargs
SyncManager.register("Foo", Foo, FooProxy)
子类,因此您可以将该类与自定义子类结合使用,从而省去编写任何仅由MakeProxyType()
组成的方法的需要:
BaseProxy
同样,这不能解决嵌套在其他代理值内的标准类型的问题。
我使用它来修复return self._callmethod(...)
可调用项,当您正在运行已将修复程序应用于源代码的Python版本时,此应该自动避免打补丁:
# a base class with the methods generated for us. The second argument
# doubles as the 'permitted' names, stored as _exposed_
FooProxyBase = MakeProxyType(
"FooProxyBase",
("__getattribute__", "expensive_method", "spam"),
)
class FooProxy(FooProxyBase):
@property
def flag(self):
try:
v = self._flag
except AttributeError:
# ask for the value from the server, "realvalue.flag"
# use __getattribute__ because it's an attribute, not a property
v = self._flag = self._callmethod("__getattribute__", ("flag",))
return flag
def expensive_method(self, *args, **kwargs):
if self.flag: # cached locally!
return self._callmethod("expensive_method", args, kwargs)
def spam(self, *args, **kwargs):
return self._callmethod("spam", args, kwargs
SyncManager.register("Foo", Foo, FooProxy)
导入以上内容,然后调用AutoProxy
函数来修复# Backport of https://github.com/python/cpython/pull/4819
# Improvements to the Manager / proxied shared values code
# broke handling of proxied objects without a custom proxy type,
# as the AutoProxy function was not updated.
#
# This code adds a wrapper to AutoProxy if it is missing the
# new argument.
import logging
from inspect import signature
from functools import wraps
from multiprocessing import managers
logger = logging.getLogger(__name__)
orig_AutoProxy = managers.AutoProxy
@wraps(managers.AutoProxy)
def AutoProxy(*args, incref=True, manager_owned=False, **kwargs):
# Create the autoproxy without the manager_owned flag, then
# update the flag on the generated instance. If the manager_owned flag
# is set, `incref` is disabled, so set it to False here for the same
# result.
autoproxy_incref = False if manager_owned else incref
proxy = orig_AutoProxy(*args, incref=autoproxy_incref, **kwargs)
proxy._owned_by_manager = manager_owned
return proxy
def apply():
if "manager_owned" in signature(managers.AutoProxy).parameters:
return
logger.debug("Patching multiprocessing.managers.AutoProxy to add manager_owned")
managers.AutoProxy = AutoProxy
# re-register any types already registered to SyncManager without a custom
# proxy type, as otherwise these would all be using the old unpatched AutoProxy
SyncManager = managers.SyncManager
registry = managers.SyncManager._registry
for typeid, (callable, exposed, method_to_typeid, proxytype) in registry.items():
if proxytype is not orig_AutoProxy:
continue
create_method = hasattr(managers.SyncManager, typeid)
SyncManager.register(
typeid,
callable=callable,
exposed=exposed,
method_to_typeid=method_to_typeid,
create_method=create_method,
)
。在启动管理器服务器之前 这样做!
答案 1 :(得分:3)
找到临时解决方案here。 我已经设法通过在 multiprocessing \ managers.py 中将所需的关键字添加到AutoProxy的初始化程序来修复它。但是,我不知道这个kwarg是否对任何事情负责。
答案 2 :(得分:1)
Sergey的原始答案要求您编辑多处理源代码,如下所示:
/anaconda3/lib/python3.6/multiprocessing
)。managers.py
manager_owned=True
添加到AutoProxy
函数。def AutoProxy(token, serializer, manager=None, authkey=None,
exposed=None, incref=True):
...
def AutoProxy(token, serializer, manager=None, authkey=None,
exposed=None, incref=True, manager_owned=True):
...
我设法解决了意外关键字参数 TypeError异常,而没有直接编辑multiprocessing的源代码,而是在我使用多处理管理器的地方添加这几行代码:
import multiprocessing
# Backup original AutoProxy function
backup_autoproxy = multiprocessing.managers.AutoProxy
# Defining a new AutoProxy that handles unwanted key argument 'manager_owned'
def redefined_autoproxy(token, serializer, manager=None, authkey=None,
exposed=None, incref=True, manager_owned=True):
# Calling original AutoProxy without the unwanted key argument
return backup_autoproxy(token, serializer, manager, authkey,
exposed, incref)
# Updating AutoProxy definition in multiprocessing.managers package
multiprocessing.managers.AutoProxy = redefined_autoproxy