AttributeError in "with" statement (3.2.2)

Eric Snow ericsnowcurrently at gmail.com
Wed Dec 14 11:56:10 EST 2011


On Tue, Dec 13, 2011 at 11:05 PM, Eric Snow <ericsnowcurrently at gmail.com> wrote:
> On Tue, Dec 13, 2011 at 10:42 PM, Steve Howell <showell30 at yahoo.com> wrote:
>> I'm using Python 3.2.2, and the following program gives me an error
>> that I don't understand:
>>
>> class Foo:
>>  pass
>>
>> foo = Foo()
>> foo.name = "Steve"
>>
>> def add_goodbye_function(obj):
>>  def goodbye():
>>    print("goodbye " + obj.name)
>>  obj.goodbye = goodbye
>>
>> add_goodbye_function(foo)
>> foo.goodbye() # outputs goodbye Steve
>> foo.__exit__ = foo.goodbye
>> foo.__exit__() # outputs goodbye Steve
>>
>> with foo: # fails with AttributeError:  __exit__
>>  print("doing stuff")
>>
>> I am dynamically adding an attribute __exit__ to the variable foo,
>> which works fine when I call it directly, but it fails when I try to
>> use foo as the expression in the with statement.  Here is the full
>> output:
>>
>>> python3 with.coffee
>> goodbye Steve
>> goodbye Steve
>> Traceback (most recent call last):
>>  File "with.coffee", line 17, in <module>
>>    with foo: # fails with AttributeError:
>> AttributeError: __exit__
>>
>> What am I doing wrong?
>
> That is a tricky one.
>
> As with many of the special methods (start and end with __) in Python,
> the underlying mechanism in the interpreter is directly pulling the
> function from the class object.  It does not look to the instance
> object for the function at any time.  See
> http://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes.
>
> -eric

Incidently, if you add the function directly to the class, everything works out:

  class Foo(object): # or "class Foo:" under Python 3
      pass

  foo = Foo()
  foo.name = "Steve"

  def add_goodbye_function(cls):
      def goodbye(self, *args, **kwargs):
          print("goodbye " + self.name)
      cls.goodbye = goodbye

  add_goodbye_function(type(foo))
  foo.goodbye() # outputs goodbye Steve
  Foo.__exit__ = foo.goodbye
  foo.__exit__() # outputs goodbye Steve
  Foo.__enter__ = (lambda self: None)

  with foo:
      print("doing stuff")

However, perhaps a better approach would be to put the different
pieces directly into the class:

  class Foo: # python 3
      def __init__(self, name):
          self.name = name
      def goodbye(self):
          print("goodbye " + self.name)
      def __enter__(self):
          pass
      def __exit__(self, *args, **kwargs):
          self.goodbye()

  foo = Foo("Steve")
  foo.goodbye() # outputs goodbye Steve
  foo.__exit__() # outputs goodbye Steve
  with foo:
      print("doing stuff")

If you want to be more dynamic about it you can do it, but it involves
black magic.  Chances are really good that being explicit through your
class definition is the right approach.

-eric



More information about the Python-list mailing list