简体   繁体   中英

Python: Subclasses with the same set of __init__ arguments as base class, plus additional arguments, without using super()

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

Downsides:

1. My IDE doesn't know what type of object I'm getting.

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.

2. My IDE doesn't display expected arguments in the code-completion for 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM