Reputation: 113
I don't know how to explain my question, so I will do the best I can.
I have base class Price
:
class Price:
def __init__(self, contractid, *args, **kwargs):
self.contractid = contractid
I have a bunch of classes that derive from Price
. Each subclass requires the arguments Price
does, plus some additional arguments. For example...
class BestAvailablePrice(Price):
def __init__(self, contractid, sell_type)
super().__init__(contractid)
self.sell_type = sell_type
class MaxInvestedPrice(Price):
def __init__(self, contractid, n_shares, cumulative=True)
super().__init__(contractid)
self.n_shares = n_shares
self.cumulative = cumulative
....
The problem with using super().__init__(contractid)
is that if Price.__init__()
changes, I need to update all of my subclasses as well. For example, if I decide Price also needs a marketid
argument:
class Price:
# new __init__ method includes marketid argument
def __init__(self, contractid, marketid, *args, **kwargs):
self.contractid = contractid
self.marketid = marketid
Then I am stuck going back and updating all of the subclasses as well.
Something I've been doing has been to declare a setup_init_args(*args, **kwargs)
method, and defining the additional parameters in there:
class Price:
def __init__(self, contractid, *args, **kwargs):
self.contractid = contractid
self.setup_init_args(*args, **kwargs)
def setup_init_args(*args, **kwargs)
pass # overridden in subclasses
class BestAvailablePrice:
def setup_init_args(sell_type)
self.sell_type = sell_type
This gets my base object's attributes set correctly, and I can use a factory method to instantiate my objects:
marketid = 1000
contractid = 2020
def get_price_object(obj, *args, **kwargs):
return obj(marketid, contractid, *args, **kwargs)
bap = get_price_object(BestAvailablePrice, sell_type = 'sell')
If I change the base class's init method, I only need to change it in the factory method. This works and I will get the correct object, but...
I could do:
def get_price_object(obj, *args, **kwargs) -> Price:
but then it thinks I'm only getting a Price object. I've tried using TypeVar from python's typing library, but I couldn't seem to get this to work. Setting something with # type: BestAvailablePrice
is a workaround but not ideal.
BestAvailablePrice
When I type bap = get_price_object(BestAvailablePrice, )
I would love to see the additional arguments needed for a BestAvailablePrice object. Is there any way to get that to show up? I have dozens of price types, and it becomes difficult to remember which class requires which arguments.
Upvotes: 1
Views: 1780
Reputation: 530970
The subclasses don't have to duplicate the named parameters from Price
, if you agree to always use keyword arguments when calling. Remember, any keyword argument it doesn't recognize is added to kwargs
to be passed along. You can insist upon keyword arguments being used by making all of __init__
's parameters keyword-only.
class Price:
def __init__(self, *, contractid, **kwargs):
super().__init__(**kwargs)
self.contractid = contractid
class BestAvailablePrice(Price):
def __init__(self, *, sell_type, **kwargs)
super().__init__(**kwargs)
self.sell_type = sell_type
class MaxInvestedPrice(Price):
def __init__(self, *, n_shares, cumulative=True, **kwargs)
super().__init__(**kwargs)
self.n_shares = n_shares
self.cumulative = cumulative
Note that Price.__init__
should also use super
, to handle multiple inheritance where Price
is not necessarily the last class before object
in the MRO. If you do things properly, **kwargs
will be empty when object.__init__
is called.
Regarding your factory method, you can add an appropriate type hint:
from typing import TypeVar, Type
P = TypeVar('P', bound=Price)
def get_price_object(obj: Type[P] , *args, **kwargs) -> P:
though that may not help your IDE suggest expected parameter names.
Upvotes: 5