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.
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.)