multiple inheritance and __getattr__

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

    multiple inheritance and __getattr__

    Hi there,
    I have the following situation (I tryed to minimize the code to concentrate
    on the issue):
    >>class A(object):
    def __getattr__(sel f, name):
    print 'A.__getattr__'
    if name == 'a': return 1
    raise AttributeError( '%s not found in A' % name)
    >>class B(object):
    def __getattr__(sel f, name):
    print 'B.__getattr__'
    if name == 'b': return 1
    raise AttributeError( '%s not found in B' % name)

    Both classes have a __getattr__ method.
    Now I want to have a class that inherits from both so I write:
    >>class C(B,A):
    pass

    The problem arise when I try something like this:
    >>c=C()
    >>c.a
    A.__getattr__
    1
    >>c.b
    A.__getattr__

    Traceback (most recent call last):
    File "<pyshell#4 7>", line 1, in <module>
    c.b
    File "<pyshell#4 2>", line 5, in __getattr__
    raise AttributeError( '%s not found in A' % name)
    AttributeError: b not found in A

    I was expecting, after a fail in A.__getattr__, a call to the __getattr__
    method of B but it seems that after A.__getattr__ fails the exception stops
    the flow. So, if I did understand well, B.__getattr__ will be never called
    "automatically" . I don't know if this has a reason, if it is a design choice
    or what else, any explanation is welcome.

    Since A and B are not written by me I can only work on C. The solution that
    comes to my mind is to define a __getattr__ also in C and write something
    like:
    >>class C(A,B):
    def __getattr__(sel f, name):
    try:
    return A.__getattr__(s elf, name)
    except AttributeError:
    return B.__getattr__(s elf, name)
    >>c=C()
    >>c.a
    A.__getattr__
    1
    >>c.b
    A.__getattr__
    B.__getattr__
    1

    A better solution is welcome.
    Many thanks, Enrico


  • David C. Ullrich

    #2
    Re: multiple inheritance and __getattr__

    In article <488ddc51$0$416 54$4fafbaef@rea der4.news.tin.i t>,
    "Enrico" <4564@755189.45 wrote:
    Hi there,
    I have the following situation (I tryed to minimize the code to concentrate
    on the issue):
    >
    >class A(object):
    def __getattr__(sel f, name):
    print 'A.__getattr__'
    if name == 'a': return 1
    raise AttributeError( '%s not found in A' % name)
    >
    >class B(object):
    def __getattr__(sel f, name):
    print 'B.__getattr__'
    if name == 'b': return 1
    raise AttributeError( '%s not found in B' % name)
    >
    Both classes have a __getattr__ method.
    Now I want to have a class that inherits from both so I write:
    >
    >class C(B,A):
    pass
    >
    The problem arise when I try something like this:
    >c=C()
    >c.a
    A.__getattr__
    1
    >c.b
    A.__getattr__
    >
    Traceback (most recent call last):
    File "<pyshell#4 7>", line 1, in <module>
    c.b
    File "<pyshell#4 2>", line 5, in __getattr__
    raise AttributeError( '%s not found in A' % name)
    AttributeError: b not found in A
    >
    I was expecting, after a fail in A.__getattr__, a call to the __getattr__
    method of B but it seems that after A.__getattr__ fails the exception stops
    the flow. So, if I did understand well, B.__getattr__ will be never called
    "automatically" . I don't know if this has a reason, if it is a design choice
    or what else, any explanation is welcome.
    Well, it's simply the way it works. When you say C(A, B) then A is
    searched first for a given attribute and if A has no such attribute
    then B is searched. If you really want to do something like this
    then yes, you have to do something as below.

    I suspect that the people who feel expert enough to talk about what
    you should and shouldn't want are going to say that you shouldn't
    want something like this - with things set up the way you have them
    you really can't tell what's going to happen with c.__getattr__
    unless you look at all the code in all the subclasses.
    Since A and B are not written by me I can only work on C. The solution that
    comes to my mind is to define a __getattr__ also in C and write something
    like:
    >
    >class C(A,B):
    def __getattr__(sel f, name):
    try:
    return A.__getattr__(s elf, name)
    except AttributeError:
    return B.__getattr__(s elf, name)
    >
    >c=C()
    >c.a
    A.__getattr__
    1
    >c.b
    A.__getattr__
    B.__getattr__
    1
    >
    A better solution is welcome.
    Many thanks, Enrico
    --
    David C. Ullrich

    Comment

    • Enrico

      #3
      Re: multiple inheritance and __getattr__

      "Bruno Desthuilliers" <bdesth.quelque chose@free.quel quepart.frha scritto
      nel messaggio news:488e2e70$0 $29472$426a74cc @news.free.fr.. .
      Indeed. You explicitely raise, so the lookup stops here. You'd need to
      explicitely call on superclass instead to have B.__getattr__ called, ie:
      >
      class A(object):
      def __getattr__(sel f, name):
      if name == 'a':
      return 1
      return super(A, self).__getattr __(name)
      >
      class B(object):
      def __getattr__(sel f, name):
      if name == 'b':
      return 2
      return super(B, self).__getattr __(name)
      Hi Bruno,
      this is an interisting point. Just to understand better: when I raise an
      AttributeError the search stops but if I call a superclass (that for my
      understanding raises an AttributeError) the search continues. At this point
      I suspect that the search is doing something else, like checking if the
      class is at the top of the hierarchy. Do you know where I can look for this,
      probably in the core code of Python?
      Since A and B are not written by me I can only work on C.
      >
      Really ? You know, Python is a *very* dynamic language. If A and B are
      ordinary Python classes (ie: not builtin types, not C extensions, etc),
      you can monkeypatch them. But that's not necessarily the best thing to
      do (it would require more work than your actual solution).
      I know that I can do whatIwant with class A and class B (monkeypatch!) but I
      prefer to concentrate on my code and write a clean solution. Thanks for your
      help.
      Enrico


      Comment

      • Maric Michaud

        #4
        Re: multiple inheritance and __getattr__

        Le Monday 28 July 2008 16:48:09 Enrico, vous avez écrit :
        Hi there,
        I have the following situation (I tryed to minimize the code to concentrate
        >
        on the issue):
        >class A(object):
        >
        def __getattr__(sel f, name):
        print 'A.__getattr__'
        if name == 'a': return 1
        raise AttributeError( '%s not found in A' % name)
        >
        >class B(object):
        >
        def __getattr__(sel f, name):
        print 'B.__getattr__'
        if name == 'b': return 1
        raise AttributeError( '%s not found in B' % name)
        >
        Both classes have a __getattr__ method.
        >
        Now I want to have a class that inherits from both so I write:
        >class C(B,A):
        >
        pass
        >
        The problem arise when I try something like this:
        >c=C()
        >c.a
        >
        A.__getattr__
        1
        >
        >c.b
        >
        A.__getattr__
        >
        Traceback (most recent call last):
        File "<pyshell#4 7>", line 1, in <module>
        c.b
        File "<pyshell#4 2>", line 5, in __getattr__
        raise AttributeError( '%s not found in A' % name)
        AttributeError: b not found in A
        >
        I was expecting, after a fail in A.__getattr__, a call to the __getattr__
        method of B but it seems that after A.__getattr__ fails the exception stops
        the flow. So, if I did understand well, B.__getattr__ will be never called
        "automatically" . I don't know if this has a reason, if it is a design
        choice or what else, any explanation is welcome.
        >
        No getattr is a lookup fallback, classes which implement them in a
        non-collaborative way are unlikely to be used for multiple inheritance.
        Given how multiple inheritance work and __getattr__ semantic, I was surprised
        it is not that easy to figure out how it could work in a collaborative, and
        how far fromm this are common implementation of __getattr__.
        Normally they should be implemented like that :
        >>>[89]: class A(object) :
        def __getattr__(sel f, name) :
        if name == 'a' : return 'a'
        try : g = super(A, self).__getattr __
        except : raise AttributeError( 'no more __getattr__')
        return g(name)
        ....:
        ....:
        >>>[95]: class B(object) :
        def __getattr__(sel f, name) :
        if name == 'b' : return 'b'
        try : g = super(B, self).__getattr __
        except : raise AttributeError( 'no more __getattr__')
        return g(name)
        .....:
        .....:
        >>>[101]: class C(A, B) :
        def __getattr__(sel f, name) :
        if name == 'c' : return 'c'
        try : g = super(C, self).__getattr __
        except : raise AttributeError( 'no more __getattr__')
        return g(name)
        .....:
        .....:
        >>>[107]: C().a
        ...[107]: 'a'
        >>>[108]: C().b
        ...[108]: 'b'
        >>>[109]: C().c
        ...[109]: 'c'
        >>>[110]: C().k
        ---------------------------------------------------------------------------
        AttributeError Traceback (most recent call last)

        /home/maric/<ipython consolein <module>()

        /home/maric/<ipython consolein __getattr__(sel f, name)

        /home/maric/<ipython consolein __getattr__(sel f, name)

        /home/maric/<ipython consolein __getattr__(sel f, name)

        AttributeError: no more __getattr__

        Since A and B are not written by me I can only work on C. The solution that
        comes to my mind is to define a __getattr__ also in C and write something
        >
        like:
        >class C(A,B):
        >
        def __getattr__(sel f, name):
        try:
        return A.__getattr__(s elf, name)
        except AttributeError:
        return B.__getattr__(s elf, name)
        >
        >c=C()
        >c.a
        >
        A.__getattr__
        1
        >
        >c.b
        >
        A.__getattr__
        B.__getattr__
        1
        >
        A better solution is welcome.
        There is no way to repair those clases for mulitple inheritance except monkey
        patching them.
        The idea of this patch would be :

        def collaborative_g etattr(class_, old_name) :
        old_one = getattr(class_, old_name)
        def __getattr__(sel f, name) :
        try : return old_one(self, name)
        except AttributeError :
        try : g = super(class_, self).__getattr __
        except : raise AttributeError( 'no more __getattr__')
        return g(name)

        if not getattr(C, '_C_fixed__', False) :
        C._C_fixed__ = C.__getattr__
        C.__getattr__ = collaborative_g etattr(C, '_C_fixed__')


        That said, if your class C is a real facade for its ancestors A and B (A and B
        won't appear at all in the hierarchies of your subclasses), your solution is
        near the best one in terms of simplicity-efficiency. I said near the best one
        because your __getattr__ isn't collaborative yet ! :).

        --
        _____________

        Maric Michaud

        Comment

        • David C. Ullrich

          #5
          Re: multiple inheritance and __getattr__

          In article <488e2e70$0$294 72$426a74cc@new s.free.fr>,
          Bruno Desthuilliers <bdesth.quelque chose@free.quel quepart.frwrote :
          Enrico a écrit :
          Hi there,
          I have the following situation (I tryed to minimize the code to concentrate
          on the issue):
          >>class A(object):
          def __getattr__(sel f, name):
          print 'A.__getattr__'
          if name == 'a': return 1
          raise AttributeError( '%s not found in A' % name)
          >>class B(object):
          def __getattr__(sel f, name):
          print 'B.__getattr__'
          if name == 'b': return 1
          raise AttributeError( '%s not found in B' % name)

          Both classes have a __getattr__ method.
          Now I want to have a class that inherits from both so I write:
          >>class C(B,A):
          pass

          The problem arise when I try something like this:
          >>c=C()
          >>c.a
          A.__getattr__
          1
          >>c.b
          A.__getattr__

          Traceback (most recent call last):
          File "<pyshell#4 7>", line 1, in <module>
          c.b
          File "<pyshell#4 2>", line 5, in __getattr__
          raise AttributeError( '%s not found in A' % name)
          AttributeError: b not found in A
          >
          That's what I would have expected.
          >
          I was expecting, after a fail in A.__getattr__, a call to the __getattr__
          method of B but it seems that after A.__getattr__ fails the exception stops
          the flow.
          >
          Indeed. You explicitely raise, so the lookup stops here.
          ??? Surely the reason the lookup stops there is that a __getattr__
          was _found_. In the code below the lookup is not continuing,
          there's a _second_ lookup started by the request for super.__getattr __.
          You'd need to
          explicitely call on superclass instead to have B.__getattr__ called, ie:
          >
          class A(object):
          def __getattr__(sel f, name):
          if name == 'a':
          return 1
          return super(A, self).__getattr __(name)
          >
          class B(object):
          def __getattr__(sel f, name):
          if name == 'b':
          return 2
          return super(B, self).__getattr __(name)
          >
          class C(A, B):
          pass
          >
          --
          David C. Ullrich

          Comment

          Working...