Python's super() considered super!

Ryan Kelly ryan at rfk.id.au
Fri May 27 19:57:56 EDT 2011


On Fri, 2011-05-27 at 15:05 +0000, Duncan Booth wrote:
> sturlamolden <sturlamolden at yahoo.no> wrote:
> > I really don't like the Python 2 syntax of super, as it violates
> > the DRY principle: Why do I need to write super(type(self),self)
> > when super() will do? Assuming that 'self' will always be named
> > 'self' in my code, I tend to patch __builtins__.super like this:
> > 
> > import sys
> > def super():
> >     self = sys._getframe().f_back.f_locals['self']
> >     return __builtins__.super(type(self),self)
> > 
> > This way the nice Python 3.x syntax can be used in Python 2.x.
> > 
> > 
> Oh dear, you haven't thought this one through.
>
> ...snip...
>
> >>> C().foo()
> ... infinite recursion follows ...
> 
> Oops. There's a reason why Python 2 requires you to be explicit about 
> the class; you simply cannot work it out automatically at run time. 
> Python 3 fixes this by working it out at compile time, but for Python 2 
> there is no way around it.

Oh?  There's not much that can't be done at runtime if you're willing to
work hard enough.  Let me propose the following awful awful hack:


  import sys

  _builtin_super = __builtins__.super

  _sentinel = object()

  def _auto_super(typ=_sentinel,type_or_obj=_sentinel):
      """Automagically call correct super() at runtime"""
      #  Infer the correct call if used without arguments.
      if typ is _sentinel:
          # We'll need to do some frame hacking.
          f = sys._getframe(1)
          # Get the first positional argument of the function.
          type_or_obj = f.f_locals[f.f_code.co_varnames[0]]
          # Get the MRO for investigation
          try:
              mro = type_or_obj.__mro__
          except AttributeError:
              try:
                  mro = type_or_obj.__class__.__mro__
              except AttributeError:
                  raise RuntimeError("super() used with old-style class")
          #  Now, find the class owning the currently-executing method.
          for typ in mro:
              for meth in typ.__dict__.itervalues():
                  if not isinstance(meth,type(_auto_super)):
                      continue
                  if meth.func_code is f.f_code:
                      # Aha!  Found you.
                      break
              else:
                  continue
              break
          else:
              raise RuntimeError("super() called outside a method")
      #  Now just dispatch to builtin super.
      if type_or_obj is not _sentinel:
          return _builtin_super(typ,type_or_obj)
      return _builtin_super(typ)


Now, try is with the following:

    class Base(object):
        def hello(self,msg):
            print "hello", msg

    class Sub1(Base):
        def hello(self,msg):
            print "gunna say it"
            super().hello(msg)

    class Sub2(Base):
        def hello(self,msg):
            print "yes I am"
            super().hello(msg)

    class Diamond(Sub1,Sub2):
        def hello(self,msg):
            print "here we go..."
            super().hello(msg)

    d = Diamond()
    d.hello("autosuper!")


And you get the expected output:

    here we go...
    gunna say it
    yes I am
    hello autosuper!


There may well be some cases where this breaks down, but it seems to do
the right thing in simple cases.


Not that I'm recommending anyone use this, of course...




   Ryan


-- 
Ryan Kelly
http://www.rfk.id.au  |  This message is digitally signed. Please visit
ryan at rfk.id.au        |  http://www.rfk.id.au/ramblings/gpg/ for details

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 198 bytes
Desc: This is a digitally signed message part
URL: <http://mail.python.org/pipermail/python-list/attachments/20110528/e28506e7/attachment.sig>


More information about the Python-list mailing list