As we saw in Section 18.11, for each operation on an interface, the proxy class contains a corresponding method with the same name. To invoke an operation, you call it via the proxy. For example, here is part of the definitions for our file system from
Section 5.4:
module Filesystem {
interface Node {
idempotent string name();
};
// ...
};
The name operation returns a value of type
string. Given a proxy to an object of type
Node, the client can invoke the operation as follows:
node = ... # Initialize proxy
name = node.name() # Get name via RPC
18.12.1 Normal and idempotent Operations
You can add an idempotent qualifier to a Slice operation. As far as the signature for the corresponding proxy method is concerned,
idempotent has no effect. For example, consider the following interface:
interface Example {
string op1();
idempotent string op2();
};
class ExamplePrx(Ice.ObjectPrx):
def op1(self, _ctx=None):
# ...
def op2(self, _ctx=None):
# ...
Because idempotent affects an aspect of call dispatch, not interface, it makes sense for the two methods to look the same.
struct NumberAndString {
int x;
string str;
};
sequence<string> StringSeq;
dictionary<long, StringSeq> StringTable;
interface ClientToServer {
void op1(int i, float f, bool b, string s);
void op2(NumberAndString ns, StringSeq ss, StringTable st);
void op3(ClientToServer* proxy);
};
class ClientToServerPrx(Ice.ObjectPrx):
def op1(self, i, f, b, s, _ctx=None):
# ...
def op2(self, ns, ss, st, _ctx=None):
# ...
def op3(self, proxy, _ctx=None):
# ...
Given a proxy to a ClientToServer interface, the client code can pass parameters as in the following example:
p = ... # Get proxy...
p.op1(42, 3.14f, True, "Hello world!") # Pass simple literals
i = 42
f = 3.14f
b = True
s = "Hello world!"
p.op1(i, f, b, s) # Pass simple variables
ns = NumberAndString()
ns.x = 42
ns.str = "The Answer"
ss = [ "Hello world!" ]
st = {}
st[0] = ns
p.op2(ns, ss, st) # Pass complex variables
p.op3(p) # Pass proxy
As in Java, Python functions do not support reference arguments. That is, it is not possible to pass an uninitialized variable to a Python function in order to have its value initialized by the function. The Java mapping (see
Section 10.12.2) overcomes this limitation with the use of "holder classes" that represent each
out parameter. The Python mapping takes a different approach, one that is more natural for Python users.
The semantics of out parameters in the Python mapping depend on whether the operation returns one value or multiple values. An operation returns multiple values when it has declared multiple
out parameters, or when it has declared a non-
void return type and at least one
out parameter.
If an operation returns multiple values, the client receives them in the form of a
result tuple. A non-
void return value, if any, is always the first element in the result tuple, followed by the
out parameters in the order of declaration.
Here is the same Slice definition we saw on page 517 once more, but this time with all parameters being passed in the
out direction:
struct NumberAndString {
int x;
string str;
};
sequence<string> StringSeq;
dictionary<long, StringSeq> StringTable;
interface ServerToClient {
int op1(out float f, out bool b, out string s);
void op2(out NumberAndString ns,
out StringSeq ss,
out StringTable st);
void op3(out ServerToClient* proxy);
};
class ServerToClientPrx(Ice.ObjectPrx):
def op1(self, _ctx=None):
# ...
def op2(self, _ctx=None):
# ...
def op3(self, _ctx=None):
# ...
Given a proxy to a ServerToClient interface, the client code can receive the results as in the following example:
p = ... # Get proxy...
i, f, b, s = p.op1()
ns, ss, st = p.op2()
stcp = p.op3()
The operations have no in parameters, therefore no arguments are passed to the proxy methods. Since
op1 and
op2 return multiple values, their result tuples are unpacked into separate values, whereas the return value of
op3 requires no unpacking.
Although the Python compiler cannot check the types of arguments passed to a function, the Ice run time does perform validation on the arguments to a proxy invocation and reports any type mismatches as a
ValueError exception.
Some Slice types naturally have "empty" or "not there" semantics. Specifically, sequences, dictionaries, and strings all can be
None, but the corresponding Slice types do not have the concept of a null value. To make life with these types easier, whenever you pass
None as a parameter or return value of type sequence, dictionary, or string, the Ice run time automatically sends an empty sequence, dictionary, or string to the receiver.
This behavior is useful as a convenience feature: especially for deeply-nested data types, members that are sequences, dictionaries, or strings automatically arrive as an empty value at the receiving end. This saves you having to explicitly initialize, for example, every string element in a large sequence before sending the sequence in order to avoid a run-time error. Note that using null parameters in this way does
not create null semantics for Slice sequences, dictionaries, or strings. As far as the object model is concerned, these do not exist (only
empty sequences, dictionaries, and strings do). For example, it makes no difference to the receiver whether you send a string as
None or as an empty string: either way, the receiver sees an empty string.