PyLCGDict2 HOWTO

This document is not complete. Text appearing in this colour highlights that which is known to be missing.

Setting up the environment

Creating your own dictionary

Interactive HOWTO

The interactive Python session shown below, describes how Pylcgdict interacts with the library defined in the file PyLCGDict2/src/doc/overview/overview.h. You will need to refer to overview.h to fully understand the output shown.

Input is shown like this:

>>> do_something()

Output is shown like this:

This is the output of do_something()

The Prelude is a prerequisite for each of the subsequent sections, but, otherwise, the sections are completely independent of eachother.

Prelude

Build dictionary ... set up runtime env ... point to instructions

Start Python
] python
Python 2.3.4 (#1, Jun 7 2004, 11:39:13) [GCC 3.2] on linux2 Type "help", "copyright", "credits" or "license" for more information.

Import the PyLCGDict package
>>> import pylcgdict

Load the dictionary we created (on the basis of the header file overview.h):
>>> pylcgdict.loadDictionary('overviewDict')
Loaded dictionary overviewDict

Bind the C++ global namespace to the Python variable g:
>>> g = pylcgdict.Namespace('')

You can use Python's dir function to inspect the namespace:
>>> dir(g)
['Base', 'Derived', 'DynamicBase', 'DynamicDerived', 'Expose', 'MyFirstClass', 'Outer', 'Overload', 'Templates', '_C_metanamespace', '__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', '_add_attribute', 'std']

Pylcgdict provides a more C++ friendly version (pylcgdict.dir). NOTE this is still experimental.
>>> pylcgdict.dir(g)
['Base', 'Derived', 'DynamicBase', 'DynamicDerived', 'Expose', 'MyFirstClass', 'Outer', 'Overload', 'Templates', 'std']

Classes

Instantiating a class looks similar to the way it would be done in C++:
>>> mc = g.MyFirstClass(1)
mc is an object of type pylcgdict.MyFirstClass:
>>> mc
<pylcgdict.MyFirstClass object at 0x4018ee0c>
>>> type(mc)
<class 'pylcgdict.MyFirstClass'>

Note that dir reports the existence of the constructor (MyFirstClass), the field (datum), and the two methods (get_datum, set_datum):
>>> pylcgdict.dir(mc)
['MyFirstClass', 'datum', 'get_datum', 'set_datum']

Methods are called just like they would be in C++:
>>> mc.get_datum()
1
>>> mc.set_datum(2)

Fields are accessed just like they would be in C++:
>>> mc.datum
2
>>> mc.datum = 3
>>> mc.get_datum()
3

Namespaces

Pylcgdict reflects the C++ namespace hierarchy found in the loaded dictionaries. The C++ scope resolution operator (::) maps to Python's attribute access syntax (.). g.Outer is a pylcgdict.Namespace whose name is "Outer":
>>> g.Outer
<pylcgdict.Namespace Outer object at 0x401927ec>

It contains the Inner namespace:
>>> pylcgdict.dir(g.Outer)
['Inner']

... which, in turn, contains AClass
>>> g.Outer.Inner
<pylcgdict.Namespace Inner object at 0x4019296c>
>>> pylcgdict.dir(g.Outer.Inner)
['AClass']
>>> g.Outer.Inner.AClass
<class 'pylcgdict.AClass'>

... and that contains an inner class
>>> pylcgdict.dir(g.Outer.Inner.AClass)
['AClass', 'InnerClass']
>>> g.Outer.Inner.AClass.AClass
<pylcgdict.Constructor object at 0x401920ec>
>>> g.Outer.Inner.AClass.InnerClass
<class 'pylcgdict.InnerClass'>

Templates

The g.Templates namespace contains a template called "Parametrized":
>>> g.Templates.Parametrized
<Template 'Templates::Parametrized<>'>

Store it in a Python variable to save typing:
>>> gTP = g.Templates.Parametrized
>>> gTP
<Template 'Templates::Parametrized<>'>

The instantiations which can be made are shown by dir:
>>> pylcgdict.dir(gTP)
['Parametrized<Outer::Inner::AClass,MyFirstClass>', 'Parametrized<int,double>', 'Parametrized<int,float>']
There is a bug here: the instantiations do to show up in dir until after they have been instantiated at least once.

The angled brackets which enclose template arguments in C++, map to parentheses in Python: Parametrized<int, double> becomes Parametrized(int, double)

The instantiated templates now act as normal classes:
>>> gTP(int, float)
<class 'pylcgdict.Parametrized<int,float>'>
>>> gTP(int, double)
<class 'pylcgdict.Parametrized<int,double>'>
>>> gTP(g.Outer.Inner.AClass, g.MyFirstClass)
<class 'pylcgdict.Parametrized<Outer::Inner::AClass,MyFirstClass>'>
>>> Pif = gTP(int, float)(1, 2.5)
>>> Pif
<pylcgdict.Parametrized<int,float> object at 0x40192e8c>
>>> Pif.get_tea()
1
>>> Pif.get_you()
2.5
>>> type(Pif.get_you())
<type 'float'>

Note that although Python has only one precision of floating point numbers (equivalent to C's double), pylcgdict provides both float and double as symbols for use in template arguments:
>>> gTP(int, float )(1, 0.8).get_you()
0.80000001192092896
>>> gTP(int, double)(1, 0.8).get_you()
0.80000000000000004
Note the loss of precision in the case of float in the above. Even though the result is stored in a Python (double precision) float in both cases, in the first case the underlying C++ class used single precision.

# FIXME: give example of template argument without dictionary entry. (Need to faff around with selection file.)

Templates can be nested:
>>> g.Templates.Nestme(g.Templates.Parametrized(g.Outer.Inner.AClass, g.MyFirstClass))()
<pylcgdict.Nestme<Templates::Parametrized<Outer::Inner::AClass,MyFirstClass> > object at 0x40196bec>


Sometimes the template parameter is a type for which no dictionary is available. In such cases you can pass the type name as a string: ... to be done ... FIXME

Overloaded methods

>>> ov = g.Overload()

You can use Python's help to see the signatures of methods. In this case we can see that ov.overloaded names six overloaded methods:
>>> help(ov.overloaded)
Help on method dispatching_method in module pylcgdict:

dispatching_method(self, *args) method of pylcgdict.Overload instance
    std::basic_string  ()
    std::basic_string  (int)
    std::basic_string  (double)
    std::basic_string  (MyFirstClass)
    std::basic_string  (int, int)
    std::basic_string  (int, MyFirstClass)
On some platforms you will need to type q to return from help back into the interactive Python shell.

The overloaded methods can be called just like they would be called in C++:
>>> ov.overloaded()
'void'
>>> ov.overloaded(1)
'int'
>>> ov.overloaded(1.0)
'double'
>>> ov.overloaded(g.MyFirstClass())
'MyFirstClass'
>>> ov.overloaded(1, 1)
'int, int'
>>> ov.overloaded(1, g.MyFirstClass())
'int, MyFirstClass'

If the arguments passed do not match any of the signatures, then a pylcgdict.NoMatchingMethod exception is raised:
>>> ov.overloaded(1, 1, 1)
Traceback (most recent call last): <... details of traceback elided ...> pylcgdict.NoMatchingMethod: Overload::overloaded(<type 'int'>, <type 'int'>, <type 'int'>) Candidates: () (int) (double) (MyFirstClass) (int, int) (int, MyFirstClass) NoMatchingMethod is a subclass of TypeError.

Static methods

Sequence protocol.

Iteration.

Which types map to which.

Inheritance

Pylcgdict reflects the class hierarchies it finds in the original library:
>>> g.Derived.__bases__
(<class 'pylcgdict.Base'>,)
>>> isinstance(g.Derived(), g.Base)
True

Methods (and data) are inherited and overridden as one expects in C++:
>>> b = g.Base()
>>> d = g.Derived()
>>> b.whoami()
'Base'
>>> d.whoami()
'Derived'
>>> b.inheritme()
'This was defined in Base'
>>> d.inheritme()
'This was defined in Base'

Pointers, values and references

There are no pointers in Python, and, ideally users should be protected from ever having to handle a raw pointer. Unfortunately very many C++ library interfaces do expose pointers, and sometimes it is impossible to avoid handling them in Python. Pylcgdict has pointer types which try to behave, as much as possible, as the underlying type referenced by the pointer.

>>> e1 = g.Expose('one')
>>> e2 = g.Expose('two')
>>> type(e1)
<class 'pylcgdict.Expose'>
>>> e1.passvalue(e2)
'two'
>>> e1.passreference(e2)
'two'

Pylcgdict considers an instance of a type to be a valid argument even if the pointer to the type is expected:
>>> e1.passpointer(e2)
'two'

Sometimes, however, the interface really requires a pointer. Pylcgdict provides a Pointer metatype, which can create pointer types, or pointers to existing instances. Create the Python proxy for the type Expose*:
>>> pylcgdict.Pointer(g.Expose)
<class 'pylcgdict.Pointer(Expose)'>

Make a NULL pointer of that type:
>>> epNULL = pylcgdict.Pointer(g.Expose)(0)
>>> epNULL
<pylcgdict.Pointer(Expose) object at 0x40322dec>

Make a pointer to an existing object:
>>> ep2 = pylcgdict.Pointer(g.Expose)(e2)
>>> ep2
<pylcgdict.Pointer(Expose) object at 0x403262cc>

Such pointers can be passed to functions expecting Expose*:
>>> e1.passpointer(epNULL)
'NULL pointer'
>>> e1.passpointer(ep2)
'two'

... and they can also be passed to functions expecting Expose values:
>>> e1.passvalue(ep2)
'two'
>>> e1.passreference(ep2)
'two'

But you do have to be careful with NULL pointers: e1.passvalue(epNULL) gives a Segmentation fault

The pointer types try to behave as the underlying type whenever possible:
>>> ep2.passvalue(e1)
'one'

Note that the above is equivalent to ep2->passvalue(e1) in C++. Consequently the C++ dereference operator "->" is mapped to Python's attribute access syntax "." (This has not been implemented yet: FIXME).

Raw pointers are made unavoidable by interfaces which require pointers as output parameters.
>>> ep3 = pylcgdict.Pointer(g.Expose)(0)

Be careful! this is a NULL pointer: at this point ep3.whoami() gives a segmentation fault. But this pointer can be readjusted when passed as an output parameter:
>>> e1.outputparameter('three', ep3)
... making the pointer point to a valid object, and the same operation is now safe:
>>> ep3.whoami()
'three'

Objects returned from methods always appear as values, regardless of whether the formal return type is a value, reference or pointer:
>>> type(e1.getvalue())
<class 'pylcgdict.Expose'>
>>> type(e1.getreference())
<class 'pylcgdict.Expose'>
>>> type(e1.getpointer())
<class 'pylcgdict.Expose'>

pylcgdict.Deref

Automatic Dynamic Casting

In C++ the actual (dynamic) type of an object may differ from its formal (static) type. It is up to the programmer to discover the true dynamic type by guessing it, performing a dynamic_cast and checking the validity of the resulting pointer. Such low-level details are none of the programmer's concern in Python, so Pylcgdict downcasts all returned values to their true dynamic type.

>>> dd = g.DynamicDerived()
>>> type(dd.getBase())
<class 'pylcgdict.DynamicBase'>
>>> type(dd.getDerived())
<class 'pylcgdict.DynamicDerived'>

If, however, the dynamic type of an existing Python proxy object is changed as follows:
>>> db = g.DynamicBase()
>>> dd.modify(1, db),

such changes are too subtle for Pylcgdict to handle efficiently, and it does not perform a type change automatically:
>>> type(db)
<class 'pylcgdict.DynamicBase'>

In such cases the conversion to the new dynamic type must be requested explicitly:
>>> pylcgdict.dynamic_downcast(db)
>>> type(db)
<class 'pylcgdict.Pointer(DynamicDerived)'>
NOTE ... the dynamic casting interface and behaviour will change (improve!) in the future.

Subclassing C++ classes in Python: callbacks
Jacek Generowicz
Last modified: Fri Jun 18 18:26:03 CEST 2004