Wrapper objects

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Egil M?ller

    #1

    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__(t ype):
    def __getattribute_ _(self, name):
    print "Klass", name
    return type.__getattri bute__(self, name)
    def __getattribute_ _(self, name):
    print "Objekt", name
    return object.__getatt ribute__(self, name)

    [color=blue][color=green][color=darkred]
    >>> Foo() + 1[/color][/color][/color]
    Traceback (most recent call last):
    File "<stdin>", line 1, in ?
    TypeError: unsupported operand type(s) for +: 'Foo' and 'int'[color=blue][color=green][color=darkred]
    >>> Foo().__add__(1 )[/color][/color][/color]
    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.__getattri bute__(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.va lue, name)


    does not work. Any ideas for a solution?
  • Simon Brunning

    #2
    Re: Wrapper objects

    On 9 Dec 2004 06:11:41 -0800, Egil M?ller <redhog@takeit. se> wrote:[color=blue]
    > Is there any way to create transparent wrapper objects in Python?[/color]

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

    --
    Cheers,
    Simon B,
    simon@brunningo nline.net,

    Comment

    • Bengt Richter

      #3
      Re: Wrapper objects

      On 9 Dec 2004 06:11:41 -0800, redhog@takeit.s e (Egil M?ller) wrote:
      [color=blue]
      >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__(t ype):
      > def __getattribute_ _(self, name):
      > print "Klass", name
      > return type.__getattri bute__(self, name)
      > def __getattribute_ _(self, name):
      > print "Objekt", name
      > return object.__getatt ribute__(self, name)
      >
      >[color=green][color=darkred]
      >>>> Foo() + 1[/color][/color]
      >Traceback (most recent call last):
      > File "<stdin>", line 1, in ?
      >TypeError: unsupported operand type(s) for +: 'Foo' and 'int'[color=green][color=darkred]
      >>>> Foo().__add__(1 )[/color][/color]
      >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.__getattri bute__(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.va lue, name)
      >
      >
      >does not work. Any ideas for a solution?[/color]

      This seems to go some way towards it:
      [color=blue][color=green][color=darkred]
      >>> class MethodDesc(obje ct):[/color][/color][/color]
      ... def __get__(self, inst, cls=None):
      ... if inst is None: return self
      ... func = self.__dict__['func']
      ... if func: return func.__get__(se lf.thing, type(self.thing ))
      ... else: return getattr(self.th ing, 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)
      ...[color=blue][color=green][color=darkred]
      >>> def wrapit(thing, *otherdata):[/color][/color][/color]
      ... class Wrapper(object) :
      ... def __metaclass__(c name, cbases, cdict):
      ... for name, func in type(thing).__d ict__.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__(sel f, name):
      ... raise AttributeError( 'Wrapped %r object has no attribute %r'% (type(self).__n ame__, name))
      ... Wrapper.__name_ _ = 'Wrapped_'+type (thing).__name_ _
      ... return Wrapper(*otherd ata)
      ...[color=blue][color=green][color=darkred]
      >>> class Demo(object):[/color][/color][/color]
      ... a = 'eigh'
      ... two = 2
      ... def foo(self): return 'Demo foo'
      ... def bar(self, *args, **kw): print 'Demo bar %r %r'%(args, kw)
      ...[color=blue][color=green][color=darkred]
      >>> o2 = wrapit( Demo(), 'stuff')
      >>> o2.foo[/color][/color][/color]
      <bound method Demo.foo of <__main__.Dem o object at 0x02EF184C>>[color=blue][color=green][color=darkred]
      >>> o2.foo()[/color][/color][/color]
      'Demo foo'[color=blue][color=green][color=darkred]
      >>> o2.bar(1,2,3,x= 111,y=222)[/color][/color][/color]
      Demo bar (1, 2, 3) {'y': 222, 'x': 111}[color=blue][color=green][color=darkred]
      >>> o2.a[/color][/color][/color]
      'eigh'[color=blue][color=green][color=darkred]
      >>> o2.two[/color][/color][/color]
      2[color=blue][color=green][color=darkred]
      >>> o2.z[/color][/color][/color]
      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'[color=blue][color=green][color=darkred]
      >>> o2.z = 'zee'
      >>> o2.z[/color][/color][/color]
      'zee'[color=blue][color=green][color=darkred]
      >>> o2[/color][/color][/color]
      <Wrapped_Demo object at 0x02EF1B6C>[color=blue][color=green][color=darkred]
      >>> o2.a = 'not eigh'
      >>> o2.a[/color][/color][/color]
      'not eigh'[color=blue][color=green][color=darkred]
      >>> del o2.a
      >>> o2.a[/color][/color][/color]
      'eigh'[color=blue][color=green][color=darkred]
      >>> o3 = wrapit('strange ', 'stuff')
      >>> o3[/color][/color][/color]
      'strange'[color=blue][color=green][color=darkred]
      >>> o3.otherdata[/color][/color][/color]
      ('stuff',)[color=blue][color=green][color=darkred]
      >>> o3[2:][/color][/color][/color]
      'range'[color=blue][color=green][color=darkred]
      >>>
      >>> o3.z[/color][/color][/color]
      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:
      [color=blue][color=green][color=darkred]
      >>> o3 += 'fail'
      >>> o3[/color][/color][/color]
      'strangefail'[color=blue][color=green][color=darkred]
      >>> type(o3)[/color][/color][/color]
      <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

      Comment

      • Nick Coghlan

        #4
        Re: Wrapper objects

        Simon Brunning wrote:[color=blue]
        > On 9 Dec 2004 06:11:41 -0800, Egil M?ller <redhog@takeit. se> wrote:
        >[color=green]
        >>Is there any way to create transparent wrapper objects in Python?[/color]
        >
        >
        > This work - <http://aspn.activestat e.com/ASPN/Cookbook/Python/Recipe/52295>?
        >[/color]

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

        Cheers,
        Nick.

        --
        Nick Coghlan | ncoghlan@email. com | Brisbane, Australia
        ---------------------------------------------------------------

        Comment

        • Kent Johnson

          #5
          Re: Wrapper objects

          Nick Coghlan wrote:[color=blue]
          > Simon Brunning wrote:[color=green]
          >> This work -
          >> <http://aspn.activestat e.com/ASPN/Cookbook/Python/Recipe/52295>?
          >>[/color]
          >
          > Only for old-style classes, though. If you inherit from object or
          > another builtin, that recipe fails.[/color]

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

          Thanks,
          Kent

          Comment

          • redhog@takeit.se

            #6
            Re: Wrapper objects

            Bengt Richter wrote:[color=blue]
            > On 9 Dec 2004 06:11:41 -0800, redhog@takeit.s e (Egil M?ller) wrote:
            >[color=green]
            > >Is there any way to create transparent wrapper objects in Python?
            > >
            > >I thought implementing __getattribute_ _ on either the wrapper class[/color][/color]
            or[color=blue][color=green]
            > >its metaclass would do the trick, but it does not work for the built
            > >in operators:
            > >
            > >class Foo(object):
            > > class __metaclass__(t ype):
            > > def __getattribute_ _(self, name):
            > > print "Klass", name
            > > return type.__getattri bute__(self, name)
            > > def __getattribute_ _(self, name):
            > > print "Objekt", name
            > > return object.__getatt ribute__(self, name)
            > >
            > >[color=darkred]
            > >>>> Foo() + 1[/color]
            > >Traceback (most recent call last):
            > > File "<stdin>", line 1, in ?
            > >TypeError: unsupported operand type(s) for +: 'Foo' and 'int'[color=darkred]
            > >>>> Foo().__add__(1 )[/color]
            > >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.__getattri bute__(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.va lue, name)
            > >
            > >
            > >does not work. Any ideas for a solution?[/color]
            >
            > This seems to go some way towards it:
            >[color=green][color=darkred]
            > >>> class MethodDesc(obje ct):[/color][/color]
            > ... def __get__(self, inst, cls=None):
            > ... if inst is None: return self
            > ... func = self.__dict__['func']
            > ... if func: return func.__get__(se lf.thing,[/color]
            type(self.thing ))[color=blue]
            > ... else: return getattr(self.th ing, 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[/color]
            %r'%(self.name, args, kw)[color=blue]
            > ...[color=green][color=darkred]
            > >>> def wrapit(thing, *otherdata):[/color][/color]
            > ... class Wrapper(object) :
            > ... def __metaclass__(c name, cbases, cdict):
            > ... for name, func in type(thing).__d ict__.items():
            > ... if callable(func):
            > ... if name not in [
            > ... '__getattr__', '__getattribute __',[/color]
            '__setattr__',[color=blue]
            > ... '__new__', '__init__']:
            > ... cdict[name] = MethodDesc(name ,[/color]
            thing, func)[color=blue]
            > ... else:
            > ... cdict[name] = MethodDesc(name , thing, None)
            > ... return type(cname, cbases, cdict)
            > ... def __init__(self, *otherdata):
            > ... if otherdata: self.otherdata = otherdata
            > ... def __getattr__(sel f, name):
            > ... raise AttributeError( 'Wrapped %r object has no[/color]
            attribute %r'% (type(self).__n ame__, name))[color=blue]
            > ... Wrapper.__name_ _ = 'Wrapped_'+type (thing).__name_ _
            > ... return Wrapper(*otherd ata)
            > ...[color=green][color=darkred]
            > >>> class Demo(object):[/color][/color]
            > ... a = 'eigh'
            > ... two = 2
            > ... def foo(self): return 'Demo foo'
            > ... def bar(self, *args, **kw): print 'Demo bar %r %r'%(args,[/color]
            kw)[color=blue]
            > ...[color=green][color=darkred]
            > >>> o2 = wrapit( Demo(), 'stuff')
            > >>> o2.foo[/color][/color]
            > <bound method Demo.foo of <__main__.Dem o object at 0x02EF184C>>[color=green][color=darkred]
            > >>> o2.foo()[/color][/color]
            > 'Demo foo'[color=green][color=darkred]
            > >>> o2.bar(1,2,3,x= 111,y=222)[/color][/color]
            > Demo bar (1, 2, 3) {'y': 222, 'x': 111}[color=green][color=darkred]
            > >>> o2.a[/color][/color]
            > 'eigh'[color=green][color=darkred]
            > >>> o2.two[/color][/color]
            > 2[color=green][color=darkred]
            > >>> o2.z[/color][/color]
            > 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'[color=green][color=darkred]
            > >>> o2.z = 'zee'
            > >>> o2.z[/color][/color]
            > 'zee'[color=green][color=darkred]
            > >>> o2[/color][/color]
            > <Wrapped_Demo object at 0x02EF1B6C>[color=green][color=darkred]
            > >>> o2.a = 'not eigh'
            > >>> o2.a[/color][/color]
            > 'not eigh'[color=green][color=darkred]
            > >>> del o2.a
            > >>> o2.a[/color][/color]
            > 'eigh'[color=green][color=darkred]
            > >>> o3 = wrapit('strange ', 'stuff')
            > >>> o3[/color][/color]
            > 'strange'[color=green][color=darkred]
            > >>> o3.otherdata[/color][/color]
            > ('stuff',)[color=green][color=darkred]
            > >>> o3[2:][/color][/color]
            > 'range'[color=green][color=darkred]
            > >>>
            > >>> o3.z[/color][/color]
            > 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[/color]
            attributes on the wrapped object,[color=blue]
            > so setting attributes sets them on the wrapper itself, but that could[/color]
            be fixed.[color=blue]
            >
            > Now what was it you wanted to use a wrapper for? ;-)
            > Note:
            >[color=green][color=darkred]
            > >>> o3 += 'fail'
            > >>> o3[/color][/color]
            > 'strangefail'[color=green][color=darkred]
            > >>> type(o3)[/color][/color]
            > <type 'str'>
            >
            > So if you want new wrapped instances as results from various ops, we[/color]
            need some more code ;-)[color=blue]
            > Another time ... (cf. a recent post of mine re wrapping complex as a[/color]
            point type).[color=blue]
            >
            > Regards,
            > Bengt Richter[/color]


            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__(se lf.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

            Comment

            • Nick Coghlan

              #7
              Re: Wrapper objects

              Kent Johnson wrote:[color=blue]
              > Nick Coghlan wrote:
              >[color=green]
              >> Simon Brunning wrote:
              >>[color=darkred]
              >>> This work -
              >>> <http://aspn.activestat e.com/ASPN/Cookbook/Python/Recipe/52295>?
              >>>[/color]
              >>
              >> Only for old-style classes, though. If you inherit from object or
              >> another builtin, that recipe fails.[/color]
              >
              >
              > Could you explain, please? I thought __getattr__ worked the same with
              > new- and old-style classes?[/color]

              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(obj ect):
              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 LookupSpecialAt trs(type):
              """Metaclas s 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(LookupSpe cialAttrs, cls).__init__(n ame, bases, dict)
              # Now create our looked up methods
              if (hasattr(cls, "__lookup__ ")):
              for meth in cls.__lookup__:
              setattr(cls, meth, LookupDescr(met h))


              # Simple wrapper
              class Wrapper(object) :
              """Delegate s attribute access and addition"""
              __metaclass__ = LookupSpecialAt trs
              __lookup__ = ["__add__", "__radd__", "__str__", "__int__"]

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

              def __getattr__(sel f, attr):
              wrapped = super(Wrapper, self).__getattr ibute__("_wrapp ed")
              return getattr(wrapped , attr)

              def __setattr__(sel f, attr, value):
              setattr(self._w rapped, 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 | ncoghlan@email. com | Brisbane, Australia
              ---------------------------------------------------------------

              Comment

              • Bengt Richter

                #8
                Re: Wrapper objects

                On 10 Dec 2004 09:33:51 -0800, redhog@takeit.s e wrote:
                [color=blue]
                >Bengt Richter wrote:[color=green]
                >> On 9 Dec 2004 06:11:41 -0800, redhog@takeit.s e (Egil M?ller) wrote:
                >>[color=darkred]
                >> >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.va lue, name)
                >> >
                >> >
                >> >does not work. Any ideas for a solution?[/color]
                >>
                >> This seems to go some way towards it:
                >>[/color][/color]
                [snip ugly wrapper hack][color=blue][color=green]
                >> Not tested beyond what you see ;-) This doesn't wrap setting[/color]
                >attributes on the wrapped object,[color=green]
                >> so setting attributes sets them on the wrapper itself, but that could[/color]
                >be fixed.[color=green]
                >>
                >> Now what was it you wanted to use a wrapper for? ;-)[/color][/color]
                [...][color=blue]
                >
                >
                >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[/color]
                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 ;-)[color=blue]
                >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).[/color]
                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?[color=blue]
                >
                >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__?[/color]
                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).__dic t__['__dict__'].__get__ (on a new style obj instance).
                vs type(obj).__dic t__.__get__ which doesn't work.
                [color=blue]
                >
                >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__(se lf.thing, type(self.thing ))
                >
                >?[/color]
                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 ;-)
                [color=blue]
                >
                >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)?[/color]
                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

                Comment

                • redhog@takeit.se

                  #9
                  Re: Wrapper objects

                  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__(se lf.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

                  Comment

                  • redhog@takeit.se

                    #10
                    Re: Wrapper objects

                    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...

                    Comment

                    • redhog@takeit.se

                      #11
                      Re: Wrapper objects

                      >Well, that could be a feature, depending on what your use case is.[color=blue]
                      >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?[/color]

                      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).

                      Comment

                      • Nick Coghlan

                        #12
                        Re: Wrapper objects

                        redhog@takeit.s e wrote:[color=blue]
                        > The wrapper objects needs to work as dictionary keys, to support
                        > printing, concatenation/addition, getitem/setitem and such things...[/color]

                        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 | ncoghlan@email. com | Brisbane, Australia
                        ---------------------------------------------------------------

                        Comment

                        • Bengt Richter

                          #13
                          Re: Wrapper objects

                          On 10 Dec 2004 15:03:01 -0800, redhog@takeit.s e wrote:
                          [color=blue][color=green]
                          >>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?[/color]
                          >
                          >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).
                          >[/color]

                          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(ob j, 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).__nam e__)
                          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(o ther))

                          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(fo o, Wrap_one)
                          W2 = wrapperclass(fo o, Wrap_two)
                          print '--- plain foo:\n', foo
                          for w in (W0, W1, W2, W0):
                          foo.__class__ = w
                          print '\n---- %s ----' % type(foo).__nam e__
                          print foo
                          if w!=W0: print foo + ' a %s string.'%w.__na me__
                          bar = Foo(22,33,bar=' this is bar')
                          for w in (W0, W1, W2, W0):
                          bar.__class__ = w
                          print '\n---- %s ----' % type(bar).__nam e__
                          print bar

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

                          Result:

                          [ 1:16] C:\pywk\clp\wra pper>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_on e_Foo ----
                          W1: Sat Dec 11 01:16:40 2004 'Wrap_one cvar'
                          Wrap_one {'y': 222, 'x': 111} repr:
                          <__main__.Wrapp ed_Wrap_one_Foo object at 0x00901630>
                          Wrap_one {'y': 222, 'x': 111} repr:
                          <__main__.Wrapp ed_Wrap_one_Foo object at 0x00901630> + ' a Wrapped_Wrap_on e_Foo string.'

                          ---- Wrapped_Wrap_tw o_Foo ----
                          W2: Sat Dec 11 01:16:40 2004 'orig Foo cvar ;-)'
                          Wrap_two (1, 2, 3) repr:
                          <__main__.Wrapp ed_Wrap_two_Foo object at 0x00901630>
                          'Wrapped_Wrap_t wo_Foo' + ' a Wrapped_Wrap_tw o_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_on e_Foo ----
                          W1: Sat Dec 11 01:16:40 2004 'Wrap_one cvar'
                          Wrap_one {'bar': 'this is bar'} repr:
                          <__main__.Wrapp ed_Wrap_one_Foo object at 0x00901390>

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

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


                          Regards,
                          Bengt Richter

                          Comment

                          Working...