Lets say I a have two objects of different framework that I cant alter (one is C implemented and the other uses a nested factory to instantiate):
# framework_a.py
class Foo:
"""
produced by C implementation
"""
...
text = "some dynamic text"
# more methods to work with text
...
def provide_text():
pass
# framework_b.py
class Bar:
...
parsed_text = ["some", "dynamic", "text"]
# nested utility function
def parse_from_plain(text):
return text.split(" ")
So at runtime I'm getting two objects. Now for easier handling I want them to glue together. My solution is simple:
# userpackage.py
from framework_a import Foo
from framework_b import Bar, parse_from_plain
class Composed:
def __init__(self, text, parsed_text):
self.text = text
self.parsed_text = parsed_text
# static function to run it in a Pool
def make_composed_object(foo_text: Foo, bar_parsed: Bar):
...
# do some additional stuff before finally returning the object
return Composed(foo_text, bar_parsed)
# main.py
from framework_a import provide_text
from userpackage import make_composed_object
...
db = []
for n in range(100_000_000)
text = provide_text()
parsed_text = parse_from_plain(text)
obj = make_composed_object(text, parsed_text)
obj.text.do_stuff()
obj.parsed_text.do_even_more()
# many more method calls depending on what user wants to do.
db.append(obj)
...
however, this is not very nice. I like to have an interface like this
...
obj.do_stuff()
obj.do_even_more()
Other solutions i could imagine:
- Monkeypath class Foo or Bar?
- Metaprogramming?
- Delegating many methods?
- Descriptor?
- make a script which parses the class Composed from the others class as abstract class. at init update the composed.dict
CodePudding user response:
Given your example, Composed
seems to be better as a dataclass.
from dataclasses import dataclass
@dataclass
class Composed:
text: Foo
parsed: Bar
def __post_init__(self):
"""
After initialising do some additional stuff
replacing make_composed_object
"""
self.text = NotImplemented
self.parsed = NotImplemented
def do_text_stuff(self):
# does something that affects text state
NotImplemented
def do_parsed_stuff(self):
# does something that affects parsed state
NotImplemented
results:
def db_iter(num):
for n in range(num)
text = provide_text()
parsed_text = parse_from_plain(text)
composed = Composed(text, parsed_text)
composed.do_text_stuff()
composed.do_parsed_stuff()
yield composed
db = db_iter(1_000_000) # returns a Generator
NOTE: Keep in mind that you can actually call those two methods in __post_init__
:
@dataclass
class Composed:
text: Foo
parsed: Bar
def __post_init__(self):
self._additional_stuff()
self._text_stuff()
self._parsed_stuff()
def _additional_stuff(self):
# additional stuff
self.text = NotImplemented
self.parsed = NotImplemented
def _text_stuff(self):
# does something that affects text state
NotImplemented
def _parsed_stuff(self):
# does something that affects parsed state
NotImplemented
def db_iter(num):
for n in range(num)
text = provide_text()
parsed_text = parse_from_plain(text)
yield Composed(text, parsed_text)
db = db_iter(1_000_000)
CodePudding user response:
After playing around with ast and libCST I realized that there is a super simple solution
# userpackage.py
from framework_a import Foo
from framework_b import Bar, parse_from_plain
class Composed(Foo, Bar):
def __init__(self, text, parsed_text):
super().__init__()
for param in [text, parsed]
self.__dict__.update(attr.__dict__)
...
this will give the desired interface. However, name conflicts needs to be solved manually, but for my concrete problem this will work.