如何以Python的方式将包/框架中的不同对象组合成类或对象



假设我有两个不同框架的对象,我无法更改(一个是C实现的,另一个是使用嵌套工厂进行实例化(:

# 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(" ")

所以在运行时,我得到两个对象。现在为了更容易处理,我想把它们粘在一起。我的解决方案很简单:

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

然而,这不是很好。我喜欢像这样的界面

...
obj.do_stuff()
obj.do_even_more()

我可以想象的其他解决方案:

  • Monkeypath类Foo还是Bar
  • 元编程
  • 授权多种方法
  • 描述符
  • 制作一个脚本,将其他类的Composed类解析为抽象类。在init更新composeddict

在您的示例中,Composed作为数据类似乎更好。

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

结果:

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

注意:请记住,您实际上可以在__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)

在玩了ast和libCST之后,我意识到有一个超级简单的解决方案

# 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_text]
self.__dict__.update(attr.__dict__)
# or using a factory avoiding the init call
...

这将提供所需的界面。然而,名称冲突需要手动解决,但对于我的具体问题,这将起作用。

最新更新