Gleiry Agustin
Gleiry Agustin

Reputation: 61

Union vs Inheritance in python implementation

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

Answers (2)

Kaleb Barrett
Kaleb Barrett

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.

    • The implicit interface is a double-edged sword as changes to the union may cause the interface to change, making existing correct code become incorrect; i.e. it's potentially fragile.
  • 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:

  1. You don't seem to need to allow the user to extend the type set.
  2. You are not dynamically-dispatching to a common method of the types in the type set, you are always disambiguating before using.

Upvotes: 1

Samwise
Samwise

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

Related Questions