@property decorator doesn't raise exceptions

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

    @property decorator doesn't raise exceptions

    Hi,

    I've encountered a problem which is making debugging less obvious than
    it should be. The @property decorator doesn't always raise exceptions.
    It seems like it is bound to the class but ignored when called. I can
    see the attribute using dir(self.__clas s__) on an instance, but when
    called, python enters __getattr__. If I correct the bug, the attribute
    calls work as expected and do not call __getattr__.

    I can't seem to make a simple repro. Can anyone offer any clues as to
    what might cause this so I can try to prove it?


    Cheers,

    - Rafe
  • Christian Heimes

    #2
    Re: @property decorator doesn't raise exceptions

    Rafe wrote:
    Hi,
    >
    I've encountered a problem which is making debugging less obvious than
    it should be. The @property decorator doesn't always raise exceptions.
    It seems like it is bound to the class but ignored when called. I can
    see the attribute using dir(self.__clas s__) on an instance, but when
    called, python enters __getattr__. If I correct the bug, the attribute
    calls work as expected and do not call __getattr__.
    >
    I can't seem to make a simple repro. Can anyone offer any clues as to
    what might cause this so I can try to prove it?
    You must subclass from "object" to get a new style class. properties
    don't work correctly on old style classes.

    Christian

    Comment

    • Rafe

      #3
      Re: @property decorator doesn't raise exceptions

      On Oct 24, 2:21 am, Christian Heimes <li...@cheimes. dewrote:
      Rafewrote:
      Hi,
      >
      I've encountered a problem which is making debugging less obvious than
      it should be. The @property decorator doesn't always raise exceptions.
      It seems like it is bound to the class but ignored when called. I can
      see the attribute using dir(self.__clas s__) on an instance, but when
      called, python enters __getattr__. If I correct the bug, the attribute
      calls work as expected and do not call __getattr__.
      >
      I can't seem to make a simple repro. Can anyone offer any clues as to
      what might cause this so I can try to prove it?
      >
      You must subclass from "object" to get a new style class. properties
      don't work correctly on old style classes.
      >
      Christian
      All classes are a sub-class of object. Any other ideas?

      - Rafe

      Comment

      • Peter Otten

        #4
        Re: @property decorator doesn't raise exceptions

        Rafe wrote:
        On Oct 24, 2:21 am, Christian Heimes <li...@cheimes. dewrote:
        >Rafewrote:
        Hi,
        >>
        I've encountered a problem which is making debugging less obvious than
        it should be. The @property decorator doesn't always raise exceptions.
        It seems like it is bound to the class but ignored when called. I can
        see the attribute using dir(self.__clas s__) on an instance, but when
        called, python enters __getattr__. If I correct the bug, the attribute
        calls work as expected and do not call __getattr__.
        >>
        I can't seem to make a simple repro. Can anyone offer any clues as to
        what might cause this so I can try to prove it?
        >>
        >You must subclass from "object" to get a new style class. properties
        >don't work correctly on old style classes.
        >>
        >Christian
        >
        All classes are a sub-class of object. Any other ideas?
        Hard to tell when you don't give any code.
        >>class A(object):
        .... @property
        .... def attribute(self) :
        .... raise AttributeError
        .... def __getattr__(sel f, name):
        .... return "nobody expects the spanish inquisition"
        ....
        >>A().attribu te
        'nobody expects the spanish inquisition'

        Do you mean something like this? I don't think the __getattr__() call can be
        avoided here.

        Peter

        Comment

        • Steven D'Aprano

          #5
          Re: @property decorator doesn't raise exceptions

          On Fri, 24 Oct 2008 09:34:36 -0700, Rafe wrote:
          >You must subclass from "object" to get a new style class. properties
          >don't work correctly on old style classes.
          >>
          >Christian
          >
          All classes are a sub-class of object. Any other ideas?
          Only in Python 3. If you are relying on that to be true in Python 2.x,
          you're going to disappointed:
          >>class A():
          .... pass
          ....
          >>issubclass( A, object)
          False


          There are a lot of classic classes in the standard library. If you
          inherit from them, your class will also be a classic class and properties
          will fail to work correctly.


          Earlier, you wrote:

          "I've encountered a problem which is making debugging less obvious than
          it should be. The @property decorator doesn't always raise exceptions."

          Are you expecting it to raise an exception when the class is defined, or
          when the property is called? e.g.

          class Fail(object):
          @property
          "this should raise an exception"

          Works for me -- I get a syntax error, as expected.


          "It seems like it is bound to the class but ignored when called. I can
          see the attribute using dir(self.__clas s__) on an instance, but when
          called, python enters __getattr__. If I correct the bug, the attribute
          calls work as expected and do not call __getattr__."


          Perhaps you can give us a minimal example showing the code with the bug,
          then how you corrected the bug? That might give as a hint as to what is
          going on. I know that's hard, when you can't reproduce the problem, but
          it's just as hard for us to solve your problem without more information.





          --
          Steven

          Comment

          • Steven D'Aprano

            #6
            Re: @property decorator doesn't raise exceptions

            On Fri, 24 Oct 2008 01:47:10 -0700, Rafe wrote:
            Hi,
            >
            I've encountered a problem which is making debugging less obvious than
            it should be. The @property decorator doesn't always raise exceptions.
            I don't think that's the problem. I think properties do correctly raise
            all exceptions that occur inside them, at least built-in exceptions.

            I wrote a test that raises exceptions inside properties and they (almost)
            all are raised normally.

            Whatever your problem is, it isn't that properties don't raise exceptions.

            For those who are interested, the test program I used follows.


            # test module

            def get_exceptions( ):
            import __builtin__
            list_of_excepti ons = []
            for key in dir(__builtin__ ):
            # do not use __builtins__ -- note the 's'.
            obj = getattr(__built in__, key)
            if not isinstance(obj, type):
            continue
            if issubclass(obj, Exception):
            list_of_excepti ons.append(obj)
            return list_of_excepti ons

            def builder(list_of _exceptions):
            class PropertyTester( object):
            """Do properties raise exceptions? Let's find out."""
            for exception in list_of_excepti ons:
            name = exception.__nam e__
            @property
            def func(self, exception=excep tion):
            # Make sure we bind a local copy to exception.
            raise exception
            setattr(Propert yTester, name, func)
            return PropertyTester( )

            def testRaises(obj, exception_type, verbose):
            """Test that appropriate exception is raised when accessing an
            attribute."""
            name = exception_type. __name__
            try:
            getattr(obj, name)
            except exception_type:
            if verbose:
            print "Passed: expected exception %s raised correctly" \
            % exception_type
            except Exception, e:
            print "Failed: expected %s but got %r" % (exception_type , e)
            else:
            print "Failed: expected %s but got no exception at all" \
            % exception_type

            if __name__ == '__main__':
            import sys
            if sys.argv[1:] == ['verbose']:
            verbose = True
            else:
            verbose = False
            exceptions = get_exceptions( )
            tester = builder(excepti ons)
            for exception in exceptions:
            testRaises(test er, exception, verbose)



            --
            Steven

            Comment

            • Rafe

              #7
              Re: @property decorator doesn't raise exceptions

              On Oct 24, 9:58 am, Peter Otten <__pete...@web. dewrote:
              Rafe wrote:
              On Oct 24, 2:21 am, Christian Heimes <li...@cheimes. dewrote:
              Rafewrote:
              Hi,
              >
              I've encountered a problem which is making debugging less obvious than
              it should be. The @property decorator doesn't always raise exceptions.
              It seems like it is bound to the class but ignored when called. I can
              see the attribute using dir(self.__clas s__) on an instance, but when
              called, python enters __getattr__. If I correct the bug, the attribute
              calls work as expected and do not call __getattr__.
              >
              I can't seem to make a simple repro. Can anyone offer any clues as to
              what might cause this so I can try to prove it?
              >
              You must subclass from "object" to get a new style class. properties
              don't work correctly on old style classes.
              >
              Christian
              >
              All classes are a sub-class of object. Any other ideas?
              >
              Hard to tell when you don't give any code.
              >
              >class A(object):
              >
              ...     @property
              ...     def attribute(self) :
              ...             raise AttributeError
              ...     def __getattr__(sel f, name):
              ...             return "nobody expects the spanish inquisition"
              ...>>A().attrib ute
              >
              'nobody expects the spanish inquisition'
              >
              Do you mean something like this? I don't think the __getattr__() call canbe
              avoided here.
              >
              Peter
              You nailed it Peter! I thought __getattr__ was a symptom, not the
              cause of the misleading errors. Here is the repro (pretty much
              regurgitated):

              The expected behavior...
              >>class A(object):
              .... @property
              .... def attribute(self) :
              .... raise AttributeError( "Correct Error.")
              >>A().attribu te
              Traceback (most recent call last):
              File "<console>" , line 0, in <module>
              File "<console>" , line 0, in attribute
              AttributeError: Correct Error.


              The unexpected and misleading exception...
              >>class A(object):
              .... @property
              .... def attribute(self) :
              .... raise AttributeError( "Correct Error.")
              .... def __getattr__(sel f, name):
              .... cls_name = self.__class__. __name__
              .... msg = "%s has no attribute '%s'." % (cls_name, name)
              .... raise AttributeError( msg)
              Traceback (most recent call last):
              File "<console>" , line 0, in <module>
              File "<console>" , line 0, in __getattr__
              AttributeError: A has no attribute 'attribute'.


              The docs state:
              "Called when an attribute lookup has not found the attribute in the
              usual places (i.e. it is not an instance attribute nor is it found in
              the class tree for self). name is the attribute name. This method
              should return the (computed) attribute value or raise an
              AttributeError exception."

              Can anyone explain why this is happening? I can hack a work-around,
              but even then I could use some tips on how to raise the 'real'
              exception so debugging isn't guesswork.


              Cheers,

              - Rafe

              Comment

              • Rafe

                #8
                Re: @property decorator doesn't raise exceptions

                On Oct 24, 9:58 am, Peter Otten <__pete...@web. dewrote:
                Rafe wrote:
                On Oct 24, 2:21 am, Christian Heimes <li...@cheimes. dewrote:
                Rafewrote:
                Hi,
                >
                I've encountered a problem which is making debugging less obvious than
                it should be. The @property decorator doesn't always raise exceptions.
                It seems like it is bound to the class but ignored when called. I can
                see the attribute using dir(self.__clas s__) on an instance, but when
                called, python enters __getattr__. If I correct the bug, the attribute
                calls work as expected and do not call __getattr__.
                >
                I can't seem to make a simple repro. Can anyone offer any clues as to
                what might cause this so I can try to prove it?
                >
                You must subclass from "object" to get a new style class. properties
                don't work correctly on old style classes.
                >
                Christian
                >
                All classes are a sub-class of object. Any other ideas?
                >
                Hard to tell when you don't give any code.
                >
                >class A(object):
                >
                ...     @property
                ...     def attribute(self) :
                ...             raise AttributeError
                ...     def __getattr__(sel f, name):
                ...             return "nobody expects the spanish inquisition"
                ...>>A().attrib ute
                >
                'nobody expects the spanish inquisition'
                >
                Do you mean something like this? I don't think the __getattr__() call canbe
                avoided here.
                >
                Peter

                Peter nailed it, thanks! I thought __getattr__ was a symptom, not a
                cause of the misleading exceptions. Here is a complete repro:


                The expected behavior...
                >>class A(object):
                .... @property
                .... def attribute(self) :
                .... raise AttributeError( "Correct Error.")
                >>A().attribu te
                Traceback (most recent call last):
                File "<console>" , line 0, in <module>
                File "<console>" , line 0, in attribute
                AttributeError: Correct Error.


                The misleading/unexpected behavior...
                >>class A(object):
                .... @property
                .... def attribute(self) :
                .... raise AttributeError( "Correct Error.")
                .... def __getattr__(sel f, name):
                .... cls_name = self.__class__. __name__
                .... msg = "%s has no attribute '%s'." % (cls_name, name)
                .... raise AttributeError( msg)
                >>A().attribu te
                Traceback (most recent call last):
                File "<console>" , line 0, in <module>
                File "<console>" , line 0, in __getattr__
                AttributeError: A has no attribute 'attribute'.


                Removing @property works as expected...
                >>class A(object):
                .... def attribute(self) :
                .... raise AttributeError( "Correct Error.")
                .... def __getattr__(sel f, name):
                .... cls_name = self.__class__. __name__
                .... msg = "%s has no attribute '%s'." % (cls_name, name)
                .... raise AttributeError( msg)
                >>A().attribute () # Note the '()'
                Traceback (most recent call last):
                File "<console>" , line 0, in <module>
                File "<console>" , line 0, in attribute
                AttributeError: Correct Error.


                The docs seem to suggest this is impossible:
                "Called when an attribute lookup has not found the attribute in the
                usual places (i.e. it is not an instance attribute nor is it found in
                the class tree for self). name is the attribute name. This method
                should return the (computed) attribute value or raise an
                AttributeError exception."

                Can anyone explain why this is happening? Is it a bug? I can write a
                workaround to detect this by comparing the attribute name passed
                __getattr__ with dir(self.__clas s__) = self.__dict__.k eys(), but how
                can I raise the expected exception?


                Thanks,

                - Rafe

                Comment

                • greg

                  #9
                  Re: @property decorator doesn't raise exceptions

                  Rafe wrote:
                  The docs seem to suggest this is impossible:
                  "Called when an attribute lookup has not found the attribute in the
                  usual places (i.e. it is not an instance attribute nor is it found in
                  the class tree for self).
                  Getting an AttributeError is the way that the interpreter
                  machinery tells that the attribute wasn't found. So when
                  your property raises an AttributeError, this is
                  indistinguishab le from the case where the property wasn't
                  there at all.

                  To avoid this you would have to raise some exception
                  that doesn't derive from AttributeError.

                  --
                  Greg

                  Comment

                  • Rafe

                    #10
                    Re: @property decorator doesn't raise exceptions

                    On Oct 24, 1:47 am, Rafe <rafesa...@gmai l.comwrote:
                    Hi,
                    >
                    I've encountered a problem which is making debugging less obvious than
                    it should be. The @property decorator doesn't always raise exceptions.
                    It seems like it is bound to the class but ignored when called. I can
                    see the attribute using dir(self.__clas s__) on an instance, but when
                    called, python enters __getattr__. If I correct the bug, the attribute
                    calls work as expected and do not call __getattr__.
                    >
                    I can't seem to make a simple repro. Can anyone offer any clues as to
                    what might cause this so I can try to prove it?
                    >
                    Cheers,
                    >
                    - Rafe

                    Peter Oten pointed me in the right direction. I tried to reply to his
                    post 2 times and in spite of GoogleGroups reporting the post was
                    successful, it never showed up. Here is the repro:

                    The expected behavior...
                    >>class A(object):
                    .... @property
                    .... def attribute(self) :
                    .... raise AttributeError( "Correct Error.")
                    >>A().attribu te
                    Traceback (most recent call last):
                    File "<console>" , line 0, in <module>
                    File "<console>" , line 0, in attribute
                    AttributeError: Correct Error.


                    The misleading/unexpected behavior...
                    >>class A(object):
                    .... @property
                    .... def attribute(self) :
                    .... raise AttributeError( "Correct Error.")
                    .... def __getattr__(sel f, name):
                    .... cls_name = self.__class__. __name__
                    .... msg = "%s has no attribute '%s'." % (cls_name, name)
                    .... raise AttributeError( msg)
                    >>A().attribu te
                    Traceback (most recent call last):
                    File "<console>" , line 0, in <module>
                    File "<console>" , line 0, in __getattr__
                    AttributeError: A has no attribute 'attribute'.


                    Removing @property works as expected...
                    >>class A(object):
                    .... def attribute(self) :
                    .... raise AttributeError( "Correct Error.")
                    .... def __getattr__(sel f, name):
                    .... cls_name = self.__class__. __name__
                    .... msg = "%s has no attribute '%s'." % (cls_name, name)
                    .... raise AttributeError( msg)
                    >>A().attribute () # Note the '()'
                    Traceback (most recent call last):
                    File "<console>" , line 0, in <module>
                    File "<console>" , line 0, in attribute
                    AttributeError: Correct Error.


                    I never suspected __getattr__ was the cause and not just a symptom.
                    The docs seem to indicate __gettattr__ should never be called when the
                    attribute exisits in the class:
                    "Called when an attribute lookup has not found the attribute in the
                    usual places (i.e. it is not an instance attribute nor is it found in
                    the class tree for self). name is the attribute name. This method
                    should return the (computed) attribute value or raise an
                    AttributeError exception."

                    Is this a bug? Any idea why this happens? I can write a hack in to
                    __getattr__ in my class which will detect this, but I'm not sure how
                    to raise the expected exception.


                    Cheers,

                    - Rafe

                    Comment

                    • Duncan Booth

                      #11
                      Re: @property decorator doesn't raise exceptions

                      Rafe <rafesacks@gmai l.comwrote:
                      Peter Oten pointed me in the right direction. I tried to reply to his
                      post 2 times and in spite of GoogleGroups reporting the post was
                      successful, it never showed up.
                      This is the third variant on your message that has shown up in the
                      newsgroup.

                      Please be aware that messages take time to propogate through usenet: don't
                      repost just because Google groups hasn't yet got around to displaying your
                      message. If it says the post was successful then the post was successful.
                      Just be patient a bit longer for it to become visible to you.

                      Comment

                      • Peter Otten

                        #12
                        Re: @property decorator doesn't raise exceptions

                        Rafe wrote:
                        Can anyone explain why this is happening?
                        When an attribute error is raised that is an indication that the requested
                        attribute doesn't exist, and __getattr__() must be called as a fallback.
                        I can hack a work-around,
                        but even then I could use some tips on how to raise the 'real'
                        exception so debugging isn't guesswork.
                        Look at the problem again, maybe you can find a solution without
                        __getattr__() and use only properties.

                        Otherwise you have to wrap your getter with something like

                        try:
                        ...
                        except AttributeError:
                        raise BuggyProperty, None, original_traceb ack


                        If you put that functionality into a decorator you get:

                        import sys

                        class BuggyProperty(E xception):
                        pass

                        def safe_getter(get ):
                        def safe_get(self):
                        try:
                        return get(self)
                        except AttributeError:
                        t, e, tb = sys.exc_info()
                        raise BuggyProperty(" AttributeError in getter %s(); "
                        "giving original traceback"
                        % get.__name__), None, tb
                        return property(safe_g et)

                        class A(object):
                        @safe_getter
                        def attr(self):
                        return self.m(3)

                        def m(self, n):
                        if n 0:
                        return self.m(n-1)
                        raise AttributeError( "it's a bug")

                        def __getattr__(sel f, name):
                        return "<%s>" % name

                        A().attr

                        Peter

                        Comment

                        • Rafe

                          #13
                          Re: @property decorator doesn't raise exceptions

                          OT... Sorry about he spam.
                          Thanks for taking the time to post this Duncan.

                          I had the same thought. I have posted to this list before but never
                          experienced anything like this wait. I figured it was possible that I
                          hit "Reply to Author" the first time so I sent it again. I waited
                          about 8 hours before sending the third time (and I posted to
                          GoogleGroups support as well). Even then I didn't see my original
                          post. I'm surprised it took so long to update. Next time I'll just
                          make sure the post was made successfully and wait...as long as it
                          takes.


                          Cheers,

                          - Rafe


                          On Oct 26, 4:23 pm, Duncan Booth <duncan.bo...@i nvalid.invalidw rote:
                          Rafe<rafesa...@ gmail.comwrote:
                          Peter Oten pointed me in the right direction. I tried to reply to his
                          post 2 times and in spite of GoogleGroups reporting the post was
                          successful, it never showed up.
                          >
                          This is the third variant on your message that has shown up in the
                          newsgroup.
                          >
                          Please be aware that messages take time to propogate through usenet: don't
                          repost just because Google groups hasn't yet got around to displaying your
                          message. If it says the post was successful then the post was successful.
                          Just be patient a bit longer for it to become visible to you.

                          Comment

                          • Rafe

                            #14
                            Re: @property decorator doesn't raise exceptions

                            On Oct 27, 2:47 pm, Peter Otten <__pete...@web. dewrote:
                            Rafewrote:
                            Can anyone explain why this is happening?
                            >
                            When an attribute error is raised that is an indication that the requested
                            attribute doesn't exist, and __getattr__() must be called as a fallback.
                            >
                            I can hack a work-around,
                            but even then I could use some tips on how to raise the 'real'
                            exception so debugging isn't guesswork.
                            >
                            Look at the problem again, maybe you can find a solution without
                            __getattr__() and use only properties.
                            >
                            Otherwise you have to wrap your getter with something like
                            >
                            try:
                                ...
                            except AttributeError:
                                raise BuggyProperty, None, original_traceb ack
                            >
                            If you put that functionality into a decorator you get:
                            >
                            import sys
                            >
                            class BuggyProperty(E xception):
                                pass
                            >
                            def safe_getter(get ):
                                def safe_get(self):
                                    try:
                                        return get(self)
                                    except AttributeError:
                                        t, e, tb = sys.exc_info()
                                        raise BuggyProperty(" AttributeError in getter %s(); "
                                                        "giving original traceback"
                                                        % get.__name__), None, tb
                                return property(safe_g et)
                            >
                            class A(object):
                                @safe_getter
                                def attr(self):
                                    return self.m(3)
                            >
                                def m(self, n):
                                    if n 0:
                                        return self.m(n-1)
                                    raise AttributeError( "it's a bug")
                            >
                                def __getattr__(sel f, name):
                                    return "<%s>" % name
                            >
                            A().attr
                            >
                            Peter

                            Thanks for the idea Peter. What confuses me is why this only happens
                            to @Property (and I assume other decorator related bindings?). Does it
                            have something to do with the way the class gets built? 'Normal'
                            attributes will raise AttributeErrors as expected, without triggering
                            __getattr__(). Considering this is a built-in decorator, it would be
                            nice if this behavior was fixed if possible.

                            Unfortunately, I need __getattr__() because my class is a wrapper (it
                            is delegating calls to another object when attributes aren't found in
                            the class). As a hack, I am testing the passed attr name against the
                            instance, class and super-class attributes. If there is a match, I
                            assume it is an error and raise an exception which points the
                            developer in the right direction. It isn't ideal, but it helps.

                            You're code is better, as it displays the 'real' traceback, but I need
                            to know more about the effects of using an exception which is not an
                            AttrbiuteError. Which brings me to my next question...

                            In case it isn't obvious, I'm fairly new to Python (and this level of
                            programming in general). I've been wondering about the optimal use of
                            custom exceptions. Up until now, I've been sticking to the built-in
                            exceptions, which seem to work in 90% of situations. Are you aware of
                            any resources which talk about this aspect of programming (more about
                            theory than code)?


                            Thanks again,

                            - Rafe

                            Comment

                            • Peter Otten

                              #15
                              Re: @property decorator doesn't raise exceptions

                              Rafe wrote:
                              Thanks for the idea Peter. What confuses me is why this only happens
                              to @Property (and I assume other decorator related bindings?). Does it
                              have something to do with the way the class gets built? 'Normal'
                              attributes will raise AttributeErrors as expected, without triggering
                              __getattr__(). Considering this is a built-in decorator, it would be
                              nice if this behavior was fixed if possible.
                              Normal attributes either exist, and then they don't raise an AttributeError,
                              or they don't exist, and then __getattr__() *is* triggered. The problem has
                              nothing to do with decorators. It is just that properties invoke custom
                              code, and Python currently has no way of finding out whether an
                              AttributeError was raised accidentally by a buggy getter or whether it is
                              meant to signal that the attribute wasn't found in the class hierarchy and
                              now should be calculated by __getattr__().

                              I guess (without looking into the C source) that it could be changed to meet
                              your intuition but that it would complicate the implementation.

                              Peter

                              Comment

                              Working...