The Wanderer
The Wanderer

Reputation: 3271

calling super on base class python/django

Before you mark it as duplicate, let me state that I understand how super works and I have read these three links:

What does 'super' do in Python?
Understanding Python super() with __init__() methods
http://python-history.blogspot.nl/2010/06/method-resolution-order.html

This is how super is supposed to work in case of baseclasses:

class X(object):
    def __init__(self):
        print "calling init from X"
        super(X, self).__init__()


class Y(object):
    def abc(self):
        print "calling abc from Y"
        super(Y, self).abc()

a = X()
# prints "calling init from X"  (works because object possibly has an __init__ method)

b = Y()
b.abc()
# prints "calling abc from Y" and then
# throws error "'super' object has no attribute 'abc'" (understandable because object doesn't have any method named abc)

Question: In django core implementation, there are several places where they call methods using super on classes inheriting from object (case Y in my example above). For example: can someone explain me why this code works?

from django.core.exceptions import PermissionDenied

class LoginRequiredMixin(object):

    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated():
            raise PermissionDenied

        return super(LoginRequiredMixin, self).\
            dispatch(request, *args, **kwards)   # why does this work? 

Ref: copied this code from this talk: https://youtu.be/rMn2wC0PuXw?t=403

Upvotes: 1

Views: 1788

Answers (2)

Josh J
Josh J

Reputation: 6893

It works because LoginRequiredMixin is intended to be used in a multiple inheritance scenario. In this case, the MRO would resolve to the object in the same level of the class hierarchy. (The other type specified alongside LoginRequiredMixin)

You can see below that the order matters too

Python 2.7.12 (default, Oct 11 2016, 05:20:59)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.38)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>>
>>> class Y(object):
...     def abc(self):
...         print "calling abc from Y"
...         super(Y, self).abc()
...
>>> class Z(object):
...     def abc(self):
...         print "calling abc from Z"
...
>>> class YZ(Y, Z):  # multiple inheritance
...     pass
...
>>> c = YZ()
>>> c.abc()
calling abc from Y
calling abc from Z
>>>
>>> class ZY(Z,Y): pass
...
>>> ZY().abc()
calling abc from Z

ZY calls Z.abc based on MRO so Y.abc is ignored

Upvotes: 2

zwer
zwer

Reputation: 25789

Have you tested all the code in that presentation? The code above may work only if somebody is doing something that shouldn't be done (and in some Python implementations is strictly forbidden) behind the scenes - modifying Python builtins.

You can do it, too - before you get to execute or build anything, do this:

import __builtin__

class custom_object(object):
    def abc(self):
        print "calling abc from custom_object"

__builtin__.object = custom_object

Then try building your X and Y types and see how it goes.

P.S. Just to emphasize something, this is presented for educational purposes only - DO NOT USE THIS! There really is no need to ever resort to this and you'd just make life a living hell for developers who might need to untangle your code in the future.

UPDATE:

As per Josh J's suggestion above, LoginRequiredMixin might not be intended to be used as a standalone class but added in a multiple-inheritance chain. In that scenario a base class, which implements dispatch() method and extends object, can be glued to the LoginRequiredMixin. Taking into the account how Python's does MRO, super() from LoginRequiredMixin will actually refer to the 'glued' class' methods.

You can get your code to behave the same like:

class Y(object):
    def abc(self):
        print "calling abc from Y"
        super(Y, self).abc()

class Z(object):
    def abc(self):
        print "calling abc from Z"

class YZ(Y, Z):  # multiple inheritance
    pass

c = YZ()
c.abc()
# prints "calling abc from Y" and then
# prints "calling abc from Z"

It still is a sign of bad framework design (just consider how long it took us to get to the bottom of the issue based on quite simple code), just slightly less atrocious than messing with builtins. So, if you're designing your framework one day DO NOT DO THIS, EITHER.

Upvotes: 1

Related Questions