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

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

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

You Might Also Like