3

Pyro を使用してリモート ホストで Python コード関数を処理し、結果を取得するコードをセットアップしようとしています。ネーム サーバーを起動した後、リモート ホスト (実際にはまだ localhost 上) で次のコードを実行します。

import Pyro4

class Server(object):
    def evaluate(self, func, args):
        return func(*args)

def main():
    server = Server()
    Pyro4.Daemon.serveSimple(
            {
                server: "server"
            },
            ns=True)

if __name__ == '__main__':
    main()

クライアント側には、このコードがあります。これは、設定しようとしている動作の例です。

import Pyro4

remoteServer = Pyro4.Proxy('PYRONAME:server')

def square(x): 
    return x**2

print remoteServer.evaluate(square, 4)

ただし、このコードでは次の例外が発生します。

/usr/lib/python2.7/site-packages/Pyro4/core.py:155: UserWarning: HMAC_KEY not set,
protocol data may not be secure
warnings.warn("HMAC_KEY not set, protocol data may not be secure")
Traceback (most recent call last):
  File "/home/davide/Projects/rempy/example-api-pyro.py", line 7, in <module>
    print remoteServer.evaluate(square, 4)
  File "/usr/lib/python2.7/site-packages/Pyro4/core.py", line 149, in __call__
    return self.__send(self.__name, args, kwargs)
  File "/usr/lib/python2.7/site-packages/Pyro4/core.py", line 289, in _pyroInvoke
    raise data
AttributeError: 'module' object has no attribute 'square'

関数オブジェクトが正しくピクルされ、リモート ホストの Server インスタンスに送信されているように見えますが、名前空間に問題があります。

どうすればこの問題を解決できますか?

ありがとう

4

1 に答える 1

3

私はあなたの問題を知っていると思います:

関数が定義されているモジュールが呼び出されます

'__main__'

実行中のすべてのバージョンの Python に存在します。

pickle はソース コードを転送しませんが、参照を転送します

__main__.square

したがって、次の 2 つの可能性があります。

ソースを四角くして、次のようにメインモジュールをできるだけ短くします。

# main.py

def square(x): 
   return x**2

import Pyro4
def main():
    remoteServer = Pyro4.Proxy('PYRONAME:server')


    print remoteServer.evaluate(square, 4)

と:

# __main__.py
import main
main.main()

その後、サーバーはファイルからまったく同じモジュールをインポートできます。

または私のコードでモジュールを作成します:

class ThisShallNeverBeCalledError(Exception):
    pass

class _R(object):
    def __init__(self, f, *args):
        self.ret = (f, args)
    def __reduce__(self):
        return self.ret
    def __call__(self, *args):
        raise ThisShallNeverBeCalledError()

    @classmethod
    def fromReduce(cls, value):
        ret = cls(None)
        ret.ret = value
        return ret


def dump_and_load(obj):
    '''pickle and unpickle the object once'''
    s = pickle.dumps(obj)
    return pickle.loads(s)

# this string creates an object of an anonymous type that can
# be called to create an R object or that can be reduced by pickle
# and creates another anonymous type when unpickled
# you may not inherit from this MetaR object because it is not a class
PICKLABLE_R_STRING= "type('MetaR', (object,), " \
                    "       {'__call__' : lambda self, f, *args: "\
                    "          type('PICKLABLE_R', "\
                    "               (object,), "\
                    "               {'__reduce__' : lambda self: (f, args), "\
                    "                '__module__' : 'pickleHelp_', "\
                    "                '__name__'   : 'PICKLABLE_R', "\
                    "                '__call__'   : lambda self: None})(), "\
                    "        '__reduce__' : lambda self: "\
                    "           self(eval, meta_string, "\
                    "                {'meta_string' : meta_string}).__reduce__(), "\
                    "        '__module__' : 'pickleHelp_', "\
                    "        '__name__' : 'R'})()".replace('  ', '')
PICKLABLE_R = _R(eval, PICKLABLE_R_STRING, \
                {'meta_string' : PICKLABLE_R_STRING})
R = dump_and_load(PICKLABLE_R)
del PICKLABLE_R, PICKLABLE_R_STRING

PICKLABLE___builtins__ = R(vars, R(__import__, '__builtin__'))
PICKLABLE_FunctionType = R(type, R(eval, 'lambda:None'))

##R.__module__ = __name__
##R.__name__ = 'PICKLABLE_R'


def packCode(code, globals = {}, add_builtins = True, use_same_globals = False, \
             check_syntax = True, return_value_variable_name = 'obj',
             __name__ = __name__ + '.packCode()'):
    '''return an object that executes code in globals when unpickled
use_same_globals
    if use_same_globals is True all codes sent through
    one pickle connection share the same globals
    by default the dont
return_value_variable_name
    if a variable with the name in return_value_variable_name exists
    in globals after the code execution
    it is returned as result of the pickling operation
    if not None is returned
__name__

'''
    if check_syntax:
        compile(code, '', 'exec')
    # copying locals is important
    # locals is transferred through pickle for all code identical
    # copying it prevents different code from beeing executed in same globals
    if not use_same_globals:
        globals = globals.copy()
    if add_builtins:
        globals['__builtins__'] = PICKLABLE___builtins__
    globals.setdefault('obj', None)
    # get the compilation code
    # do not marshal or unmarshal code objects because the platforms may vary
    code = R(compile, code, __name__, 'exec')
    # the final object that can reduce, dump and load itself
    obj = R(R(getattr, tuple, '__getitem__'), (
            R(R(PICKLABLE_FunctionType, code, globals)),
            R(R(getattr, type(globals), 'get'), globals, \
              returnValueVariableName, None)
            ), -1)
    return obj

次に、これを反対側に送信します。

packCode('''
def square(...):
    ...
''', return_value_variable_name = 'square')

関数は反対側に出てきます。このpython関数を他のサーバー側に転送するためにモジュールコードは必要ありません。

何かうまくいかない場合は、教えてください。

于 2012-05-12T19:27:41.200 に答える