Search code examples
pythonpython-3.xinheritancemultiple-inheritance

Python multiple classes inheritance error. How can I initialize them correctly?


I'm trying to build a python3 module for an HTTP RESTful API that I've coded.

My idea was to create a base class that should have a request.Session() attribute so I can assign an authorization token header to that and don't worry about it anymore and also a logger function and so on. The problem is that a class called User inherits from 2 classes: PublicUser and base and I can't initialize them correctly.

It's the first time that I'm working with inherited class so obviously I'm missing something.

This is my folder structure:

examplemodule/
  |--> __init__.py
  |--> classes/
        |-->  base.py
        |-->  user.py

base.py

from requests import Session
from requests.sessions import session


class Logger:
    def __init__(self):
        pass

    def log(self, message):
        print(message)


class Base:
    def __init__(self, token=None):
        if not hasattr(self, 'logger'):
            self.logger = Logger()
        if not hasattr(self, 'session'):
            self.session = Session()
            self.session.headers.update(
                {'authorization': 'Token {}'.format(token)}
            )
            # Try to login to see if token is valid, if not raise exception
            # If token is valid then the retrieved user json is saved
            self._user = {
                'id': 1,
                'username': 'test1',
                'email': 'test@test.com'
            }

user.py

from .base import Base

PUBBLIC_USER_ATTRS = ['id', 'username']
PRIVATE_USER_ATTRS = ['email']


class PublicUser:
    def __init__(self, user):
        for k in PUBBLIC_USER_ATTRS:
            setattr(self, k, user[k])


class User(Base, PublicUser):
    def __init__(self, token=None):
        super(Base, self).__init__(token=token)
        super(PublicUser, self).__init__(self._user)
        for k in PRIVATE_USER_ATTRS:
            setattr(self, k, self._user[k])

__init__.py

from .classes.user import User

then to test my module I run:

import examplemodule
examplemodule.User(token='')

but unfortunately I get a TypeError at super(Base, self).__init__(token=token)

TypeError: super() takes no keyword arguments

What is the best way to get through this?


Solution

  • super is meant for cooperative inheritance, where all involved classes are using super in a way that ensures all necessary methods are called. That means super should also be used by base classes, even if all they inherit from is object.

    class Base:
        def __init__(self, *, token=None, **kwargs):
            super().__init__(**kwargs)
            if not hasattr(self, 'logger'):
                self.logger = Logger()
            if not hasattr(self, 'session'):
                self.session = Session()
                self.session.headers.update(
                    {'authorization': 'Token {}'.format(token)}
                )
                # Try to login to see if token is valid, if not raise exception
                # If token is valid then the retrieved user json is saved
                self._user = {
                    'id': 1,
                    'username': 'test1',
                    'email': 'test@test.com'
                }
    
    class PublicUser:
        def __init__(self, *, id, username, **kwargs):
            super().__init__(**kwargs)
            self.id = id
            self.username = username
            
    class User(Base, PublicUser):
        def __init__(self, *, email, **kwargs):
            super().__init__(**kwargs)
            self.email = email
    
    
    u = User(token='...', id='...', username='...', email='...')
    

    User.__init__ only has to make one call to super().__init__, knowing that its base classes also use super().__init__ to always call the next __init__, until reaching the end of the MRO. You start with User.__init__, which calls Base.__init__, which calls PublicUser.__init__ (not object.__init__), which finally calls object.__init__.

    At each step, the remaining keyword arguments are split between the "known" arguments, which are handled, and the "unknown" arguments, which are passed up the line. Eventually, all keyword objects should be extracted and handled by the time object.__init__ is called.

    See https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ for a more thorough explanation of how this works in practiced (in particular, why keyword arguments are preferred over positional arguments).