Reputation: 61
Let's say we have this class.
class MyConfig():
config_type: SomeEnum
retries: int
context: Context
This Context depends on the value of type meaning the context is dependent on its value. Which implementation of Context is best? why one is better than the other.
class MySpecificContext:
value: int
name: str
class OtherUnrelatedContext:
length: int
position: int
Contex = Union[MySpecificContex,OtherUnrelatedContext]
or
class Context:
pass
class MySpecificContext(Context):
value: int
name: str
class OtherUnrelatedContext(Context):
length: int
position: int
To be more specific. Let say you have a configuration class, this configuration will hold data that will be used to configure a process. This process will contain a few things that relate to all configurations, like retries
value. However, it is expected that based on config_type
there is an additional context that is required. This is highly related to the particular configuration type. Note that these classes only hold data and they are not expected to implement any behaviors in the future. However, it is expected that the SomeEnum enumeration will be extended and more "contexts" will be added at some point. Think of my config as immutable.
Upvotes: 4
Views: 1005
Reputation: 1594
They can be functionally equivalent, but there are a couple differences.
With inheritance, the set of types can be extended by the user. With the union, only the enumerated types are allowable. There are benefits for both depending on what you are trying to accomplish.
The inheritance approach requires you predict the common methods you wish to dynamically dispatch to and declare them in the base class. The union approach creates an interface ad-hoc based on the intersection of the methods of all the types in the type set.
With inheritance, all types in the set must be defined after the base type. This means you can't add an existing type to the type set. Unions do not have this problem.
You can't use unions in isinstance
checks until Python 3.10.
Based on your use case, I think unions would make more sense because:
Upvotes: 1
Reputation: 71454
Here's how I might approach it:
from enum import Enum
from typing import Generic, NamedTuple, TypeVar, Union
class MySpecificContext(NamedTuple):
value: int
name: str
class OtherUnrelatedContext(NamedTuple):
length: int
position: int
Context = Union[MySpecificContext, OtherUnrelatedContext]
_T = TypeVar("_T", bound=Context)
class SomeEnum(Enum):
MY_SPECIFIC = 1
OTHER_UNRELATED = 2
config_types = {
MySpecificContext: SomeEnum.MY_SPECIFIC,
OtherUnrelatedContext: SomeEnum.OTHER_UNRELATED,
}
class MyConfig(Generic[_T]):
def __init__(self, context: _T, retries: int):
self.retries = retries
self.context = context
@property
def config_type(self) -> SomeEnum:
return config_types[type(self.context)]
Encoding the kind of type relationship you're talking about between two attributes is tricky; I can't think of any way to do it without having different MyConfig
subclasses. On the other hand, if you can derive one from the other, it doesn't make sense to have them be independent attributes in the first place, which is why I've just turned config_type
into a property that's derived from config
. (This is assuming you're wedded to this interface -- I would just get rid of the enum and the mapping and use the context type directly, so as to have less code to maintain.)
Upvotes: 2