dbus-python-in-C and Variants
Simon McVittie
simon.mcvittie at collabora.co.uk
Thu Oct 5 06:08:41 PDT 2006
A follow-up mail about variants, as promised. I'll answer your questions
first, and describe why I implemented it like this, and some
alternatives, afterwards.
On Mon, 02 Oct 2006 at 18:08:00 -0400, John (J5) Palmieri wrote:
> On Wed, 2006-09-27 at 15:54 +0100, Simon McVittie wrote:
> > * Variants arrive as method parameters, returns from proxy methods, etc.
> > as Variant objects. This is a bit of a big change, but makes things
> > completely unambiguous. To unwrap variants like the old Pyrex
> > implementation did, you can write::
> >
> > while isinstance(myparam, dbus.Variant):
> > myparam = myparam.object
> >
> > This should also work (and do nothing) under the Pyrex implementation.
>
> I'm a little worried about this. Python is a dynamic language.
> Everything is a variant (it was the purpose of variants in the first
> place). If signatures are not specified by the service we assume
> variant. I don't know if strongly typing python here is valuable. Can
> I use myparam as if it were it's contained type? For instance if the
> myparam variant held a dbus.Dictionary would print myparam.keys() work?
> More importantly would isinstance(myparam, dict) work?
No, if you want do do that, you need to unwrap the variant, as I
described.
myparam.keys() does not currently work, but could be made to if there is
a compelling reason. I'm a bit reluctant to add too much functionality
though, because eventually I'll have to stop emulating, and a Python
programmer encountering the point where I stopped will continue to be
surprised.
isinstance(myparam, dict) does not return True, and, in any
implementation resembling the current one, never will. Variant is a type
in its own right, subclassing only __builtin__.object.
> > * Variants are now immutable "value objects".
>
> > * Variants are somewhat more useful: they can be cast using int(),
> > str(), long(), float(), you can iterate over them with iter() if they
> > contain an iterable, you can compare them with ``==`` and ``!=``,
> > and you can hash them if the underlying object supports it.
>
> Ok, I guess that answers my myparam.keys() issue (or does it?) but the
> isinstance question still remains.
I mean exactly what I said, no more, no less. The iter() change means
that for arrays, structs or dictionaries wrapped in a Variant v,
for x in v:
print x
will work. Other methods don't work, unless I specifically write code
for them.
So. Variants, and why I changed the API. I've thought about this quite a
bit and discussed it with the other Collabora people - basically, I'm
aiming for the least astonishing "lossless" implementation.
The API in 0.7 is "lossy" - when inside an exported method implementation
or a signal handler, you can't tell from the Python-level arguments
exactly what went over the wire. Among other things, this makes Matthew
Johnson's binding cross-tests unimplementable - it's impossible to write
a method Identity(v) -> v which does the right thing, because you can't
tell the difference between Variant('abc') and Variant(...(Variant('abc'))...).
To get round this we had the explicitly_pass_message decorator, which in
my view should never be necessary in a high-level binding in a dynamic
language - the kwargs like sender_keyword that have been added are much
better, IMO.
Here are some alternative approaches for dealing with Variants that I've
considered. For each I quote how to code methods VS (which expects a
Variant argument containing the string 'abc', and should put a
Variant containing the string 'abc' on the bus) and VVS (which
expects a Variant containing a Variant containing 'abc', and should put a
Variant containing a Variant containing 'abc' on the bus).
* My current implementation: for D-Bus => Python make everything explicit.
For Python => D-Bus, when expecting a Variant, accept a Variant as-is,
but for programmer convenience, if a non-Variant is returned wrap it
in a Variant automatically.
@method(in_signature='v', out_signature='v')
def VS(self, variant):
assert variant == Variant('abc')
return 'abc' # Variant('abc') would also work
@method(in_signature='v', out_signature='v')
def VVS(self, variant):
assert variant == Variant(Variant('abc'))
return Variant(Variant('abc'))
* The "one level of variant" implementation: for D-Bus => Python
unwrap exactly one level of "variant'ness", for Python => D-Bus
(for a variant) add exactly one level of "variant'ness".
@method(in_signature='v', out_signature='v')
def VS(self, variant):
assert variant == 'abc'
return 'abc' # Variant('abc') would NOT work
@method(in_signature='v', out_signature='v')
def VVS(self, variant):
assert variant == Variant('abc')
return Variant('abc')
* The "no variants" implementation: for D-Bus => Python unwrap
arbitrarily many levels of "variant'ness", for Python => D-Bus
add one level of "variant'ness", and live with the fact that we
lose a small amount of type information.
@method(in_signature='v', out_signature='v')
def VS(self, variant):
assert variant == 'abc'
return 'abc'
@method(in_signature='v', out_signature='v')
def VVS(self, variant):
assert variant == 'abc' # lossy!
return Variant('abc')
* The "annotated types" implementation: Throw away the Variant type and
ensure there is a specialized subclass of a Python type for each D-Bus
type (so resurrect dbus.Boolean, dbus.String, dbus.Double, dbus.Struct
as separate types), and give all the D-Bus types an attribute
dbus_variant_level which is a non-negative integer. This has the issue
that bool cannot be subtyped, so I'd have to implement dbus.Boolean as a
subclass of int rather than bool, like in the Pyrex implementation.
Least astonishment, etc.
Still, this does end up looking fairly Pythonic in common cases:
@method(in_signature='v', out_signature='v')
def VS(self, variant):
assert isinstance(variant, dbus.String) and variant == 'abc' \
and variant.dbus_variant_level == 1
return 'abc' # auto-wrapped
# or: return dbus.String('abc') (auto-wrapped)
# or: return dbus.String('abc', dbus_variant_level=0) (auto-wrapped)
# or: return dbus.String('abc', dbus_variant_level=1)
@method(in_signature='v', out_signature='v')
def VVS(self, variant):
assert isinstance(variant, dbus.String) and variant == 'abc' \
and variant.dbus_variant_level == 2
s = dbus.String('abc', dbus_variant_level=2)
assert s.dbus_variant_level == 2
return s
* A variation on the "annotated types" implementation: subtract 1 from
the variant level when going D-Bus => Python for a variant parameter,
add 1 to the variant level when going Python => D-Bus.
Thoughts? If you object strongly to the implementation I've gone with so
far, I think the "annotated types" implementation has potential.
Regards,
Simon
PS: J5, will you be at the GNOME Summit at the weekend? If so,
I look forward to meeting you and arguing about^W^Wdiscussing this in
person :-)
More information about the dbus
mailing list