Hello I'd like to use a class method that requires multiple arguments as well as an attribute from the class' __init__().
However, because I have to make this call several times, I'd like to avoid providing multiple arguments to each call. Therefore, I thought to make a child class which supplies the arguments such as below. However, I don't want to override the parent method in the child class.
Here are the two classes with an example call:
class Parent:
def __init__(self, parent_arg: dict) -> None:
self.parent_arg = parent_arg
def create_child(self, child_arg):
return Child(child_arg, self.parent_arg)
def method_a(self, method_arg: str, child_arg: str) -> dict:
self.method_b(method_arg, child_arg)
def method_b(self, method_arg: str, child_arg: str) -> str:
print(f"{self.parent_arg}, {child_arg}, and {method_arg}")
class Child(Parent):
def __init__(self, child_arg: str, parent_arg: dict) -> None:
super().__init__(parent_arg)
self.child_arg = child_arg
def method_a(self, method_arg: str = None) -> dict:
return super().method_a(method_arg, self.child_arg)
def method_b(self, method_arg: str) -> str:
return super().method_b(method_arg, self.child_arg)
if __name__ == "__main__":
parent_instance = Parent(parent_arg="Parent Argument")
child_instance = parent_instance.create_child(child_arg="Child Argument")
child_instance.method_a(method_arg="Method Argument")
Again, I'd like to be able to call the same method names from Parent and Child instances. However, I'd like the computation to be done in the parent class methods. Thank you so so much for any and all help.
Here's one solution without inheritance, however, I'm curious if there's a more elegant solution.
class Child:
def __init__(self, child_arg: str, parent_arg: dict) -> None:
self.parent = Parent(parent_arg)
self.child_arg = child_arg
def method_a(self, method_arg: str = None) -> dict:
return self.parent.method_a(method_arg, self.child_arg)
def method_b(self, method_arg: str) -> str:
return self.parent.method_b(method_arg, self.child_arg)
I think the answer you're looking for is don't override method_b in the Child class.
You might be thinking that the execution "goes" to the parent when you call super(), and that's not right. You're "in" the same object the entire time (the self). If you simple omit method_b from your child definition, the value of child_instance.method_b will be the parent's definition.
Basically, only override these methods in the child if you have additional or different behavior you want to implement.
EDIT based on OP's comment
Take a look at this. It lets you assign attributes to objects as you create them, so you can re-use them without having to provide them on every method call. It implements Parent so that it is unaware of / not concerned with the Child. All the Child does is override the methods so it can make use of the additional child_arg param.
class Parent:
def __init__(self, parent_arg: str) -> None:
self.parent_arg = parent_arg
def method_a(self, method_arg: str) -> None:
self.method_b(method_arg)
def method_b(self, method_arg: str) -> None:
print(f"self.parent_arg: {self.parent_arg}")
print(f"method_arg: {method_arg}")
class Child(Parent):
def __init__(self, child_arg: str, parent_arg: str) -> None:
super().__init__(parent_arg)
self.child_arg = child_arg
def method_a(self, method_arg: str) -> None:
super().method_a(method_arg)
def method_b(self, method_arg: str) -> None:
print(f"self.parent_arg: {self.parent_arg}")
print(f"self.child_arg: {self.child_arg}")
print(f"method_arg: {method_arg}")
if __name__ == "__main__":
parent_instance = Parent(parent_arg="Parent Argument")
print("parent_instance calling a:")
parent_instance.method_a("Method Argument")
print("child_instance calling a:")
child_instance = Child(
child_arg="Child Argument",
parent_arg="Parent Argument",
)
child_instance.method_a("Method Argument")
If you don't want to call the child's method, call the method from a specific class directly instead of going through self.methodname.
def method_a(self, method_arg: str, child_arg: str) -> dict:
return Parent.method_b(self, method_arg, child_arg)
Related
I am stuck on a strategy pattern implementation.
I would like the child classes methods implementing the strategy pattern to be called, but it seems that it is only the abstract class method that is called.
from abc import abstractmethod
class TranslationStrategy:
#classmethod
#abstractmethod
def translate_in_french(cls, text: str) -> str:
pass
#classmethod
#abstractmethod
def translate_in_spanish(cls, text: str) -> str:
pass
FRENCH = translate_in_french
SPANISH = translate_in_spanish
class Translator(TranslationStrategy):
#abstractmethod
def __init__(self, strategy = TranslationStrategy.FRENCH):
self.strategy = strategy
def get_translation(self):
print(self.strategy("random string"))
class LiteraryTranslator(Translator):
def __init__(self, strategy = TranslationStrategy.FRENCH):
super().__init__(strategy)
def translate_in_french(self, text: str) -> str:
return "french_literary_translation"
def translate_in_spanish(self, text: str) -> str:
return "spanish_literary_translation"
class TechnicalTranslator(Translator):
def __init__(self, strategy=TranslationStrategy.FRENCH):
super().__init__(strategy)
def translate_in_french(self, text: str) -> str:
return "french_technical_translation"
def translate_in_spanish(self, text: str) -> str:
return "spanish_technical_translation"
translator = TechnicalTranslator(TranslationStrategy.FRENCH)
translator.get_translation() # prints None, I expect "french_technical_translation"
Am I missusing the strategy pattern here ?
I am not familiar with the strategy pattern, but to make your code work, you could use something like the following:
from abc import ABC, abstractmethod
from enum import Enum
class TranslationStrategy(str, Enum):
FRENCH = 'french'
SPANISH = 'spanish'
#classmethod
def _missing_(cls, value):
if isinstance(value, str):
try:
return cls._member_map_[value.upper()]
except KeyError:
pass
return super()._missing_(value)
class Translator(ABC):
def __init__(self, strategy=TranslationStrategy.FRENCH):
self._strategy = TranslationStrategy(strategy)
self.strategy = getattr(self, f'translate_in_{self._strategy}')
#abstractmethod
def translate_in_french(cls, text: str) -> str:
pass
#abstractmethod
def translate_in_spanish(cls, text: str) -> str:
pass
def get_translation(self, text: str = 'random string'):
print(self.strategy(text))
class LiteraryTranslator(Translator):
def __init__(self, strategy=TranslationStrategy.FRENCH):
super().__init__(strategy)
def translate_in_french(self, text: str) -> str:
return "french_literary_translation"
def translate_in_spanish(self, text: str) -> str:
return "spanish_literary_translation"
class TechnicalTranslator(Translator):
def __init__(self, strategy=TranslationStrategy.FRENCH):
super().__init__(strategy)
def translate_in_french(self, text: str) -> str:
return "french_technical_translation"
def translate_in_spanish(self, text: str) -> str:
return "spanish_technical_translation"
Note that the __init__ method is NOT an abstract method. That method should never be marked as an abstract method, and any method for which you rely on the implementation should not be marked as abstract.
The self._strategy = TranslationStrategy(strategy) line will ensure that the given strategy is a member of that enum. That is, it will automatically normalize input, and reject invalid values:
>>> TranslationStrategy('French')
<TranslationStrategy.FRENCH: 'french'>
>>> TranslationStrategy('french')
<TranslationStrategy.FRENCH: 'french'>
>>> TranslationStrategy('FRENCH')
<TranslationStrategy.FRENCH: 'french'>
>>> TranslationStrategy(TranslationStrategy.FRENCH)
<TranslationStrategy.FRENCH: 'french'>
>>> TranslationStrategy('foo')
Traceback (most recent call last):
...
ValueError: 'foo' is not a valid TranslationStrategy
In order to properly obtain a reference to a subclass' method, a reference to it must be stored once the subclass can be known. The self.strategy = getattr(self, f'translate_in_{self._strategy}') line stores a reference to the translate_in_french or translate_in_spanish method in the current object, which will be the one defined in the class you initialize. The reason the approach you used did not work was that it stored a reference to the abstract TranslationStrategy.translate_in_french or TranslationStrategy.translate_in_spanish method, not the one defined in the subclass.
Technically, the __init__ implementations in LiteraryTranslator and TechnicalTranslator are not strictly necessary here since they don't do anything except call super().__init__(...) with the same arguments as the parent class.
Lastly, stacking #classmethod with #abststractmethod results in an abstract class method, not an abstract instance (normal) method. Since these are intended to be normal methods, the #classmethod had to be omitted.
The strategy pattern is much easier to implement in Python, where you can simply pass functions as arguments and return new functions from a function. No need for all the class boilerplate.
from typing import Callable
TranslationStrategy = Callable[[str], str]
Translator = Callable[[str], str]
def literary_french(text: str) -> str:
...
def literary_spanish(text: str) -> str:
...
def technical_french(text: str) -> str:
...
def technical_spanish(text: str) -> str:
...
def make_translator(strategy: TranslationStrategy) -> Translator:
# Yes, this is simple enough we could just write
#
# return strategy
#
def translate(text: str) -> str:
return strategy(text)
return translate
translator = make_translator(technical_french)
print(translator("..."))
from typing import Type, TypeVar
T = TypeVar('T', bound='A')
class A:
#classmethod
def create(cls: Type[T]) -> T:
return cls()
class B(A):
def do(self):
...
#classmethod
def create(cls):
obj = super().create()
obj.do()
# ^^ <- complaint
if __name__ == '__main__':
b = B.create() # OK, no error
In the above example, the type-checker complains about do method invocation in the subclass create method. Is there a way to work around this?
Adding a type hint on B.create seems to do the trick, even with strict options of mypy enabled
from typing import Type, TypeVar
T = TypeVar('T', bound='A')
U = TypeVar('U', bound='B')
class A:
#classmethod
def create(cls: Type[T]) -> T:
return cls()
class B(A):
def do(self) -> None:
pass
#classmethod
def create(cls: Type[U]) -> U:
obj = super().create()
obj.do()
return obj
if __name__ == '__main__':
b = B.create()
Here is a simple reproduction:
from typing import Union
class Parent:
def __init__(self) -> None:
print("In Parent")
class A(Parent):
def __init__(self) -> None:
super().__init__()
print("In A")
class B(Parent):
def __init__(self) -> None:
super().__init__()
print("In B")
def factory(arg: str) -> Union[A, B]:
mapper = {"A": A, "B": B}
return mapper[arg]()
This seems to me like it ought to be typed correctly, but mypy reports the following error:
error: Incompatible return value type (got "Parent", expected "Union[A, B]")
It either produces an error with a bad argument, or one of the child classes specified in the Union. So why does mypy get Parent?
Is there some better way I can write this? This variant doesn't produce any error, but I prefer the former, especially if there were many cases:
def factory(arg: str) -> Union[A, B]:
if arg == "A":
return A()
return B()
Thanks in advance.
I have doubts about the pythonic way to do something like this:
from my_package import A, B
class Main:
def __init__(self) -> None:
""" Constructor """
self._var1: Union[A, B]
def set_values(self, value: str) -> None:
""" Sets the values """
self._var1.function_caller(value)
class First(Main):
def __init__(self) -> None:
""" Constructor """
self._var1 = A()
# Super Constructor
Main.__init__(self)
class Second(Main):
def __init__(self) -> None:
""" Constructor """
self._var1 = B()
# Super Constructor
Main.__init__(self)
And then in my main program:
import First, Second
my_class = First() if arg['value'] == 'first' else Second()
my_class.set_values('test')
This way, MyPy does not show any error but PyCharm gives me the following:
Unresolved attribute reference '_var1' for class 'Main'
How this situation should be approached in a correct and pythonic way?
Change it like this:
from my_package import A, B
class Main:
_var1: Union[A, B]
def __init__(self) -> None:
""" Constructor """
pass
def set_values(self, value: str) -> None:
""" Sets the values """
self._var1.function_caller(value)
To improve the code further you may consider making Main an Abstract Base Class, since it is clearly not meant to be instantiated itself (_var1 will not be set), and is there to act as a common base for your 'real' classes First and Second.
from abc import ABC
from my_package import A, B
class Main(ABC):
_var1: Union[A, B]
def __init__(self) -> None:
""" Constructor """
pass
def set_values(self, value: str) -> None:
""" Sets the values """
self._var1.function_caller(value)
This is an alternative to Anentropic's answer. With this variant you cannot "forget" to assign the required attribute. The other answer effectively tells PyCharm: "this class has this attribute" even if it is not provided at runtime.
class Main:
# pass the value via constructor
def __init__(self, var: Union[A, B]) -> None:
self._var1 = var
def set_values(self, value: str) -> None:
self._var1.function_caller(value)
class First(Main):
def __init__(self) -> None:
# don't set the value but delegate to the base class
Main.__init__(self, A())
class Second(Main):
def __init__(self) -> None:
# don't set the value but delegate to the base class
Main.__init__(self, B())
Neither mypy nor PyCharm complain here and you cannot forget to set the attribute if you add another sub class.
Please see example below, with python 3.7, where I can't find the way to correctly annotate. The annotations errors are shown in comments, and are given by mypy.
I have a "generic class" which implements "generic members". And concrete classes and members inheriting of that structure.
The concrete members can have additional methods and use different arguments for the constructor.
What is the way to correctly annotate this?
Thanks a lot.
import abc
import typing
class ParentProvider(metaclass=abc.ABCMeta):
def __init__(self) -> None:
pass
class ChildProvider(ParentProvider):
def __init__(self, who: str) -> None:
ParentProvider.__init__(self)
self._who: str = who
#property
def p(self) -> str:
return "Hello %s" % self._who
class Parent(metaclass=abc.ABCMeta):
#property
#abc.abstractmethod
def providerType(self) -> typing.Type[ParentProvider]:
pass
#property
#abc.abstractmethod
def providerKwargs(self) -> typing.Dict[str, typing.Any]:
pass
#property
def provider(self) -> ParentProvider:
# How to avoid the following error?
# error: Too many arguments for "ParentProvider" [call-arg]
return self.providerType(**self.providerKwargs)
#abc.abstractmethod
def useIt(self) -> None:
pass
class Child(Parent):
#property
def providerType(self) -> typing.Type[ParentProvider]:
# Using Type[ChildProvider] instead Type[ParentProvider]
# doesn't helps
return ChildProvider
#property
def providerKwargs(self) -> typing.Dict[str, typing.Any]:
return dict(who='world')
def useIt(self) -> None:
# How to avoid the following error?
# error: "ParentProvider" has no attribute "p" [attr-defined]
print(self.provider.p)
if __name__ == "__main__":
Child().useIt()
I have a "generic class" which implements "generic members"
Then mark it as such:
from typing import Generic, TypeVar
_T = TypeVar('_T', bound='ParentProvider')
class Parent(Generic[_T], metaclass=abc.ABCMeta):
#property
#abc.abstractmethod
def providerType(self) -> typing.Type[_T]:
pass
#property
#abc.abstractmethod
def providerKwargs(self) -> typing.Dict[str, typing.Any]:
pass
#property
def provider(self) -> _T:
return self.providerType(**self.providerKwargs)
#abc.abstractmethod
def useIt(self) -> None:
pass
Now the specific Child impl becomes:
class Child(Parent[ChildProvider]):
#property
def providerType(self) -> typing.Type[ChildProvider]:
return ChildProvider
#property
def providerKwargs(self) -> typing.Dict[str, typing.Any]:
return dict(who='world')
def useIt(self) -> None:
print(self.provider.p)
How to avoid the following error?
error: Too many arguments for "ParentProvider" [call-arg]
The Parent.__init__ signature doesn't allow any arguments to be passed - you can loosen that with
class ParentProvider(metaclass=abc.ABCMeta):
def __init__(self, *args, **kwargs) -> None:
pass
(or just
class ParentProvider(metaclass=abc.ABCMeta):
def __init__(self, **kwargs) -> None:
pass
if you don't want to allow positional args passed in providers constructors).