471,305 Members | 1,502 Online
Bytes | Software Development & Data Engineering Community
Post +

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 471,305 software developers and data experts.

Wrapper objects

Is there any way to create transparent wrapper objects in Python?

I thought implementing __getattribute__ on either the wrapper class or
its metaclass would do the trick, but it does not work for the built
in operators:

class Foo(object):
class __metaclass__(type):
def __getattribute__(self, name):
print "Klass", name
return type.__getattribute__(self, name)
def __getattribute__(self, name):
print "Objekt", name
return object.__getattribute__(self, name)

Foo() + 1 Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unsupported operand type(s) for +: 'Foo' and 'int' Foo().__add__(1)

Objekt __add__
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 8, in __getattribute__
AttributeError: 'Foo' object has no attribute '__add__'
Thus, note that a + b does not do

try:
return a.__add__(b)
except:
return b.__radd__(a)

and neither, as I first thought

try:
return type(a).__add__(a, b)
....

but something along the lines of

try:
return type.__getattribute__(type(a), '__add__')(a, b)
....
So my naive implementation of a wrapper class,
class wrapper(object):
def __init__(self, value, otherdata):
self.value = value
self.otherdata = otherdata
def __getattribute__(self, name):
return getattr(self.value, name)
does not work. Any ideas for a solution?
Jul 18 '05 #1
12 3729
On 9 Dec 2004 06:11:41 -0800, Egil M?ller <re****@takeit.se> wrote:
Is there any way to create transparent wrapper objects in Python?


This work - <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52295>?

--
Cheers,
Simon B,
si***@brunningonline.net,
http://www.brunningonline.net/simon/blog/
Jul 18 '05 #2
On 9 Dec 2004 06:11:41 -0800, re****@takeit.se (Egil M?ller) wrote:
Is there any way to create transparent wrapper objects in Python?

I thought implementing __getattribute__ on either the wrapper class or
its metaclass would do the trick, but it does not work for the built
in operators:

class Foo(object):
class __metaclass__(type):
def __getattribute__(self, name):
print "Klass", name
return type.__getattribute__(self, name)
def __getattribute__(self, name):
print "Objekt", name
return object.__getattribute__(self, name)

Foo() + 1Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unsupported operand type(s) for +: 'Foo' and 'int' Foo().__add__(1)Objekt __add__
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 8, in __getattribute__
AttributeError: 'Foo' object has no attribute '__add__'
Thus, note that a + b does not do

try:
return a.__add__(b)
except:
return b.__radd__(a)

and neither, as I first thought

try:
return type(a).__add__(a, b)
...

but something along the lines of

try:
return type.__getattribute__(type(a), '__add__')(a, b)
...
So my naive implementation of a wrapper class,
class wrapper(object):
def __init__(self, value, otherdata):
self.value = value
self.otherdata = otherdata
def __getattribute__(self, name):
return getattr(self.value, name)
does not work. Any ideas for a solution?


This seems to go some way towards it:
class MethodDesc(object): ... def __get__(self, inst, cls=None):
... if inst is None: return self
... func = self.__dict__['func']
... if func: return func.__get__(self.thing, type(self.thing))
... else: return getattr(self.thing, self.name)
... def __init__(self, name, thing, func):
... self.name = name
... self.thing = thing
... self.func = func
... def __call__(self, *args, **kw): print 'called %s: %r %r'%(self.name, args, kw)
... def wrapit(thing, *otherdata): ... class Wrapper(object):
... def __metaclass__(cname, cbases, cdict):
... for name, func in type(thing).__dict__.items():
... if callable(func):
... if name not in [
... '__getattr__', '__getattribute__', '__setattr__',
... '__new__', '__init__']:
... cdict[name] = MethodDesc(name, thing, func)
... else:
... cdict[name] = MethodDesc(name, thing, None)
... return type(cname, cbases, cdict)
... def __init__(self, *otherdata):
... if otherdata: self.otherdata = otherdata
... def __getattr__(self, name):
... raise AttributeError('Wrapped %r object has no attribute %r'% (type(self).__name__, name))
... Wrapper.__name__ = 'Wrapped_'+type(thing).__name__
... return Wrapper(*otherdata)
... class Demo(object): ... a = 'eigh'
... two = 2
... def foo(self): return 'Demo foo'
... def bar(self, *args, **kw): print 'Demo bar %r %r'%(args, kw)
... o2 = wrapit( Demo(), 'stuff')
o2.foo <bound method Demo.foo of <__main__.Demo object at 0x02EF184C>> o2.foo() 'Demo foo' o2.bar(1,2,3,x=111,y=222) Demo bar (1, 2, 3) {'y': 222, 'x': 111} o2.a 'eigh' o2.two 2 o2.z Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 16, in __getattr__
AttributeError: Wrapped 'Wrapped_Demo' object has no attribute 'z' o2.z = 'zee'
o2.z 'zee' o2 <Wrapped_Demo object at 0x02EF1B6C> o2.a = 'not eigh'
o2.a 'not eigh' del o2.a
o2.a 'eigh' o3 = wrapit('strange', 'stuff')
o3 'strange' o3.otherdata ('stuff',) o3[2:] 'range'
o3.z Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 16, in __getattr__
AttributeError: Wrapped 'Wrapped_str' object has no attribute 'z'

Not tested beyond what you see ;-) This doesn't wrap setting attributes on the wrapped object,
so setting attributes sets them on the wrapper itself, but that could be fixed.

Now what was it you wanted to use a wrapper for? ;-)
Note:
o3 += 'fail'
o3 'strangefail' type(o3)

<type 'str'>

So if you want new wrapped instances as results from various ops, we need some more code ;-)
Another time ... (cf. a recent post of mine re wrapping complex as a point type).

Regards,
Bengt Richter
Jul 18 '05 #3
Simon Brunning wrote:
On 9 Dec 2004 06:11:41 -0800, Egil M?ller <re****@takeit.se> wrote:
Is there any way to create transparent wrapper objects in Python?

This work - <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52295>?


Only for old-style classes, though. If you inherit from object or another
builtin, that recipe fails.

Cheers,
Nick.

--
Nick Coghlan | nc******@email.com | Brisbane, Australia
---------------------------------------------------------------
http://boredomandlaziness.skystorm.net
Jul 18 '05 #4
Nick Coghlan wrote:
Simon Brunning wrote:
This work -
<http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52295>?


Only for old-style classes, though. If you inherit from object or
another builtin, that recipe fails.


Could you explain, please? I thought __getattr__ worked the same with new- and old-style classes?

Thanks,
Kent
Jul 18 '05 #5
Bengt Richter wrote:
On 9 Dec 2004 06:11:41 -0800, re****@takeit.se (Egil M?ller) wrote:
Is there any way to create transparent wrapper objects in Python?

I thought implementing __getattribute__ on either the wrapper class or
its metaclass would do the trick, but it does not work for the built
in operators:

class Foo(object):
class __metaclass__(type):
def __getattribute__(self, name):
print "Klass", name
return type.__getattribute__(self, name)
def __getattribute__(self, name):
print "Objekt", name
return object.__getattribute__(self, name)

> Foo() + 1Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unsupported operand type(s) for +: 'Foo' and 'int'
> Foo().__add__(1)

Objekt __add__
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 8, in __getattribute__
AttributeError: 'Foo' object has no attribute '__add__'
Thus, note that a + b does not do

try:
return a.__add__(b)
except:
return b.__radd__(a)

and neither, as I first thought

try:
return type(a).__add__(a, b)
...

but something along the lines of

try:
return type.__getattribute__(type(a), '__add__')(a, b)
...
So my naive implementation of a wrapper class,
class wrapper(object):
def __init__(self, value, otherdata):
self.value = value
self.otherdata = otherdata
def __getattribute__(self, name):
return getattr(self.value, name)
does not work. Any ideas for a solution?


This seems to go some way towards it:
>>> class MethodDesc(object): ... def __get__(self, inst, cls=None):
... if inst is None: return self
... func = self.__dict__['func']
... if func: return func.__get__(self.thing,

type(self.thing)) ... else: return getattr(self.thing, self.name)
... def __init__(self, name, thing, func):
... self.name = name
... self.thing = thing
... self.func = func
... def __call__(self, *args, **kw): print 'called %s: %r %r'%(self.name, args, kw) ... >>> def wrapit(thing, *otherdata): ... class Wrapper(object):
... def __metaclass__(cname, cbases, cdict):
... for name, func in type(thing).__dict__.items():
... if callable(func):
... if name not in [
... '__getattr__', '__getattribute__', '__setattr__', ... '__new__', '__init__']:
... cdict[name] = MethodDesc(name, thing, func) ... else:
... cdict[name] = MethodDesc(name, thing, None)
... return type(cname, cbases, cdict)
... def __init__(self, *otherdata):
... if otherdata: self.otherdata = otherdata
... def __getattr__(self, name):
... raise AttributeError('Wrapped %r object has no attribute %r'% (type(self).__name__, name)) ... Wrapper.__name__ = 'Wrapped_'+type(thing).__name__
... return Wrapper(*otherdata)
... >>> class Demo(object): ... a = 'eigh'
... two = 2
... def foo(self): return 'Demo foo'
... def bar(self, *args, **kw): print 'Demo bar %r %r'%(args, kw) ... >>> o2 = wrapit( Demo(), 'stuff')
>>> o2.foo <bound method Demo.foo of <__main__.Demo object at 0x02EF184C>> >>> o2.foo() 'Demo foo' >>> o2.bar(1,2,3,x=111,y=222) Demo bar (1, 2, 3) {'y': 222, 'x': 111} >>> o2.a 'eigh' >>> o2.two 2 >>> o2.z Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 16, in __getattr__
AttributeError: Wrapped 'Wrapped_Demo' object has no attribute 'z' >>> o2.z = 'zee'
>>> o2.z 'zee' >>> o2 <Wrapped_Demo object at 0x02EF1B6C> >>> o2.a = 'not eigh'
>>> o2.a 'not eigh' >>> del o2.a
>>> o2.a 'eigh' >>> o3 = wrapit('strange', 'stuff')
>>> o3 'strange' >>> o3.otherdata ('stuff',) >>> o3[2:] 'range' >>>
>>> o3.z Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 16, in __getattr__
AttributeError: Wrapped 'Wrapped_str' object has no attribute 'z'

Not tested beyond what you see ;-) This doesn't wrap setting attributes on the wrapped object, so setting attributes sets them on the wrapper itself, but that could be fixed.
Now what was it you wanted to use a wrapper for? ;-)
Note:
>>> o3 += 'fail'
>>> o3 'strangefail' >>> type(o3)
<type 'str'>

So if you want new wrapped instances as results from various ops, we

need some more code ;-) Another time ... (cf. a recent post of mine re wrapping complex as a point type).
Regards,
Bengt Richter

Ah, thanks. I didn't think of the possibility of creating a list of
methods that needed wrapping, and wrapping them uppon creation of the
wrapper object. Mainly I think, becaus it seems to me as such an uggly
workaround for a misdesign in Python. Also, if the wrapped object gets
some extra methods/callable member variables added "after the fact",
those will not get wrapped (this is however not a common thing to
happen, it just annoys me that it won't work).

However, I do have some questions about your implementation:

If "if inst is None: return self" to protect for infinite recursion
when looking up self.__dict__?

What is the reason to give func to the MethodDesc property object, why
does its __get__ method have to do

if func: return func.__get__(self.thing, type(self.thing))

?

What is the reason to neither wrap, nor just copy, any of __getattr__,
__getattribute__, __setattr__, '__new__' or '__init__'? Especially
__getattr__, __getattribute__ and __setattr__ seems to need at least
some type of wrapping (but perheaps some special one)?
Regards,
Egil Möller

Jul 18 '05 #6
Kent Johnson wrote:
Nick Coghlan wrote:
Simon Brunning wrote:
This work -
<http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52295>?


Only for old-style classes, though. If you inherit from object or
another builtin, that recipe fails.

Could you explain, please? I thought __getattr__ worked the same with
new- and old-style classes?


Looking at the recipe more closely, I believe you are correct - the behaviour
shouldn't change much between old and new style classes (the main difference
being that the new-style version is affected by descriptors, along with the
other things which may prevent __getattr__ from being invoked in either sort of
class).

However, all that means is that the recipe wouldn't help the OP even with a
classic class. In neither case will implicit invocation find the correct methods
on the object we are delegating to.

The trick has to do with the way special values are often looked up by the
Python interpreter.

Every class object contains entries that correspond to all the magic methods
that Python knows about (in CPython, these are function pointers inside a C
structure, FWIW).

When looking for a special method, the interpreter may simply check the relevant
entry in the class object directly - if it's empty, it assumes the magic method
is not defined and continues on that basis.

A simple example:

class foo:
def __init__(self):
print "Hi there!"

When a class object is built from this definition, the "def __init__" line
actually means two things:
1. Declare a standard Python function called '__init__' in the class 'foo'
2. Populate the appropriate magic method entry in class 'foo'

When overriding __getattribute__ only, step 2 never happens for most of the
magic methods, so, as far as the interpreter is concerned, the class may provide
access to an attribute called "__add__" (via delegation), but it does NOT
provide the magic function "__add__".

In order to have the delegation work as expected, Python has to be told which
magic method entries should be populated (there is no sensible way for Python to
guess which methods you intend to delegate - delegating __init__ or
__getattribute__ is almost certainly insane, but what about methods like
__call__ or __getattr__? __repr__ and __str__ pose interesting questions, too)

A nice way to do this is with a custom metaclass (thanks to Bengt for inspiring
this design - note that his version automatically delegates everything when you
call wrapit, but you have to call wrapit for each class you want to wrap,
whereas in this one you spell out in your wrapper class which methods are
delegated, but that class can then wrap just about anything).

wrapper.py:
=================
# A descriptor for looking up the item
class LookupDescr(object):
def __init__(self, name):
self._name = name

def __get__(self, inst, cls=None):
if inst is None:
return self
# Look it up in the Python namespace
print self._name # Debug output
return inst.__getattr__(self._name)

# Our metaclass
class LookupSpecialAttrs(type):
"""Metaclass that looks up specified 'magic' attributes consistently

__lookup__: a list of strings specifying method calls to look up
"""

def __init__(cls, name, bases, dict):
# Create the 'normal' class
super(LookupSpecialAttrs, cls).__init__(name, bases, dict)
# Now create our looked up methods
if (hasattr(cls, "__lookup__")):
for meth in cls.__lookup__:
setattr(cls, meth, LookupDescr(meth))
# Simple wrapper
class Wrapper(object):
"""Delegates attribute access and addition"""
__metaclass__ = LookupSpecialAttrs
__lookup__ = ["__add__", "__radd__", "__str__", "__int__"]

def __init__(self, obj):
super(Wrapper, self).__setattr__("_wrapped", obj)

def __getattr__(self, attr):
wrapped = super(Wrapper, self).__getattribute__("_wrapped")
return getattr(wrapped, attr)

def __setattr__(self, attr, value):
setattr(self._wrapped, attr, value)

=================

Using our new wrapper type:
=================
..>>> from wrapper import Wrapper
..>>> x = Wrapper(1)
..>>> x + 1
__add__
2
..>>> 1 + x
__radd__
2
..>>> print x
__str__
1
..>>> x + x
__add__
__add__
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unsupported operand type(s) for +: 'Wrapper' and 'Wrapper'
..>>> x = wrapper.Wrapper("Hi")
..>>> x + " there!"
__add__
'Hi there!'
..>>> "Wrapper says " + x
__radd__
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "wrapper.py", line 11, in __get__
return inst.__getattr__(self._name)
File "wrapper.py", line 40, in __getattr__
return getattr(wrapped, attr)
AttributeError: 'str' object has no attribute '__radd__'
..>>> x + x
__add__
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: cannot concatenate 'str' and 'Wrapper' objects
=================

So close! What's going wrong here? Well, it has to do with the fact that, when
developing new types, the onus is on the author of the type to play well with
others (e.g. accepting builtin types as arguments to operations).

Even wrapping '__int__' and '__str__' hasn't helped us - the builtin add methods
don't try to coerce either argument. Instead, they fail immediately if either
argument is not of the correct type. (Ditto for many other operations on builtin
types)

That's why in the examples that worked, it is the method of our wrapper object
that was invoked - after the delegation, both objects were the correct type and
the operation succeeded.

For the 'two wrapper objects' case, however, when we do the delegation,
regardless of direction, the 'other' argument is a Wrapper object. So the
operational fails. And strings don't have __radd__, so the operation with our
wrapper on the right failed when we were wrapping a string.

However, some judicious calls to str() and int() can fix all those 'broken' cases.

Fixing the broken cases:
=================
..>>> x = Wrapper("Hi")
..>>> "Wrapper says " + str(x)
__str__
'Wrapper says Hi'
..>>> str(x) + str(x)
__str__
__str__
'HiHi'
..>>> x = Wrapper(1)
..>>> int(x) + int(x)
__int__
__int__
2
=================

Methinks I'll be paying a visit to the cookbook this weekend. . .

Cheers,
Nick.

--
Nick Coghlan | nc******@email.com | Brisbane, Australia
---------------------------------------------------------------
http://boredomandlaziness.skystorm.net
Jul 18 '05 #7
On 10 Dec 2004 09:33:51 -0800, re****@takeit.se wrote:
Bengt Richter wrote:
On 9 Dec 2004 06:11:41 -0800, re****@takeit.se (Egil M?ller) wrote:
>So my naive implementation of a wrapper class,
>
>
>class wrapper(object):
> def __init__(self, value, otherdata):
> self.value =3D value
> self.otherdata =3D otherdata
> def __getattribute__(self, name):
> return getattr(self.value, name)
>
>
>does not work. Any ideas for a solution?
This seems to go some way towards it:
[snip ugly wrapper hack] Not tested beyond what you see ;-) This doesn't wrap setting

attributes on the wrapped object,
so setting attributes sets them on the wrapper itself, but that could

be fixed.

Now what was it you wanted to use a wrapper for? ;-)

[...]

Ah, thanks. I didn't think of the possibility of creating a list of
methods that needed wrapping, and wrapping them uppon creation of the
wrapper object. Mainly I think, becaus it seems to me as such an uggly
workaround for a misdesign in Python. Also, if the wrapped object gets It is ugly. I am not satisfied with it, but I wanted to offer some
experimental results that might be useful (if only to decide not to
go that way ;-)some extra methods/callable member variables added "after the fact",
those will not get wrapped (this is however not a common thing to
happen, it just annoys me that it won't work). Well, that could be a feature, depending on what your use case is.
Or you could make a method for adding methods, I suppose.
A perfectly transparent wrap of obj would be to do nothing ;-)
What do you actually want to do?
However, I do have some questions about your implementation:

If "if inst is None: return self" to protect for infinite recursion
when looking up self.__dict__? What self are you referring to? Anyway, inst is None when you access
a descriptor instance as a class attribute, e.g., MyClass.desc as opposed
to as an attribute of an instance of that class, e.g., MyClass().desc.
Even __dict__ is actually itself a descriptor. Try
type(obj).__dict__['__dict__'].__get__ (on a new style obj instance).
vs type(obj).__dict__.__get__ which doesn't work.

What is the reason to give func to the MethodDesc property object, why
does its __get__ method have to do

if func: return func.__get__(self.thing, type(self.thing))

? This is to create a bound method that's bound to the thing, not to the
wrapper object. I guess this could be done at wrapping time instead.
I was just hacking down the path of least resistance ;-)

What is the reason to neither wrap, nor just copy, any of __getattr__,
__getattribute__, __setattr__, '__new__' or '__init__'? Especially
__getattr__, __getattribute__ and __setattr__ seems to need at least
some type of wrapping (but perheaps some special one)?

Sorry, this was to eliminate some problems I had along the way. But __new__
and __init__ have to be kept out of the Wrapper class or they will be
invoked when Wrapper() is done to create the wrapped object.

I have hacked something better, not using def __metaclass__, which isn't
necessary. (I just prepare a cdict similarly, and then use that in

Wrapper = type('Wrapped_'+type(thing).__name__, (object,), cdict)

plus playing games to avoid the wrong __init__ and __new__ etc.)

Also, I just remembered an idea that someone had used to limit
methods in an attempt at security by assigning obj.__class__ with a
compatible class. So that might be an avenue too. Maybe a wrapper could
be a subclass of type(obj), and then just copy method function references
to the wrapper class dict? I'll have to explore that another time...

What is your actual use case? ;-)

Regards,
Bengt Richter
Jul 18 '05 #8
Ah, thanks. I didn't think of the possibility of creating a list of
methods that needed wrapping, and wrapping them uppon creation of the
wrapper object. Mainly I think, becaus it seems to me as such an uggly
workaround for a misdesign in Python. Also, if the wrapped object gets
some extra methods/callable member variables added "after the fact",
those will not get wrapped (this is however not a common thing to
happen, it just annoys me that it won't work).

As to what I want to use this for, I today have a huge program in which
several objects are wrapped up with comments (made up by some DOMish
structre) which are displayed to the user at various times. For
example, a list of users may be represented as as comment "List of
users" and a python list of elements, each being a user id (represented
as an integer), with a comment being the username. This means the list
is usable both for user-output and for machine-handling. Hm, this
wasn't prolly such a good example, but hopefully, it shows enought of
the idea...

Todya, the comment-value-pair is an ordinary object with two member
variables, and there are two functions, getComment and getValue, which
extracts a comment if its argument is such an object, or None
otherwise, and the wrapped value if its argument is such an object, and
the argument itself otherwize, respectively.

This means my code is literally filled with calls to getValue(), which
I would like to be able to remove by making the comment-value pair more
transparent.

Anyway, I do have some questions about your implementation:

If "if inst is None: return self" to protect for infinite recursion
when looking up self.__dict__?

What is the reason to give func to the MethodDesc property object, why
does its __get__ method have to do

if func: return func.__get__(self.thing, type(self.thing))

?

What is the reason to neither wrap, nor just copy, any of __getattr__,
__getattribute__, __setattr__, '__new__' or '__init__'? Especially
__getattr__, __getattribute__ and __setattr__ seems to need at least
some type of wrapping (but perheaps some special one)?
Regards,
Egil Möller

Jul 18 '05 #9
As to what I want to use this for, I today have a huge program in which
several objects are wrapped up with comments (made up by some DOMish
structre) which are displayed to the user at various times. For
example, a list of users may be represented as as comment "List of
users" and a python list of elements, each being a user id (represented
as an integer), with a comment being the username. This means the list
is usable both for user-output and for machine-handling. Hm, this
wasn't prolly such a good example, but hopefully, it shows enought of
the idea...

Tody, the comment-value-pair is an ordinary object with two member
variables, and there are two functions, getComment and getValue, which
extracts a comment if its argument is such an object, or None
otherwise, and the wrapped value if its argument is such an object, and
the argument itself otherwize, respectively.

This means my code is literally filled with calls to getValue(), which
I would like to be able to remove by making the comment-value pair more
transparent.

The wrapper objects needs to work as dictionary keys, to support
printing, concatenation/addition, getitem/setitem and such things...

Jul 18 '05 #10
>Well, that could be a feature, depending on what your use case is.
Or you could make a method for adding methods, I suppose.
A perfectly transparent wrap of obj would be to do nothing ;-)
What do you actually want to do?


Actually, the very best would if only type(), isinstance() and the
is-keyword could an object and a wrapped version of it apart.

I've thought of using a weakdict instaed, problem is, it would be a
little bit too transparent for my purpose as the only way to tell an
object and a wrapped version of it apart would be by my own functions
which would do lookups in that dict. Secondly, it would only allow for
one comment per object dopy, which would fail horribly for e.g. numbers
(there are hundreds of uses of the number 1, which all needs different
comments for the number).

Jul 18 '05 #11
re****@takeit.se wrote:
The wrapper objects needs to work as dictionary keys, to support
printing, concatenation/addition, getitem/setitem and such things...


In that case, identifying exactly which operations you want to support, and
using a metaclass based approach like mine or Bengt's should work.

However, I highly recommend the 'opt-in' approach to delegating magic methods.
As Bengt discovered, there are a number of them that can break your class badly
if you inadvertently delegate them (e.g. if you delegate __new__ or __init__,
your class cannot be instantiated).

The Python Language Reference can tell you which methods need to be delegated to
support a given operation: http://docs.python.org/ref/specialnames.html

The delegation may still not be completely transparent, since the delegated
methods may not know how to deal with your wrapper objects. So you may require
calls to methods like int() or str() in your application code to make the types
work out correctly.

Cheers,
Nick.

--
Nick Coghlan | nc******@email.com | Brisbane, Australia
---------------------------------------------------------------
http://boredomandlaziness.skystorm.net
Jul 18 '05 #12
On 10 Dec 2004 15:03:01 -0800, re****@takeit.se wrote:
Well, that could be a feature, depending on what your use case is.
Or you could make a method for adding methods, I suppose.
A perfectly transparent wrap of obj would be to do nothing ;-)
What do you actually want to do?


Actually, the very best would if only type(), isinstance() and the
is-keyword could an object and a wrapped version of it apart.

I've thought of using a weakdict instaed, problem is, it would be a
little bit too transparent for my purpose as the only way to tell an
object and a wrapped version of it apart would be by my own functions
which would do lookups in that dict. Secondly, it would only allow for
one comment per object dopy, which would fail horribly for e.g. numbers
(there are hundreds of uses of the number 1, which all needs different
comments for the number).


Here is a different approach, which I think will perform better than masses
of descriptors and delegations and individual classes, plus wrapping is
just a single assignment. See the W0, W1, W2 usage in the example below.

The idea is to clone the object's class as a subclass in a factory (wrapperclass)
function that takes an example object plus a second argument containing
new or overriding methods and/or class variables. This add-ins argument
is just a class, and everything except what comes in an empty class
(i.e., ['__dict__', '__module__', '__weakref__', '__doc__'] ) is copied
from the dict in to the wrapper class dict, which is initialized as
a clone of the object's class dict.
----< wrapo.py >-----------------------------------------
# wrapo.py -- wrapperclass factory and usage examples
# 20041211 01:02:54 -- bokr
# Use at your own risk. Alpha.

def wrapperclass(obj, AddIns):
"""
Create a (sub)class which may be assigned to obj.__class__
to change the obj instance's apparent available methods
and class variables, taking additions from a supplied
AddIns class serving as a container for methods and variables.
"""
cdict = dict(type(obj).__dict__)
for name, value in AddIns.__dict__.items():
if name not in ['__dict__', '__module__', '__weakref__', '__doc__']:
cdict[name] = value
cname = 'Wrapped_%s_%s'%(AddIns.__name__, type(obj).__name__)
W = type(cname, (type(obj),), cdict)
return W

import time
class Wrap_one(object):
cvar = 'Wrap_one cvar'
def now(self): return 'W1: '+time.ctime()
def __repr__(self):
return 'Wrap_one %r repr:\n %s'%(self.kw, object.__repr__(self))
def __add__(self, other):
return '%r + %r'%(self,str(other))

class Wrap_two(object):
# let orig cvar alone
def now(self): return 'W2: '+time.ctime()
def __repr__(self):
return 'Wrap_two %r repr:\n %s'%(self.args, object.__repr__(self))
def __add__(self, other):
return '%r + %r'%(type(self).__name__, str(other))

def test():
class Foo(object):
cvar = 'orig Foo cvar ;-)'
def __init__(self, *args, **kw):
self.args = args
self.kw =kw
def now(self): return '(time n/a)'
def __str__(self):
return '%s %r\n %s'%(self.now(), self.cvar, repr(self))
foo = Foo(1,2,3, x=111,y=222)
W0 = type(foo)
W1 = wrapperclass(foo, Wrap_one)
W2 = wrapperclass(foo, Wrap_two)
print '--- plain foo:\n', foo
for w in (W0, W1, W2, W0):
foo.__class__ = w
print '\n---- %s ----' % type(foo).__name__
print foo
if w!=W0: print foo + ' a %s string.'%w.__name__
bar = Foo(22,33,bar='this is bar')
for w in (W0, W1, W2, W0):
bar.__class__ = w
print '\n---- %s ----' % type(bar).__name__
print bar

if __name__ == '__main__':
test()
---------------------------------------------------------

Result:

[ 1:16] C:\pywk\clp\wrapper>wrapo.py
--- plain foo:
(time n/a) 'orig Foo cvar ;-)'
<__main__.Foo object at 0x00901630>

---- Foo ----
(time n/a) 'orig Foo cvar ;-)'
<__main__.Foo object at 0x00901630>

---- Wrapped_Wrap_one_Foo ----
W1: Sat Dec 11 01:16:40 2004 'Wrap_one cvar'
Wrap_one {'y': 222, 'x': 111} repr:
<__main__.Wrapped_Wrap_one_Foo object at 0x00901630>
Wrap_one {'y': 222, 'x': 111} repr:
<__main__.Wrapped_Wrap_one_Foo object at 0x00901630> + ' a Wrapped_Wrap_one_Foo string.'

---- Wrapped_Wrap_two_Foo ----
W2: Sat Dec 11 01:16:40 2004 'orig Foo cvar ;-)'
Wrap_two (1, 2, 3) repr:
<__main__.Wrapped_Wrap_two_Foo object at 0x00901630>
'Wrapped_Wrap_two_Foo' + ' a Wrapped_Wrap_two_Foo string.'

---- Foo ----
(time n/a) 'orig Foo cvar ;-)'
<__main__.Foo object at 0x00901630>

---- Foo ----
(time n/a) 'orig Foo cvar ;-)'
<__main__.Foo object at 0x00901390>

---- Wrapped_Wrap_one_Foo ----
W1: Sat Dec 11 01:16:40 2004 'Wrap_one cvar'
Wrap_one {'bar': 'this is bar'} repr:
<__main__.Wrapped_Wrap_one_Foo object at 0x00901390>

---- Wrapped_Wrap_two_Foo ----
W2: Sat Dec 11 01:16:40 2004 'orig Foo cvar ;-)'
Wrap_two (22, 33) repr:
<__main__.Wrapped_Wrap_two_Foo object at 0x00901390>

---- Foo ----
(time n/a) 'orig Foo cvar ;-)'
<__main__.Foo object at 0x00901390>
Regards,
Bengt Richter
Jul 18 '05 #13

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

2 posts views Thread by Hallvard B Furuseth | last post: by
reply views Thread by Peer Dr. Griebel | last post: by
5 posts views Thread by niidjan | last post: by
3 posts views Thread by Michael Brown | last post: by
4 posts views Thread by Stephen | last post: by
22 posts views Thread by linwu02 | last post: by
2 posts views Thread by Ole Nielsby | last post: by
5 posts views Thread by GCRhoads | last post: by
reply views Thread by rosydwin | last post: by

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.