У меня есть класс, который я хочу предоставить как удаленную службу с использованием pythons SimpleXMLRPCServer. Запуск сервера выглядит так:
server = SimpleXMLRPCServer((serverSettings.LISTEN_IP,serverSettings.LISTEN_PORT))
service = Service()
server.register_instance(service)
server.serve_forever()
Затем у меня есть класс ServiceRemote, который выглядит так:
def __init__(self,ip,port):
self.rpcClient = xmlrpclib.Server('http://%s:%d' %(ip,port))
def __getattr__(self, name):
# forward all calls to the rpc client
return getattr(self.rpcClient, name)
Таким образом, все вызовы объекта ServiceRemote будут перенаправляться на xmlrpclib.Server, который затем перенаправляет его на удаленный сервер. Проблема заключается в методе службы, который принимает именованные varargs:
@useDb
def select(self, db, fields, **kwargs):
pass
Декоратор @useDb обертывает функцию, создавая базу данных перед вызовом и открывая ее, а затем закрывая ее после завершения вызова перед возвратом результата.
Когда я вызываю этот метод, я получаю сообщение об ошибке «вызов () получил неожиданный аргумент ключевого слова 'name'». Итак, можно ли удаленно вызывать методы, принимающие аргументы с переменными именами? Или мне придется создавать переопределение для каждого варианта метода, который мне нужен.
Спасибо за ответы. Я немного изменил свой код, поэтому вопрос больше не является проблемой. Однако теперь я знаю это на будущее, если мне действительно нужно реализовать позиционные аргументы и поддерживать удаленный вызов. Я думаю, что комбинация подходов Томаса и праптаков была бы хорошей. Превращение kwargs в позиционные аргументы на клиенте через xmlrpclient и наличие оболочки для методов на стороне сервера для распаковки позиционных аргументов.






Насколько мне известно, базовый протокол не поддерживает именованные varargs (или любые именованные аргументы, если на то пошло). Обходной путь для этого - создать оболочку, которая будет принимать ** kwargs и передавать ее как обычный словарь методу, который вы хотите вызвать. Что-то вроде этого
Сторона сервера:
def select_wrapper(self, db, fields, kwargs):
"""accepts an ordinary dict which can pass through xmlrpc"""
return select(self,db,fields, **kwargs)
На стороне клиента:
def select(self, db, fields, **kwargs):
"""you can call it with keyword arguments and they will be packed into a dict"""
return self.rpcClient.select_wrapper(self,db,fields,kwargs)
Отказ от ответственности: код отражает общую идею, вы можете сделать это немного чище (например, написать для этого декоратор).
XML-RPC действительно не имеет концепции «аргументов ключевого слова», поэтому xmlrpclib не пытается их поддерживать. Вам нужно будет выбрать соглашение, а затем изменить xmlrpclib._Method, чтобы он принимал аргументы ключевого слова и передавал их вместе с этим соглашением.
Например, я работал с сервером XML-RPC, который передавал аргументы ключевого слова в виде двух аргументов, «-KEYWORD», за которым следует фактический аргумент, в плоском списке. У меня больше нет доступа к коду, который я написал для доступа к этому серверу XML-RPC из Python, но это было довольно просто, например:
import xmlrpclib
_orig_Method = xmlrpclib._Method
class KeywordArgMethod(_orig_Method):
def __call__(self, *args, **kwargs):
if args and kwargs:
raise TypeError, "Can't pass both positional and keyword args"
args = list(args)
for key in kwargs:
args.append('-%s' % key.upper())
args.append(kwargs[key])
return _orig_Method.__call__(self, *args)
xmlrpclib._Method = KeywordArgMethod
Он использует monkeypatching, потому что это, безусловно, самый простой способ сделать это из-за некоторого неуклюжего использования глобальных переменных модуля и атрибутов с измененными именами (например, __request) в классе ServerProxy.
monkeypatching - это изменение модуля или другого фрагмента кода без изменения источника этого кода. См. en.wikipedia.org/wiki/Monkey_patch
отстрел обезьян часто считается вредным
Это действительно так. Однако попробуйте заменить класс _Method, используемый xmlrpclib без него. Пожалуйста :-) Вы увидите, что есть более вредные вещи, чем обезьяньи патчи.
@ Томас, я думаю, что это так, что в этом такого вредного?
Что вредно, так это то, что класс ServerProxy использует глобальный _Method напрямую (нет способа отменить этот выбор), и единственный способ изменить это - создать подкласс и переопределить getattr. Но getattr нуждается в доступе к запросу self .__, которого он не может из-за искажения имени. Как ты сделал это?
Хорошее решение. Незначительный момент: нет необходимости ограничивать передачу только одного из аргументов позиции и ключевого слова. В итоге я отправил такие аргументы: ("hello", "world", "** kwargs", "text", "Browser"), что является не чем иным, как ("hello", "world", text = "Browser "). Сервер автоматически переводит это. Если кто-то желает сделать патч обезьяны, он может поступить так, как указал Томас Воутерс, или просто назвать это расширенным способом.
Как сказал Томас Воутерс, XML-RPC не имеет аргументов ключевого слова. С точки зрения протокола имеет значение только порядок аргументов, и в XML они могут называться как угодно: arg0, arg1, arg2 прекрасно подходят, как сыр, конфеты и бекон для тех же аргументов.
Возможно, вам стоит просто переосмыслить использование протокола? Использование чего-то вроде document / literal SOAP было бы намного лучше, чем обходной путь, например, представленный в других ответах здесь. Конечно, это может оказаться невозможным.
Используя приведенный выше совет, я создал рабочий код.
Оболочка серверного метода:
def unwrap_kwargs(func):
def wrapper(*args, **kwargs):
print args
if args and isinstance(args[-1], list) and len(args[-1]) == 2 and "kwargs" == args[-1][0]:
func(*args[:-1], **args[-1][1])
else:
func(*args, **kwargs)
return wrapper
Настройка клиента (сделать один раз):
_orig_Method = xmlrpclib._Method
class KeywordArgMethod(_orig_Method):
def __call__(self, *args, **kwargs):
args = list(args)
if kwargs:
args.append(("kwargs", kwargs))
return _orig_Method.__call__(self, *args)
xmlrpclib._Method = KeywordArgMethod
Я протестировал это, и он поддерживает метод с фиксированными, позиционными и ключевыми аргументами.
Мне не нравится код, который вторгается в другие модули. Я стараюсь полностью обойти другой код, который более гибкий, менее загадочный и с меньшей вероятностью взорвется вам в лицо.
Я согласен, но все еще новичок в питоне, поэтому просто пробую путь наименьшего сопротивления. А так все в одном проекте банкомат. это не такая уж большая проблема.
Вы не можете сделать это с помощью простого xmlrpc, поскольку он не имеет понятия аргументов ключевого слова. Однако вы можете наложить это как протокол поверх xmlrpc, который всегда будет передавать список в качестве первого аргумента и словарь в качестве второго, а затем предоставить правильный код поддержки, чтобы он стал прозрачным для вашего использования, пример ниже:
from SimpleXMLRPCServer import SimpleXMLRPCServer
class Server(object):
def __init__(self, hostport):
self.server = SimpleXMLRPCServer(hostport)
def register_function(self, function, name=None):
def _function(args, kwargs):
return function(*args, **kwargs)
_function.__name__ = function.__name__
self.server.register_function(_function, name)
def serve_forever(self):
self.server.serve_forever()
#example usage
server = Server(('localhost', 8000))
def test(arg1, arg2):
print 'arg1: %s arg2: %s' % (arg1, arg2)
return 0
server.register_function(test)
server.serve_forever()
import xmlrpclib
class ServerProxy(object):
def __init__(self, url):
self._xmlrpc_server_proxy = xmlrpclib.ServerProxy(url)
def __getattr__(self, name):
call_proxy = getattr(self._xmlrpc_server_proxy, name)
def _call(*args, **kwargs):
return call_proxy(args, kwargs)
return _call
#example usage
server = ServerProxy('http://localhost:8000')
server.test(1, 2)
server.test(arg2=2, arg1=1)
server.test(1, arg2=2)
server.test(*[1,2])
server.test(**{'arg1':1, 'arg2':2})
Идея хороша, но ничего не работает, если существует более одного уровня вызовов API (например, server.level.test ()). В таких случаях функция getattr не будет знать, не является ли «уровень» вызовом функции, и также вернет прокси-функцию для этого, что не сработает. Но это хорошая идея и в правильном направлении! В итоге я использовал это решение вместе с модификацией ответа Томаса Воутерса ниже. Спасибо!
Что такое патч обезьяны? Быстрый поиск в Google не дал мне определения, только пример с использованием декораторов.