__getattr__ and functions that don't exist

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Erik Johnson

    __getattr__ and functions that don't exist


    Maybe I just don't know the right special function, but what I am wanting to
    do is write something akin to a __getattr__ function so that when you try to
    call an object method that doesn't exist, it get's intercepted *along with
    it's argument*, in the same manner as __getattr__ intercepts attributes
    references for attributes that don't exist.


    This doesn't quite work:
    [color=blue][color=green][color=darkred]
    >>> class Foo:[/color][/color][/color]
    .... def __getattr__(sel f, att_name, *args):
    .... print "%s%s" % (att_name, str(tuple(*args )))
    ....[color=blue][color=green][color=darkred]
    >>> f = Foo()
    >>> f.bar[/color][/color][/color]
    bar()[color=blue][color=green][color=darkred]
    >>> f.bar(1,2,3)[/color][/color][/color]
    bar()
    Traceback (most recent call last):
    File "<stdin>", line 1, in ?
    TypeError: 'NoneType' object is not callable[color=blue][color=green][color=darkred]
    >>> f.bar()[/color][/color][/color]
    bar()
    Traceback (most recent call last):
    File "<stdin>", line 1, in ?
    TypeError: 'NoneType' object is not callable


    Is there some other special function like __getattr__ that does what I want?

    Thanks,
    -ej


  • Nick Smallbone

    #2
    Re: __getattr__ and functions that don't exist

    Erik Johnson wrote:[color=blue]
    > Maybe I just don't know the right special function, but what I am wanting to
    > do is write something akin to a __getattr__ function so that when you try to
    > call an object method that doesn't exist, it get's intercepted *along with
    > it's argument*, in the same manner as __getattr__ intercepts attributes
    > references for attributes that don't exist.
    >
    >
    > This doesn't quite work:
    >[color=green][color=darkred]
    > >>> class Foo:[/color][/color]
    > ... def __getattr__(sel f, att_name, *args):
    > ... print "%s%s" % (att_name, str(tuple(*args )))
    > ...[color=green][color=darkred]
    > >>> f = Foo()[/color][/color][/color]

    The problem is that the call to f.bar happens in two stages: the
    interpreter calls getattr(f, "foo") to get a function, and then it
    calls that function. When __getattr__ is called, you can't tell what
    the parameters of the function call will be, or even if it'll be called
    at all - someone could have run "print f.bar".

    Instead, you can make __getattr__ return a function. Then *that*
    function will be called as f.bar, and you can print out its arguments
    there:

    class Foo:
    def __getattr__(sel f, attr):
    def intercepted(*ar gs):
    print "%s%s" % (attr, args)
    return intercepted

    Comment

    • Erik Johnson

      #3
      Re: __getattr__ and functions that don't exist


      Thanks for your reply, Nick. My first thought was "Ahhh, now I see. That's
      slick!", but after playing with this a bit...
      [color=blue][color=green][color=darkred]
      >>> class Foo:[/color][/color][/color]
      .... def __getattr__(sel f, attr):
      .... def intercepted(*ar gs):
      .... print "%s%s" % (attr, args)
      .... return intercepted
      ....[color=blue][color=green][color=darkred]
      >>> f = Foo()
      >>> f[/color][/color][/color]
      __repr__()
      Traceback (most recent call last):
      File "<stdin>", line 1, in ?
      TypeError: __repr__ returned non-string (type NoneType)


      my thought is "Oooooh... that is some nasty voodoo there!" Especially
      if one wants to also preserve the basic functionality of __getattr__ so that
      it still works to just get an attribute where no arguments were given.

      I was thinking it would be clean to maintain an interface where you
      could call things like f.set_Spam('ham ') and implement that as self.Spam =
      'ham' without actually having to define all the set_XXX methods for all the
      different things I would want to set on my object (as opposed to just making
      an attribute assignment), but I am starting to think that is probably an
      idea I should just simply abandon.

      I guess I don't quite follow the error above though. Can you explain
      exactly what happens with just the evaluation of f?

      Thanks,
      -ej


      "Nick Smallbone" <nick.smallbone @gmail.com> wrote in message
      news:1148596303 .833301.15190@g 10g2000cwb.goog legroups.com...[color=blue]
      > Erik Johnson wrote:[color=green]
      > > Maybe I just don't know the right special function, but what I am[/color][/color]
      wanting to[color=blue][color=green]
      > > do is write something akin to a __getattr__ function so that when you[/color][/color]
      try to[color=blue][color=green]
      > > call an object method that doesn't exist, it get's intercepted *along[/color][/color]
      with[color=blue][color=green]
      > > it's argument*, in the same manner as __getattr__ intercepts attributes
      > > references for attributes that don't exist.
      > >
      > >
      > > This doesn't quite work:
      > >[color=darkred]
      > > >>> class Foo:[/color]
      > > ... def __getattr__(sel f, att_name, *args):
      > > ... print "%s%s" % (att_name, str(tuple(*args )))
      > > ...[color=darkred]
      > > >>> f = Foo()[/color][/color]
      >
      > The problem is that the call to f.bar happens in two stages: the
      > interpreter calls getattr(f, "foo") to get a function, and then it
      > calls that function. When __getattr__ is called, you can't tell what
      > the parameters of the function call will be, or even if it'll be called
      > at all - someone could have run "print f.bar".
      >
      > Instead, you can make __getattr__ return a function. Then *that*
      > function will be called as f.bar, and you can print out its arguments
      > there:
      >
      > class Foo:
      > def __getattr__(sel f, attr):
      > def intercepted(*ar gs):
      > print "%s%s" % (attr, args)
      > return intercepted
      >[/color]


      Comment

      • George Sakkis

        #4
        Re: __getattr__ and functions that don't exist

        Erik Johnson wrote:
        [color=blue]
        > Thanks for your reply, Nick. My first thought was "Ahhh, now I see. That's
        > slick!", but after playing with this a bit...
        >[color=green][color=darkred]
        > >>> class Foo:[/color][/color]
        > ... def __getattr__(sel f, attr):
        > ... def intercepted(*ar gs):
        > ... print "%s%s" % (attr, args)
        > ... return intercepted
        > ...[color=green][color=darkred]
        > >>> f = Foo()
        > >>> f[/color][/color]
        > __repr__()
        > Traceback (most recent call last):
        > File "<stdin>", line 1, in ?
        > TypeError: __repr__ returned non-string (type NoneType)
        >
        >
        > my thought is "Oooooh... that is some nasty voodoo there!" Especially
        > if one wants to also preserve the basic functionality of __getattr__ so that
        > it still works to just get an attribute where no arguments were given.
        >
        > I was thinking it would be clean to maintain an interface where you
        > could call things like f.set_Spam('ham ') and implement that as self.Spam =
        > 'ham' without actually having to define all the set_XXX methods for all the
        > different things I would want to set on my object (as opposed to just making
        > an attribute assignment), but I am starting to think that is probably an
        > idea I should just simply abandon.[/color]

        You're right, you should probably abandon it because python is not
        java; there's no reason to bloat your API with getters and setters
        instead of the natural attribute access and assignment. Now if you've
        spent a lot of time in the java camp and can't live without verbose
        getters and setters, it's not hard to emulate them:

        class Foo(object):
        def __getattr__(sel f, attr):
        if not (attr.startswit h('get_') or attr.startswith ('set_')):
        raise AttributeError
        name = attr[4:]
        if attr[0] == 'g':
        return lambda: getattr(self,na me)
        else:
        return lambda value: setattr(self,na me,value)

        f = Foo()
        f.set_bar(1)
        print f.get_bar()
        print f.bar

        [color=blue]
        > I guess I don't quite follow the error above though. Can you explain
        > exactly what happens with just the evaluation of f?[/color]

        Several thing happen:
        1. When you give an expression in the shell, its value is computed and
        then passed to the repr() function. The result of repr(f) is what's
        printed.
        2. repr(f) attempts to call f.__repr__()
        3. __repr__ is not defined in class Foo or any of its (zero)
        superclasses.
        4. As a result, f.__getattr__(' __repr__') is called instead and returns
        the intercept local function.
        5. intercept() is called with zero arguments (remember, intercept is
        the result of f.__repr__)
        6. intecept prints something but it doesn't return explicitly; thus it
        returns None.
        7. There's a rule that __repr__ (like __str__ and __unicode__) has to
        return a string-type (I'm not sure where is this rule enforced, by the
        classobj maybe ?). Since you returned None, an exception is raised.

        Note that there is not an exception if
        - you replace print with return in __getattr__, or
        - Foo is a new-style class, i.e. extends object like this:
        class Foo(object):
        # rest remain the same
        The reason is that in step (3) above, __repr__ would be looked up in
        Foo's superclass, object, and object.__repr__ would be called instead
        of Foo.__getattr__ . Try to use new-style classes in new code unless you
        have a good reason not to.
        [color=blue]
        > Thanks,
        > -ej[/color]

        HTH,
        George

        [color=blue]
        > "Nick Smallbone" <nick.smallbone @gmail.com> wrote in message
        > news:1148596303 .833301.15190@g 10g2000cwb.goog legroups.com...[color=green]
        > > Erik Johnson wrote:[color=darkred]
        > > > Maybe I just don't know the right special function, but what I am[/color][/color]
        > wanting to[color=green][color=darkred]
        > > > do is write something akin to a __getattr__ function so that when you[/color][/color]
        > try to[color=green][color=darkred]
        > > > call an object method that doesn't exist, it get's intercepted *along[/color][/color]
        > with[color=green][color=darkred]
        > > > it's argument*, in the same manner as __getattr__ intercepts attributes
        > > > references for attributes that don't exist.
        > > >
        > > >
        > > > This doesn't quite work:
        > > >
        > > > >>> class Foo:
        > > > ... def __getattr__(sel f, att_name, *args):
        > > > ... print "%s%s" % (att_name, str(tuple(*args )))
        > > > ...
        > > > >>> f = Foo()[/color]
        > >
        > > The problem is that the call to f.bar happens in two stages: the
        > > interpreter calls getattr(f, "foo") to get a function, and then it
        > > calls that function. When __getattr__ is called, you can't tell what
        > > the parameters of the function call will be, or even if it'll be called
        > > at all - someone could have run "print f.bar".
        > >
        > > Instead, you can make __getattr__ return a function. Then *that*
        > > function will be called as f.bar, and you can print out its arguments
        > > there:
        > >
        > > class Foo:
        > > def __getattr__(sel f, attr):
        > > def intercepted(*ar gs):
        > > print "%s%s" % (attr, args)
        > > return intercepted
        > >[/color][/color]

        Comment

        • Ben Cartwright

          #5
          Re: __getattr__ and functions that don't exist

          Erik Johnson wrote:[color=blue]
          > Thanks for your reply, Nick. My first thought was "Ahhh, now I see. That's
          > slick!", but after playing with this a bit...
          >[color=green][color=darkred]
          > >>> class Foo:[/color][/color]
          > ... def __getattr__(sel f, attr):
          > ... def intercepted(*ar gs):
          > ... print "%s%s" % (attr, args)
          > ... return intercepted
          > ...[color=green][color=darkred]
          > >>> f = Foo()
          > >>> f[/color][/color]
          > __repr__()
          > Traceback (most recent call last):
          > File "<stdin>", line 1, in ?
          > TypeError: __repr__ returned non-string (type NoneType)
          >
          >
          > my thought is "Oooooh... that is some nasty voodoo there!" Especially
          > if one wants to also preserve the basic functionality of __getattr__ so that
          > it still works to just get an attribute where no arguments were given.
          >
          > I was thinking it would be clean to maintain an interface where you
          > could call things like f.set_Spam('ham ') and implement that as self.Spam =
          > 'ham' without actually having to define all the set_XXX methods for all the
          > different things I would want to set on my object (as opposed to just making
          > an attribute assignment), but I am starting to think that is probably an
          > idea I should just simply abandon.[/color]

          Well, you could tweak __getattr__ as follows:
          [color=blue][color=green][color=darkred]
          >>> class Foo:[/color][/color][/color]
          .... def __getattr__(sel f, attr):
          .... if attr.startswith ('__'):
          .... raise AttributeError
          .... def intercepted(*ar gs):
          .... print "%s%s" % (attr, args)
          .... return intercepted

          But abandoning the whole idea is probably a good idea. How is defining
          a magic set_XXX method cleaner than just setting the attribute? Python
          is not C++/Java/C#. Accessors and mutators for simple attributes are
          overkill. Keep it simple, you'll thank yourself for it later when
          maintaining your code. :-)
          [color=blue]
          > I guess I don't quite follow the error above though. Can you explain
          > exactly what happens with just the evaluation of f?[/color]

          Sure. (Note, this is greatly simplified, but still somewhat complex.)
          The Python interpreter does the following when you type in an
          expression:

          (1) evaluate the expression, store the result in temporary object
          (2) attempt to access the object's __repr__ method
          (3) if step 2 didn't raise an AttributeError, call the method, output
          the result, and we're done
          (4) if __getattr__ is defined for the object, call it with "__repr__"
          as the argument
          (5) if step 4 didn't raise an AttributeError, call the method, output
          the result, and we're done
          (6) repeat steps 2 through 5 for __str__
          (7) as a last resort, output the default "<class __main__.Foo at
          0xDEADBEEF>" string

          In your case, the intepreter hit step 4. f.__getattr__(" __repr__")
          returned the "intercepte d" function, which was then called. However,
          the "interprete d" function returned None. The interpreter was
          expecting a string from __repr__, so it raised a TypeError.

          Clear as mud, right? Cutting out the __getattr__ trickery, here's a
          simplified scenario (gets to step 3 from above):
          [color=blue][color=green][color=darkred]
          >>> class Bar(object):[/color][/color][/color]
          ... def __repr__(self):
          ... return None
          ...[color=blue][color=green][color=darkred]
          >>> b = Bar()
          >>> b[/color][/color][/color]
          Traceback (most recent call last):
          File "<stdin>", line 1, in ?
          TypeError: __repr__ returned non-string (type NoneType)

          Hope that helps! One other small thing... please avoid top posting.

          --Ben

          Comment

          • bruno de chez modulix en face

            #6
            Re: __getattr__ and functions that don't exist

            class Parrot(object):
            class _dummy(object):
            def __init__(self, obj, name):
            self.name = name
            self.obj = obj

            def __call__(self, *args, **kw):
            print "dummy %s for %s" % (self.name, self.obj)
            print "called with %s - %s" % (str(args), str(kw))

            def __getattr__(sel f, name):
            return self._dummy(sel f, name)

            hth

            Comment

            • Bruno Desthuilliers

              #7
              Re: __getattr__ and functions that don't exist

              Erik Johnson a écrit :
              (snip)[color=blue]
              > I was thinking it would be clean to maintain an interface where you
              > could call things like f.set_Spam('ham ') and implement that as self.Spam =
              > 'ham'[/color]

              If all you want to do is to assign 'ham' to self.spam, just do it - no
              need for a setter. And if you worry about possible future needs for
              controlling assignement to spam, internally use another name for it or
              any other computation at this level, then it will be time to refactor
              obj.spam as a property:

              class Parrot(object):
              def _set_spam(self, value):
              self._another_a ttrib = any_computation _here(value)
              def _get_spam(self) :
              return some_more_proce ssing_here(self ._another_attri b)
              spam = property(_get_s pam, _set_spam)

              p = Parrot()
              p.spam = "ham"
              print p.spam
              [color=blue]
              > without actually having to define all the set_XXX methods for all the
              > different things I would want to set on my object (as opposed to just making
              > an attribute assignment), but I am starting to think that is probably an
              > idea I should just simply abandon.[/color]

              indeed.

              Comment

              Working...