Python Equivalent of Functools 'Partial' for a Class / Constructor

Python Equivalent of Functools 'Partial' for a Class / Constructor

I want to create a class that behaves like collections.defaultdict, without having the usage code specify the factory. EG: instead of

class Config(collections.defaultdict):
    pass

this:

Config = functools.partial(collections.defaultdict, list)

This almost works, but

isinstance(Config(), Config)

fails. I am betting this clue means there are more devious problems deeper in also. So is there a way to actually achieve this?

I also tried:

class Config(Object):
    __init__ = functools.partial(collections.defaultdict, list)
1

5 Answers

I don't think there's a standard method to do it, but if you need it often, you can just put together your own small function:

import functools
import collections


def partialclass(cls, *args, **kwds):

    class NewCls(cls):
        __init__ = functools.partialmethod(cls.__init__, *args, **kwds)

    return NewCls


if __name__ == '__main__':
    Config = partialclass(collections.defaultdict, list)
    assert isinstance(Config(), Config)
3

I had a similar problem but also required instances of my partially applied class to be pickle-able. I thought I would share what I ended up with.

I adapted fjarri's answer by peeking at Python's own collections.namedtuple. The below function creates a named subclass that can be pickled.

from functools import partialmethod
import sys

def partialclass(name, cls, *args, **kwds):
    new_cls = type(name, (cls,), {
        '__init__': partialmethod(cls.__init__, *args, **kwds)
    })

    # The following is copied nearly ad verbatim from `namedtuple's` source.
    """
    # For pickling to work, the __module__ variable needs to be set to the frame
    # where the named tuple is created.  Bypass this step in enviroments where
    # sys._getframe is not defined (Jython for example) or sys._getframe is not
    # defined for arguments greater than 0 (IronPython).
    """
    try:
        new_cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__')
    except (AttributeError, ValueError):
        pass

    return new_cls

If you actually need working explicit type checks via isinstance, you can simply create a not too trivial subclass:

class Config(collections.defaultdict):

    def __init__(self): # no arguments here
        # call the defaultdict init with the list factory
        super(Config, self).__init__(list)

You'll have no-argument construction with the list factory and

isinstance(Config(), Config)

will work as well.

At least in Python 3.8.5 it just works with functools.partial:

import functools

class Test:
    def __init__(self, foo):
        self.foo = foo
    
PartialClass = functools.partial(Test, 1)

instance = PartialClass()
instance.foo
1

Could use *args and **kwargs:

class Foo:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def printy(self):
        print("a:", self.a, ", b:", self.b)

class Bar(Foo):
    def __init__(self, *args, **kwargs):
        return super().__init__(*args, b=123, **kwargs)

if __name__=="__main__":
    bar = Bar(1)
    bar.printy()  # Prints: "a: 1 , b: 123"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

James H. Sterling
Author

James H. Sterling

James Sterling reports on renewable energy developments, climate policy, ecological conservation, and green tech innovations around the globe.