1

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

Community
  • 1
  • 1
The Wanderer
  • 3,051
  • 6
  • 29
  • 53

2 Answers2

2

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

Josh J
  • 6,813
  • 3
  • 25
  • 47
  • I am not sure if I understand. From this implementation, how can you say it is doing multiple inheritance? And if so, what is the second class that it is inheriting from? – The Wanderer Feb 17 '17 at 04:35
  • 1
    @TheWanderer - if you were to do somewhere something like `class YourClass(LoginRequiredMixin, MixinWithDispatch)` and if `MixinWithDispatch` extends `object` and has `dispatch()` method, that `super` call from `LoginRequiredMixin` would resolve to `MixinWithAbc.dispatch()`. If you were to use `LoginRequiredMixin` alone it would break as expected unless you mess with builtins. And, yeah, relying on multiple inheritance and MRO is also a sign of bad framework design - not as much as with modifying builtins but there really isn't any reason to do it this way either. – zwer Feb 17 '17 at 04:57
  • @zwer that is a very good response. Thank you. Can you please edit your answer with this, so that I can mark it as resolved? – The Wanderer Feb 17 '17 at 05:06
  • 1
    There you go, complete with am example of how to get your code to _work_ the 'same' way. – zwer Feb 17 '17 at 05:23
  • @zwer and @Josh : Interestingly `django` encourages this with `class based views` and `mixins` :O – The Wanderer Feb 17 '17 at 08:23
  • 1
    @The Wanderer - interestingly, Django, despite its tagline, doesn't seem to be for perfectionists after all. Relying on quirks of a language (luckily, MRO is at this point pretty much deterministic, which wasn't the case in early Python days) does not a good framework make. Besides breaking quite a number of _unofficial_ OOP **good practices™** itself, it can instill bad practices in its users. They could've just as easily create a core `DjangoMixin` class and have users extend it so that a clear 'ownership' is preserved... Alas, they've created quite a popular framework, so who am I to judge? – zwer Feb 17 '17 at 13:01
1

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.

zwer
  • 24,943
  • 3
  • 48
  • 66
  • Thank you for your answer. What you say makes some sense, but I seriously doubt `django` is doing this. If that were the case they would have to implement many such `abc`s .. moreover I couldn't find anything like this in the `django` github repo – The Wanderer Feb 17 '17 at 03:23
  • 1
    You're right, Django devs are good boys after all. I apologize to whoever I might have offended by implying that they were messing with builtins. The code in the presentation either doesn't work, or whoever did the presentation messed with builtins instead. – zwer Feb 17 '17 at 03:58