Python Mapping for Classes

On this page:

Basic Python Mapping for Classes

A Slice class maps to a Python class with the same name. The generated class contains an attribute for each Slice data member (just as for structures and exceptions). Consider the following class definition:

Slice
class TimeOfDay
{
    short hour;         // 0 - 23
    short minute;       // 0 - 59
    short second;       // 0 - 59
}

The Python mapping generates the following code for this definition:

Python
class TimeOfDay(Ice.Value):
    def __init__(self, hour=0, minute=0, second=0):
        # ...
        self.hour = hour
        self.minute = minute
        self.second = second

    def ice_id(self):
		return '::M::TimeOfDay'
 
    @staticmethod
    def ice_staticId():
        return '::M::TimeOfDay'

    # ...

There are a number of things to note about the generated code:

  1. The generated class TimeOfDay inherits from Ice.Value. This means that all classes implicitly inherit from Ice.Value, which is the ultimate ancestor of all classes. Note that Ice.Value is not the same as Ice.ObjectPrx. In other words, you cannot pass a class where a proxy is expected and vice versa.
  2. The constructor defines an attribute for each Slice data member.
  3. The class defines the method ice_id and static method ice_staticId.

There is quite a bit to discuss here, so we will look at each item in turn.

 

Inheritance from Ice.Value in Python

Like interfaces, classes implicitly inherit from a common base class, Ice.Value. However classes inherit from Ice.Value instead of Ice.ObjectPrx (which is at the base of the inheritance hierarchy for proxies). As a result, you cannot pass a class where a proxy is expected (and vice versa) because the base types for classes and proxies are not compatible.

Ice.Value contains a number of methods:

Python
class Value(object):
    def ice_id(self):
        # ...

	@staticmethod
    def ice_staticId():
        # ...

    def ice_preMarshal(self):
        # ...

    def ice_postUnmarshal(self):
        # ...

	def ice_getSlicedData(self):
        # ...

The member functions of Ice.Value behave as follows:

  • ice_id
    This method returns the actual run-time type ID of the object. If you call ice_id through a reference to a base instance, the returned type ID is the actual (possibly more derived) type ID of the instance.
  • ice_staticId
    This method is generated in each class and returns the static type ID of the class.
  • ice_preMarshal
    The Ice run time invokes this method prior to marshaling the object's state, providing the opportunity for a subclass to validate its declared data members.
  • ice_postUnmarshal
    The Ice run time invokes this method after unmarshaling an object's state. A subclass typically overrides this function when it needs to perform additional initialization using the values of its declared data members.
  • ice_getSlicedData
    This functions returns the SlicedData object if the value has been sliced during un-marshaling or None otherwise.

Note that neither Ice.Value nor the generated class override __hash__ and __eq__, so the default implementations apply.

Class Data Members in Python

By default, data members of classes are mapped exactly as for structures and exceptions: for each data member in the Slice definition, the generated class contains a corresponding attribute.

Optional data members use the same mapping as required data members, but an optional data member can also be set to the marker value Ice.Unset to indicate that the member is unset. A well-behaved program must test an optional data member before using its value:

Python
obj = ...
if obj.optionalMember:
    print("optionalMember = " + str(ex.optionalMember))
else:
    print("optionalMember is unset")

The Ice.Unset marker value has different semantics than None. Since None is a legal value for certain Slice types, the Ice run time requires a separate marker value so that it can determine whether an optional value is set. An optional value set to None is considered to be set. If you need to distinguish between an unset value and a value set to None, you can do so as follows:

Python
obj = ...
if obj.optionalMember is Ice.Unset:
    print("optionalMember is unset")
elif obj.optionalMember is None:
    print("optionalMember is None")
else:
    print("optionalMember = " + str(ex.optionalMember))

Although Python provides no standard mechanism for restricting access to an object's attributes, by convention an attribute whose name begins with an underscore signals the author's intent that the attribute should only be accessed by the class itself or by one of its subclasses. You can employ this convention in your Slice classes using the protected metadata directive. The presence of this directive causes the Slice compiler to prepend an underscore to the mapped name of the data member. For example, the TimeOfDay class shown below has the protected metadata directive applied to each of its data members:

Slice
class TimeOfDay
{
    ["protected"] short hour;   // 0 - 23
    ["protected"] short minute; // 0 - 59
    ["protected"] short second; // 0 - 59
}

The Slice compiler produces the following generated code for this definition:

Python
class TimeOfDay(Ice.Value):
    def __init__(self, hour=0, minute=0, second=0):
        # ...
        self._hour = hour
        self._minute = minute
        self._second = second

    # ...

For a class in which all of the data members are protected, the metadata directive can be applied to the class itself rather than to each member individually. For example, we can rewrite the TimeOfDay class as follows:

Slice
["protected"] class TimeOfDay
{
    short hour;         // 0 - 23
    short minute;       // 0 - 59
    short second;       // 0 - 59
}

Class Constructors in Python

Classes have a constructor that assigns to each data member a default value appropriate for its type:

Data Member TypeDefault Value
stringEmpty string
enumFirst enumerator in enumeration
structDefault-constructed value
NumericZero
boolFalse
sequenceNone
dictionaryNone
class/interfaceNone

You can also declare different default values for data members of primitive and enumerated types.

For derived classes, the constructor has one parameter for each of the base class's data members, plus one parameter for each of the derived class's data members, in base-to-derived order.

You can invoke this constructor in one of two ways:

  • Provide values for all members, including optional members, in the order of declaration:

    Python
    t = TimeOfDay(12, 33, 45)
    t2 = TimeOfDay(14, 7) # second defaults to 0
    

    Pass Ice.Unset as the value of any optional member you want to be unset.

  • Used named arguments to specify values for certain members and in any order:

    Python
    t = TimeOfDay(minute=33, hour=12)
    

Class Operations in Python

Deprecated Feature

Operations on classes are deprecated as of Ice 3.7. Skip this section unless you need to communicate with old applications that rely on this feature.

With the Python mapping, operations in classes are not mapped at all into the corresponding Python class. The generated Python class is the same whether the Slice class has operations or not.

The Slice to Python compiler also generates a separate <class-name>Disp class, which can be used to implement an Ice object with these operations. For example:

Slice
class FormattedTimeOfDay
{
    short hour;         // 0 - 23
    short minute;       // 0 - 59
    short second;       // 0 - 59
    string tz;
    string format();
}

results in the following generated code:

Python
class FormattedTimeOfDay(Ice.Value):
     # ... operation format() not mapped at all here

# Disp class for servant implementation
class FormattedTimeOfDayDisp(Ice.Object):
     # ...

The Disp class is the Python skeleton class for this Slice class. Skeleton classes are described in the Server-Side Python Mapping for Interfaces.

Value Factories in Python

While value factories were previously necessary when using classes with operations (a now deprecated feature), value factories may be used for any kind of class and are not deprecated.

Value factories allow you to create classes derived from the Python class generated by the Slice compiler, and tell the Ice run time to create instances of these classes when unmarshaling. For example, with the following simple interface:

Slice
interface Time
{
    TimeOfDay get();
}

The Ice run time will by default create and return a plain TimeOfDay instance.

If you wish, you can create your own custom derived class, and tell Ice to create and return these instances instead. For example:

Python
class CustomTimeOfDay(TimeOfDay):
    def format(self):
       # prints formatted data members
       pass

You then create and register a value factory for your custom class with your Ice communicator:

Python
def ValueFactory(Ice.ValueFactory):
    if type == TimeOfDay.ice_staticId():
        return TimeOfDayI()
    assert(False)
    return None
 
communicator = ...
communicator.getValueFactoryManager().add(ValueFactory(), TimeOfDay.ice_staticId()))


See Also