Wrapping classes

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Jeremy Sanders

    #1

    Wrapping classes

    Is it possible to implement some sort of "lazy" creation of objects only
    when the object is used, but behaving in the same way as the object?

    For instance:

    class Foo:
    def __init__(self, val):
    """This is really slow."""
    self.num = val

    # this doesn't call Foo.__init__ yet
    a = lazyclass(Foo, 6)

    # Foo is only initalised here
    print a.num

    What I really want to do is make an object which looks like a numarray,
    but only computes its contents the first time it is used.

    Thanks

    Jeremy


  • Peter Hansen

    #2
    Re: Wrapping classes

    Jeremy Sanders wrote:[color=blue]
    > Is it possible to implement some sort of "lazy" creation of objects only
    > when the object is used, but behaving in the same way as the object?
    >
    > For instance:
    >
    > class Foo:
    > def __init__(self, val):
    > """This is really slow."""
    > self.num = val
    >
    > # this doesn't call Foo.__init__ yet
    > a = lazyclass(Foo, 6)
    >
    > # Foo is only initalised here
    > print a.num
    >
    > What I really want to do is make an object which looks like a numarray,
    > but only computes its contents the first time it is used.[/color]

    Almost anything is possible in Python, though whether the underlying
    design idea is sound is a completely different question. (Translation:
    try the following pseudo-code, but I have my suspicions about whether
    what you're doing is a good idea. :-) )

    class lazyclass(objec t):
    '''should probably be called lazyobject though...'''
    def __init__(self, class_, *args, **kwargs):
    self.class_ = class_
    self.args = args
    self.kwargs = kwargs
    self.obj = None

    def _getnum(self):
    if self.obj is None:
    self.obj = self.class_(*ar gs, **kwargs)
    return self.obj.num
    num = property(_getnu m)

    Now that "should" do precisely what you've asked for above, though it is
    obviously very limited in supporting only a single attribute name even
    though the __init__ method is somewhat generalized. I didn't try
    testing the code so there could be typos.

    -Peter

    Comment

    • Paolino

      #3
      Re: Wrapping classes

      Jeremy Sanders wrote:[color=blue]
      > Is it possible to implement some sort of "lazy" creation of objects only
      > when the object is used, but behaving in the same way as the object?
      >[/color]
      A generic approach would override __getattribute_ _ to let it perform the
      __init__ method on not initialized objects.This is a case for using
      metaclasses as even __init__ method must be overridden ad hoc to
      register the arguments for the lazy initialization.
      Probably you want to fine-tune the triggering (specifing which attribute
      should make it happen ),as every look up would trigger.....

      class NotInitializedO bjects(type):
      def __init__(cls,*_ ):
      realInit=cls.__ init__
      def __newInit__(sel f,*pos,**key):
      def _init():
      realInit(self,* pos,**key)
      self._init=_ini t
      cls.__init__=__ newInit__
      def __getattribute_ _(self,attr):
      def getter(attr):
      return object.__getatt ribute__(self,a ttr)
      if '_init' in getter('__dict_ _'):
      getter('_init') ()
      del self._init
      return getter(attr)
      cls.__getattrib ute__=__getattr ibute__


      if __name__=='__ma in__':
      class Class:
      __metaclass__=N otInitializedOb jects
      def __init__(self,* pos,**key):
      self.initialize d=True
      print 'initializing with',pos,key
      a=Class('arg',k ey='key') # a fake initialization

      try:
      object.__getatt ribute__(a,'ini tialized')
      except AttributeError: # should raise
      print 'not initialized'
      else:
      raise
      try:
      a.initialized #every look up would do ,even a print
      except AttributeError:
      raise
      else:
      print 'initialized'


      Have fun Paolino





      _______________ _______________ _____
      Yahoo! Mail: gratis 1GB per i messaggi e allegati da 10MB

      Comment

      • bruno modulix

        #4
        Re: Wrapping classes

        Jeremy Sanders wrote:[color=blue]
        > Is it possible to implement some sort of "lazy" creation of objects only
        > when the object is used, but behaving in the same way as the object?[/color]

        Smells like a Proxy pattern...
        [color=blue]
        > For instance:
        >
        > class Foo:
        > def __init__(self, val):
        > """This is really slow."""
        > self.num = val
        >
        > # this doesn't call Foo.__init__ yet
        > a = lazyclass(Foo, 6)
        >
        > # Foo is only initalised here
        > print a.num
        >
        > What I really want to do is make an object which looks like a numarray,
        > but only computes its contents the first time it is used.
        >[/color]

        Here's a Q&D, stupid simple, possibly flawed solution:
        class LazyProxy(objec t):
        def __init__(self, klass, *args, **kwargs):
        self.__klass = klass
        self.__args = args
        self.__kwargs = kwargs
        self.__subject = None

        def __lazy_init(sel f):
        if self.__subject is None:
        self.__subject = self.__klass(*s elf.__args,**se lf.__kwargs)

        def __getattr__(sel f, name):
        self.__lazy_ini t()
        return getattr(self.__ subject, name)

        def __setattr__(sel f, name, value):
        # TODO : there's a better way to do this,
        # but I don't remember it ruight now and
        # don't have time to search...
        if name in ['_LazyProxy__kl ass',
        '_LazyProxy__ar gs',
        '_LazyProxy__kw args',
        '_LazyProxy__su bject']:
        self.__dict__[name] = value
        else:
        self.__lazy_ini t()
        setattr(self.__ subject, name, value)


        if __name__ == '__main__':
        class Greeter(object) :
        def __init__(self, name):
        self.name = name
        def greet(self, who):
        return "hello %s, my name is %s" % (who, self.name)

        lazy1 = LazyProxy(Greet er, 'toto')
        print lazy1.greet('ti ti')

        lazy2 = LazyProxy(Greet er, 'lolo')
        lazy2.name = "lili"
        print lazy2.greet(laz y1.name)

        Every comment, fix etc welcome.

        Now there are probably better ways to do this playing with decorators or
        meta-classes.

        --
        bruno desthuilliers
        python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
        p in 'onurb@xiludom. gro'.split('@')])"

        Comment

        • Paolino

          #5
          Re: Wrapping classes

          Paolino wrote:
          [color=blue]
          > class NotInitializedO bjects(type):
          > def __init__(cls,*_ ):
          > realInit=cls.__ init__
          > def __newInit__(sel f,*pos,**key):
          > def _init():
          > realInit(self,* pos,**key)
          > self._init=_ini t
          > cls.__init__=__ newInit__
          > def __getattribute_ _(self,attr):
          > def getter(attr):
          > return object.__getatt ribute__(self,a ttr)
          > if '_init' in getter('__dict_ _'):
          > getter('_init') ()
          > del self._init
          > return getter(attr)
          > cls.__getattrib ute__=__getattr ibute__
          >[/color]

          A lighter solution can be overriding __getattr__.
          This will produce more object-like behaving instances even when not
          initialized, aka you can call methods and access class attributes
          without triggering the init (not very useful)

          class NotInitializedO bjects(type):
          def __init__(cls,*_ ):
          realInit=cls.__ init__
          def __newInit__(sel f,*pos,**key):
          def _init():
          realInit(self,* pos,**key)
          self._init=_ini t
          cls.__init__=__ newInit__
          def __getattr__(sel f,attr):
          if hasattr(self,'_ init'):
          self._init()
          del self._init
          if hasattr(self,at tr):
          return getattr(self,at tr)
          raise AttributeError
          cls.__getattr__ =__getattr__

          ### Test with previous testing code

          A cleaner solution is decoupling the intensive calculation attributes
          from __init__ and use descriptors for them.But this is impossible if
          /the/ instance value is the intensive one to be calculated.

          Ciao Paolino



          _______________ _______________ _____
          Aggiungi la toolbar di Yahoo! Search sul tuo Browser, e'gratis!
          Latest news coverage, email, free stock quotes, live scores and video are just the beginning. Discover more every day at Yahoo!

          Comment

          • Jeremy Sanders

            #6
            Re: Wrapping classes

            Peter Hansen wrote:
            [color=blue]
            > Almost anything is possible in Python, though whether the underlying
            > design idea is sound is a completely different question. (Translation:
            > try the following pseudo-code, but I have my suspicions about whether
            > what you're doing is a good idea. :-) )[/color]

            What I'd like to do precisely is to be able to evaluate an expression like
            "a+2*b" (using eval) where a and b are objects which behave like numarray
            arrays, but whose values aren't computed until their used.

            I need to compute the values when used because the arrays could depend on
            each other, and the easiest way to get the evaluation order correct is to
            only evaluate them when they're used.

            An alternative way is to do some string processing to replace a with
            computearray("a ") in the expression or something horrible like that.

            Thanks

            Jeremy

            --
            Jeremy Sanders

            Comment

            • Diez B. Roggisch

              #7
              Re: Wrapping classes

              Jeremy Sanders wrote:[color=blue]
              > Peter Hansen wrote:
              >
              >[color=green]
              >>Almost anything is possible in Python, though whether the underlying
              >>design idea is sound is a completely different question. (Translation:
              >>try the following pseudo-code, but I have my suspicions about whether
              >>what you're doing is a good idea. :-) )[/color]
              >
              >
              > What I'd like to do precisely is to be able to evaluate an expression like
              > "a+2*b" (using eval) where a and b are objects which behave like numarray
              > arrays, but whose values aren't computed until their used.[/color]

              Maybe you can do that by passing eval your own globals dictionary -
              which in its __getitem__ method will then compute the value lazy.

              The heck, we're in python. Lets try:

              class Foo(object):

              def __init__(self):
              self.a = 10
              self.b = 20
              #return dict.__new__(se lf)

              def __getitem__(sel f, key):
              print "computing %s" % key
              return getattr(self, key)

              l = Foo()
              print l.a

              print eval("10 * a + b", globals(), l)


              It works - in python 2.4!! I tried subclassing dict, but my
              __getitem__-method wasn't called - most probably because it's a C-type,
              but I don't know for sure. Maybe someone can elaborate on that?

              Regards,

              Diez

              Comment

              • Jeremy Sanders

                #8
                Re: Wrapping classes

                Diez B. Roggisch wrote:
                [color=blue]
                > It works - in python 2.4!! I tried subclassing dict, but my
                > __getitem__-method wasn't called - most probably because it's a C-type,
                > but I don't know for sure. Maybe someone can elaborate on that?[/color]

                Yes - I tried that (see thread below). Unfortunately it needs Python 2.4,
                and I can't rely on my users having that.

                Traceback (most recent call last):
                File "test.py", line 15, in ?
                print eval("10 * a + b", globals(), l)
                TypeError: eval() argument 3 must be dict, not Foo

                If you subclass dict it doesn't call the __getitem__ method.

                Jeremy

                --
                Jeremy Sanders

                Comment

                • bruno modulix

                  #9
                  Re: Wrapping classes

                  Jeremy Sanders wrote:[color=blue]
                  > Diez B. Roggisch wrote:
                  >
                  >[color=green]
                  >>It works - in python 2.4!! I tried subclassing dict, but my
                  >>__getitem__-method wasn't called - most probably because it's a C-type,
                  >>but I don't know for sure. Maybe someone can elaborate on that?[/color]
                  >
                  >
                  > Yes - I tried that (see thread below). Unfortunately it needs Python 2.4,
                  > and I can't rely on my users having that.
                  >
                  > Traceback (most recent call last):
                  > File "test.py", line 15, in ?
                  > print eval("10 * a + b", globals(), l)
                  > TypeError: eval() argument 3 must be dict, not Foo
                  >
                  > If you subclass dict it doesn't call the __getitem__ method.[/color]

                  Could it work with a UserDict subclass ?


                  --
                  bruno desthuilliers
                  python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
                  p in 'onurb@xiludom. gro'.split('@')])"

                  Comment

                  • Jeremy Sanders

                    #10
                    Re: Wrapping classes

                    bruno modulix wrote:
                    [color=blue]
                    > Could it work with a UserDict subclass ?[/color]

                    Unfortunately not:

                    Traceback (most recent call last):
                    File "test.py", line 17, in ?
                    print eval("10 * a + b", globals(), l)
                    TypeError: eval() argument 3 must be dict, not instance

                    Thanks

                    Jeremy

                    --
                    Jeremy Sanders

                    Comment

                    • Colin J. Williams

                      #11
                      Re: Wrapping classes

                      Jeremy Sanders wrote:[color=blue]
                      > Peter Hansen wrote:
                      >
                      >[color=green]
                      >>Almost anything is possible in Python, though whether the underlying
                      >>design idea is sound is a completely different question. (Translation:
                      >>try the following pseudo-code, but I have my suspicions about whether
                      >>what you're doing is a good idea. :-) )[/color]
                      >
                      >
                      > What I'd like to do precisely is to be able to evaluate an expression like
                      > "a+2*b" (using eval) where a and b are objects which behave like numarray
                      > arrays, but whose values aren't computed until their used.[/color]
                      Could you not have functions a and b each of which returns a NumArray
                      instance?

                      Your expression would then be something like a(..)+2*b(..).

                      Colin W.[color=blue]
                      >
                      > I need to compute the values when used because the arrays could depend on
                      > each other, and the easiest way to get the evaluation order correct is to
                      > only evaluate them when they're used.
                      >
                      > An alternative way is to do some string processing to replace a with
                      > computearray("a ") in the expression or something horrible like that.
                      >
                      > Thanks
                      >
                      > Jeremy
                      >[/color]

                      Comment

                      • Jeremy Sanders

                        #12
                        Re: Wrapping classes

                        Colin J. Williams wrote:
                        [color=blue]
                        > Could you not have functions a and b each of which returns a NumArray
                        > instance?
                        >
                        > Your expression would then be something like a(..)+2*b(..).[/color]

                        The user enters the expression (yes - I'm aware of the possible security
                        issues), as it is a scientific application. I don't think they'd like to
                        put () after each variable name.

                        I could always munge the expression after the user enters it, of course.

                        Jeremy

                        --
                        Jeremy Sanders

                        Comment

                        • Pedro Werneck

                          #13
                          Re: Wrapping classes


                          I agree this is a case for using metaclasses. What about an
                          implementation like this ? Seems like checking if init was already
                          called will slow down all attribute access significantly, but, I don't
                          like this approach of changing the __init__ method.


                          class LazyInit(type):
                          def __new__(self, name, bases, dict):

                          def __getattribute_ _(self, attr):
                          attrs = object.__getatt ribute__(self, "__dict__")
                          init = attrs["_init"]
                          if not init:
                          args = attrs["_args"]
                          kwds = attrs["_kwds"]
                          __init__ = object.__getatt ribute__(self, "__init__")
                          __init__(*args, **kwds)
                          attrs["_init"] = True

                          return object.__getatt ribute__(self, attr)

                          dict['__getattribute __'] = __getattribute_ _
                          return type.__new__(se lf, name, bases, dict)

                          def __call__(cls, *args, **kwds):
                          o = object.__new__( cls, *args, **kwds)
                          o._args = args
                          o._kwds = kwds
                          o._init = False

                          return o


                          And some simple testing:
                          [color=blue][color=green][color=darkred]
                          >>> class Foo:[/color][/color][/color]
                          .... __metaclass__ = LazyInit
                          .... def __init__(self, x, y):
                          .... print "init was called", x, y
                          .... self.x = x
                          .... self.y = y
                          ....[color=blue][color=green][color=darkred]
                          >>> o = Foo(1, None)
                          >>> o[/color][/color][/color]
                          <__main__.Foo object at 0x402cc96c>[color=blue][color=green][color=darkred]
                          >>> o.x[/color][/color][/color]
                          init was called 1 None
                          1[color=blue][color=green][color=darkred]
                          >>> o.y
                          >>>[/color][/color][/color]


                          Regards,

                          Pedro

                          On Fri, 23 Sep 2005 10:28:42 +0200
                          Paolino <paolo_veronell i@tiscali.it> wrote:
                          [color=blue]
                          > Jeremy Sanders wrote:[color=green]
                          > > Is it possible to implement some sort of "lazy" creation of objects
                          > > only when the object is used, but behaving in the same way as the
                          > > object?
                          > >[/color]
                          > A generic approach would override __getattribute_ _ to let it perform
                          > the
                          > __init__ method on not initialized objects.This is a case for using
                          > metaclasses as even __init__ method must be overridden ad hoc to
                          > register the arguments for the lazy initialization.
                          > Probably you want to fine-tune the triggering (specifing which
                          > attribute should make it happen ),as every look up would trigger.....
                          >
                          > class NotInitializedO bjects(type):
                          > def __init__(cls,*_ ):
                          > realInit=cls.__ init__
                          > def __newInit__(sel f,*pos,**key):
                          > def _init():
                          > realInit(self,* pos,**key)
                          > self._init=_ini t
                          > cls.__init__=__ newInit__
                          > def __getattribute_ _(self,attr):
                          > def getter(attr):
                          > return object.__getatt ribute__(self,a ttr)
                          > if '_init' in getter('__dict_ _'):
                          > getter('_init') ()
                          > del self._init
                          > return getter(attr)
                          > cls.__getattrib ute__=__getattr ibute__
                          >
                          >
                          > if __name__=='__ma in__':
                          > class Class:
                          > __metaclass__=N otInitializedOb jects
                          > def __init__(self,* pos,**key):
                          > self.initialize d=True
                          > print 'initializing with',pos,key
                          > a=Class('arg',k ey='key') # a fake initialization
                          >
                          > try:
                          > object.__getatt ribute__(a,'ini tialized')
                          > except AttributeError: # should raise
                          > print 'not initialized'
                          > else:
                          > raise
                          > try:
                          > a.initialized #every look up would do ,even a print
                          > except AttributeError:
                          > raise
                          > else:
                          > print 'initialized'
                          >
                          >
                          > Have fun Paolino
                          >
                          >
                          >
                          >
                          >
                          > _______________ _______________ _____
                          > Yahoo! Mail: gratis 1GB per i messaggi e allegati da 10MB
                          > http://mail.yahoo.it
                          > --
                          > http://mail.python.org/mailman/listinfo/python-list[/color]


                          --
                          Pedro Werneck

                          Comment

                          • Michael Spencer

                            #14
                            Re: Wrapping classes

                            Jeremy Sanders wrote:[color=blue]
                            > Colin J. Williams wrote:
                            >
                            >[color=green]
                            >>Could you not have functions a and b each of which returns a NumArray
                            >>instance?
                            >>
                            >>Your expression would then be something like a(..)+2*b(..).[/color]
                            >
                            >
                            > The user enters the expression (yes - I'm aware of the possible security
                            > issues), as it is a scientific application. I don't think they'd like to
                            > put () after each variable name.
                            >
                            > I could always munge the expression after the user enters it, of course.
                            >
                            > Jeremy
                            >[/color]
                            Alternatively, you could build your own expression calculator, and initialize
                            the objects if necessary as they are evaluated. If you are happy with Python
                            syntax for your expressiones then the stdlib compiler package is helpful. The
                            example below is not tested beyond what you see. It's a bit verbose, but most
                            of the code is boilerplate.
                            [color=blue][color=green][color=darkred]
                            >>> a = 3
                            >>> b = 4
                            >>> calc('a * b')[/color][/color][/color]
                            using a
                            using b
                            12[color=blue][color=green][color=darkred]
                            >>> calc('a * b ** (b - a) * "a"')[/color][/color][/color]
                            using a
                            using b
                            using b
                            using a
                            'aaaaaaaaaaaa'[color=blue][color=green][color=darkred]
                            >>> calc("0 and a or b")[/color][/color][/color]
                            using b
                            4[color=blue][color=green][color=darkred]
                            >>> calc("1 and a or b")[/color][/color][/color]
                            using a
                            3[color=blue][color=green][color=darkred]
                            >>> calc("1 and a or c")[/color][/color][/color]
                            using a
                            3[color=blue][color=green][color=darkred]
                            >>> calc("0 and a or c")[/color][/color][/color]
                            Undefined symbol: c[color=blue][color=green][color=darkred]
                            >>>[/color][/color][/color]


                            HTH, Michael

                            -----------------

                            import compiler


                            class CalcError(Excep tion):
                            def __init__(self,e rror,descr = None,node = None):
                            self.error = error
                            self.descr = descr
                            self.node = node

                            def __repr__(self):
                            return "%s: %s" % (self.error, self.descr)
                            __str__ = __repr__


                            class LazyCalc(object ):

                            def __init__(self, namespace):
                            self._cache = {} # dispatch table
                            self.context = namespace

                            def visit(self, node,**kw):
                            cls = node.__class__
                            meth = self._cache.set default(cls,
                            getattr(self,'v isit'+cls.__nam e__,self.defaul t))
                            return meth(node, **kw)

                            def visitExpression (self, node, **kw):
                            return self.visit(node .node)


                            # Binary Ops
                            def visitAdd(self,n ode,**kw):
                            return self.visit(node .left) + self.visit(node .right)
                            def visitDiv(self,n ode,**kw):
                            return self.visit(node .left) / self.visit(node .right)
                            def visitFloorDiv(s elf,node,**kw):
                            return self.visit(node .left) // self.visit(node .right)
                            def visitLeftShift( self,node,**kw) :
                            return self.visit(node .left) << self.visit(node .right)
                            def visitMod(self,n ode,**kw):
                            return self.visit(node .left) % self.visit(node .right)
                            def visitMul(self,n ode,**kw):
                            return self.visit(node .left) * self.visit(node .right)
                            def visitPower(self ,node,**kw):
                            return self.visit(node .left) ** self.visit(node .right)
                            def visitRightShift (self,node,**kw ):
                            return self.visit(node .left) >> self.visit(node .right)
                            def visitSub(self,n ode,**kw):
                            return self.visit(node .left) - self.visit(node .right)

                            # Unary ops
                            def visitNot(self,n ode,*kw):
                            return not self.visit(node .expr)
                            def visitUnarySub(s elf,node,*kw):
                            return -self.visit(node .expr)
                            def visitInvert(sel f,node,*kw):
                            return ~self.visit(nod e.expr)
                            def visitUnaryAdd(s elf,node,*kw):
                            return +self.visit(nod e.expr)

                            # Flow Control
                            def visitAnd(self,n ode,**kw):
                            for arg in node.nodes:
                            val = self.visit(arg)
                            if not val:
                            return val
                            return val
                            def visitOr(self,no de,**kw):
                            for arg in node.nodes:
                            val = self.visit(arg)
                            if val:
                            return val
                            return val

                            # Logical Ops
                            def visitBitand(sel f,node,**kw):
                            return reduce(lambda a,b: a & b,[self.visit(arg) for arg in node.nodes])
                            def visitBitor(self ,node,**kw):
                            return reduce(lambda a,b: a | b,[self.visit(arg) for arg in node.nodes])
                            def visitBitxor(sel f,node,**kw):
                            return reduce(lambda a,b: a ^ b,[self.visit(arg) for arg in node.nodes])
                            def visitCompare(se lf,node,**kw):
                            comparisons = {
                            "<": operator.lt, # strictly less than
                            "<=": operator.le,# less than or equal
                            ">": operator.gt, # strictly greater than
                            ">=": operator.ge, # greater than or equal
                            "==": operator.eq, # equal
                            "!=": operator.ne, # not equal
                            "<>": operator.ne, # not equal
                            "is": operator.is_, # object identity
                            "is not": operator.is_not # negated object identity
                            }
                            obj = self.visit(node .expr)
                            for op, compnode in node.ops:
                            compobj = self.visit(comp node)
                            if not comparisons[op](obj, compobj):
                            return False
                            obj = compobj
                            return True


                            # Values
                            def visitCallFunc(s elf,node,**kw):
                            raise CalcError("Func tions not supported", node.node)

                            def visitName(self, node, **kw):
                            """LazyEvaluati on"""
                            name = node.name
                            try:
                            val = eval(name, self.context)
                            except NameError:
                            raise CalcError("Unde fined symbol",name)
                            except:
                            raise
                            print "using %s" % name # init if necessary here
                            return val

                            def visitConst(self , node, **kw):
                            return node.value

                            # Other
                            def default(self, node, **kw):
                            """Anything not expressly allowed is forbidden"""
                            raise CalcError("Not Allowed",
                            node.__class__. __name__,node)


                            def calc(source, context = None):
                            walker = LazyCalc(contex t or globals())
                            try:
                            ast = compiler.parse( source,"eval")
                            except SyntaxError, err:
                            raise
                            try:
                            return walker.visit(as t)
                            except CalcError, err:
                            return err


                            Comment

                            Working...