Table of Contents Previous Next
Logo
Client-Side Slice-to-Python Mapping : 22.7 Mapping for User-Defined Types
Copyright © 2003-2010 ZeroC, Inc.

22.7 Mapping for User-Defined Types

Slice supports user-defined types: enumerations, structures, sequences, and dictio­naries.

22.7.1 Mapping for Enumerations

Python does not have an enumerated type, so the Slice enumerations are emulated using a Python class: the name of the Slice enumeration becomes the name of the Python class; for each enumerator, the class contains an attribute with the same name as the enumerator. For example:
enum Fruit { Apple, Pear, Orange };
The generated Python class looks as follows:
class Fruit(object):
    def __init__(self, val):
        assert(val >= 0 and val < 3)
        self.value = val

    # ...

Fruit.Apple = Fruit(0)
Fruit.Pear = Fruit(1)
Fruit.Orange = Fruit(2)
Each instance of the class has a value attribute providing the integer value of the enumerator. Note that the generated class also defines a number of Python special methods, such as __str__ and __cmp__, which we have not shown.
Given the above definitions, we can use enumerated values as follows:
f1 = Fruit.Apple
f2 = Fruit.Orange

if f1 == Fruit.Apple:                # Compare with constant
    # ...

if f1 == f2:                         # Compare two enums
    # ...

if f2.value == Fruit.Apple.value:    # Use integer values
    # ...
elif f2.value == Fruit.Pear.value:
    # ...
elif f2.value == Fruit.Orange.value:
    # ...
As you can see, the generated class enables natural use of enumerated values. The Fruit class attributes are preinitialized enumerators that you can use for initial­ization and comparison. You may also instantiate an enumerator explicitly by passing its integer value to the constructor, but you must make sure that the passed value is within the range of the enumeration; failure to do so will result in an assertion failure:
favoriteFruit = Fruit(4) # Assertion failure!

22.7.2 Mapping for Structures

Slice structures map to Python classes with the same name. For each Slice data member, the Python class contains a corresponding attribute. For example, here is our Employee structure from Section 4.9.4 once more:
struct Employee {
    long number;
    string firstName;
    string lastName;
};
The Python mapping generates the following definition for this structure:
class Employee(object):
    def __init__(self, number=0, firstName='', lastName=''):
        self.number = number
        self.firstName = firstName
        self.lastName = lastName

    def __hash__(self):
        # ...

    def __eq__(self, other):
        # ...

    def __str__(self):
        # ...
The constructor initializes each of the attributes to a default value appropriate for its type. You can also declare different default values for members of primitive and enumerated types, as discussed in Section 4.9.2.
The __hash__ method returns a hash value for the structure based on the value of all its data members.
The __eq__ method returns true if all members of two structures are (recur­sively) equal.
The __str__ method returns a string representation of the structure.

22.7.3 Mapping for Sequences

Slice sequences map by default to Python lists; the only exception is a sequence of bytes, which maps by default to a string in order to lower memory utilization and improve throughput. This use of native types means that the Python mapping does not generate a separate named type for a Slice sequence. It also means that you can take advantage of all the inherent functionality offered by Python’s native types. For example:
sequence<Fruit> FruitPlatter;
We can use the FruitPlatter sequence as shown below:
platter = [ Fruit.Apple, Fruit.Pear ]
assert(len(platter) == 2)
platter.append(Fruit.Orange)
The Ice run time validates the elements of a tuple or list to ensure that they are compatible with the declared type; a ValueError exception is raised if an incompatible type is encountered.

Allowable Sequence Values

Although each sequence type has a default mapping, the Ice run time allows a sender to use other types as well. Specifically, a tuple is also accepted for a sequence type that maps to a list, and in the case of a byte sequence, the sender is allowed to supply a tuple or list of integers as an alternative to a string1.
Furthermore, the Ice run time accepts objects that implement Python’s buffer protocol as legal values for sequences of all primitive types except strings. For example, you can use the array module to create a buffer that is transferred much more efficiently than a tuple or list. Consider the two sequence values in the sample code below:
import array
...
seq1 = array.array("i", [1, 2, 3, 4, 5])
seq2 = [1, 2, 3, 4, 5]
The values have the same on-the-wire representation, but they differ greatly in marshaling overhead because the buffer can be traversed more quickly and requires no validation.
Note that the Ice run time has no way of knowing what type of elements a buffer contains, therefore it is the application’s responsibility to ensure that a buffer is compatible with the declared sequence type.

Customizing the Sequence Mapping

The previous section described the allowable types that an application may use when sending a sequence. That kind of flexibility is not possible when receiving a sequence, because in this case it is the Ice run time’s responsibility to create the container that holds the sequence.
As stated earlier, the default mapping for most sequence types is a list, and for byte sequences the default mapping is a string. Unless otherwise indicated, an application always receives sequences as the container type specified by the default mapping. If it would be more convenient to receive a sequence as a different type, you can customize the mapping by annotating your Slice defini­tions with metadata. Table 22.2 describes the metadata directives supported by the Python mapping.
A metadata directive may be specified when defining a sequence, or when a sequence is used as a parameter, return value or data member. If specified at the point of definition, the directive affects all occurrences of that sequence type unless overridden by another directive at a point of use. The following Slice defi­nitions illustrate these points:
sequence<int> IntList; // Uses list by default
["python:seq:tuple"] sequence<int> IntTuple; // Defaults to tuple

sequence<byte> ByteString; // Uses string by default
["python:seq:list"] sequence<byte> ByteList; // Defaults to list

struct S {
    IntList i1; // list
    IntTuple i2; // tuple
    ["python:seq:tuple"] IntList i3; // tuple
    ["python:seq:list"] IntTuple i4; // list
    ["python:seq:default"] IntTuple i5; // list

    ByteString b1; // string
    ByteList b2; // list
    ["python:seq:list"] ByteString b3; // list
    ["python:seq:tuple"] ByteString b4; // tuple
    ["python:seq:default"] ByteList b5; // string
};

interface I {
    IntList op1(ByteString s1, out ByteList s2);

    ["python:seq:tuple"]
    IntList op2(["python:seq:list"] ByteString s1,
                ["python:seq:tuple"] out ByteList s2);
};
The operation op2 and the data members of structure S demonstrate how to over­ride the mapping for a sequence at the point of use.
It is important to remember that these metadata directives only affect the receiver of the sequence. For example, the data members of structure S are popu­lated with the specified sequence types only when the Ice run time unmarshals an instance of S. In the case of an operation, custom metadata affects the client when specified for the operation’s return type and output parameters, whereas metadata affects the server for input parameters.

22.7.4 Mapping for Dictionaries

Here is the definition of our EmployeeMap from Section 4.9.4 once more:
dictionary<long, Employee> EmployeeMap;
As for sequences, the Python mapping does not create a separate named type for this definition. Instead, all dictionaries are simply instances of Python’s dictionary type. For example:
em = {}

e = Employee()
e.number = 31
e.firstName = "James"
e.lastName = "Gosling"

em[e.number] = e
The Ice run time validates the elements of a dictionary to ensure that they are compatible with the declared type; a ValueError exception is raised if an incompatible type is encountered.

1
Using a string for a byte sequence bypasses the validation step and avoids an extra copy, resulting in much greater throughput than a tuple or list. For larger byte sequences, the use of a string is strongly recommended.


Table of Contents Previous Next
Logo