当我无权访问实例化代码时,如何专门化对象的实例?

时间:2019-10-28 08:27:06

标签: python

假设我使用的是一个库,当调用其函数时,该库为我提供了该库中定义的类的实例:

>>> from library import find_objects
>>> result = find_objects("name = any")
[SomeObject(name="foo"), SomeObject(name="bar")]

让我们进一步假设我想将新属性附加到这些实例。例如,一个分类器以避免每次我想对实例进行分类时都运行此代码:

>>> from library import find_objects
>>> result = find_objects("name = any")
>>> for row in result:
...     row.item_class= my_classifier(row)

请注意,这是人为设计的,但可以说明问题:我现在有类SomeObject的实例,但是属性item_class未在该类中定义,并触发了类型检查器。

所以当我现在写:

print(result[0].item_class)

输入错误。由于编辑者不知道该属性的存在,还会触发编辑器中的自动完成。

而且,更不用说这种实现方式非常丑陋和笨拙。

我可以做的一件事是创建SomeObject的子类:

class ExtendedObject(SomeObject):

    item_class = None

    def classify(self):
        cls = do_something_with(self)
        self.item_class = cls

这现在使所有内容都明确,我有机会正确记录新属性并提供适当的类型提示。一切都干净。但是,如前所述,实际实例是在 library 内部创建的,我无法控制实例化。

旁注:我在flask类的Response中遇到了这个问题。我注意到flask实际上提供了一种使用Flask.response_class自定义实例化的方法。但是我仍然很感兴趣如何在不提供这种注射接缝的图书馆中实现这一目标。

我可以做的一件事就是写一个包装程序,做这样的事情:

class WrappedObject(SomeObject):

    item_class = None
    wrapped = None

    @staticmethod
    def from_original(wrapped):
        self.wrapped = wrapped
        self.item_class = do_something_with(wrapped)

    def __getattribute__(self, key):
        return getattr(self.wrapped, key)

但这似乎很hacky,无法在其他编程语言中使用。

或尝试复制数据:

from copy import deepcopy

class CopiedObject(SomeObject):

    item_class = None

    @staticmethod
    def from_original(wrapped):
        for key, value in vars(wrapped):
            setattr(self, key, deepcopy(value))
        self.item_class = do_something_with(wrapped)

但是这同样令人讨厌,并且在对象起诉属性和/或描述符时存在风险。

是否存在类似这种已知的“干净”模式?

1 个答案:

答案 0 :(得分:0)

我会采用您的WrappedObject方法的一种变体,并进行以下调整:

  • 我不会扩展SomeObject:在这种情况下,合成比继承更合适
  • 请记住,from_original是不必要的:您可以使用适当的__init__方法
  • item_class应该是实例变量,而不是类变量。应该在您的WrappedObject类构造函数中初始化
  • 在实现__getattribute__并将所有内容转发到包装的对象之前,请三思。如果您只需要原始SomeObject类的一些方法和属性,则最好将它们显式地实现为方法和属性
class WrappedObject:
    def __init__(self, wrapped):
        self.wrapped = wrapped
        self.item_class = do_something_with(wrapped)

    def a_method(self):
        return self.wrapped.a_method()

    @property
    def a_property(self):
        return self.wrapped.a_property