Search code examples
pythonpython-3.xinheritancefunctools

Partially initializing object attributes using functools.partial in python


I'm wondering whether it is a valid use case of functools.partial to partially initialize object attributes in python. Let's say I have some base class A that has three object-level attributes (attr1, attr2, attr3):

class A:
    def __init__(self, attr1, attr2, attr3):
        self.attr1 = attr1
        self.attr2 = attr2
        self.attr3 = attr3
        
    def do_something(self):
        pass

Now I want to create objects based on this base class for which one of the attributes (say attr1) is always fixed, but the other two may vary. One way I can think of doing this is to define a class that inherits from base class A and has a fixed class-level attribute which is used during initialization, something like this:

class PartialA(A):
    attr1="foo" # fixed class-level attribute
    
    def __init__(self, attr2, attr3):
        super().__init__(self.attr1, attr2, attr3)

I can then create objects by specifying only the varying object-level attributes, like so:

partial_a = PartialA(attr2="baz", attr3="baz")

Another way to achieve a similar behavior would be to use functools.partial to partially initialize the __init__ of the base class A and then create objects off of it:

from functools import partial

PartialA = partial(A, attr1="foo")

partial_a = PartialA(attr2="bar", attr3="baz")

Are both approaches equally valid, or are there dangers/drawbacks to the approach using functools.partial in this way that I am currently not aware of? Would be happy to hear your opinions!

I tried both of the approaches outlined above and so far, I can not really see any difference in terms of behavior. But there might well be some reasons to favor one over another that I'm currently unaware of.


Solution

  • In both cases, you are basically defining factory functions to create instances of A. I would take a third route, an alternate constructor implemented using a class method.

    class A:
        def __init__(self, attr1, attr2, attr3):
            self.attr1 = attr1
            self.attr2 = attr2
            self.attr3 = attr3
    
        @classmethod
        def with_foo(cls, attr2, attr3):
            return cls("foo", attr2, attr3)
            
        def do_something(self):
            pass
    
    a = A.with_foo("bar", "baz")  # a = A("foo", "bar", "baz")
    

    One major difference between this and your two attempts is that you can't override how A.with_foo sets the first attribute. (With the class, you can simply redefine PartialA.attr1 before creating an instance, plus you now have a weird second class that doesn't need to exist. With partial, you can still pass your own value of attr1 as a keyword argument.)