【问题标题】:How to redirect all methods of a contained class in Python?如何在 Python 中重定向包含类的所有方法?
【发布时间】:2012-11-07 19:02:22
【问题描述】:

如何实现组合模式?我有一个类Container,它有一个属性对象Contained。我想通过简单地调用my_container.some_contained_method() 来重定向/允许从Container 访问Contained 类的所有方法。我是否以正确的方式做正确的事?

我使用类似的东西:

class Container:
   def __init__(self):
       self.contained = Contained()
   def __getattr__(self, item):
       if item in self.__dict__: # some overridden
           return self.__dict__[item] 
       else:
           return self.contained.__getattr__(item) # redirection

背景:

我正在尝试构建一个类 (Indicator),以增加现有类 (pandas.DataFrame) 的功能。 Indicator 将拥有DataFrame 的所有方法。我可以使用 inheritance,但我遵循“赞成 composition 而不是 inheritance”的建议(例如,请参阅以下答案:python: inheriting or composition )。不继承的一个原因是基类不可序列化,我需要序列化。

我找到了this,但我不确定它是否符合我的需要。

【问题讨论】:

  • 代理对象如何帮助您的序列化-您仍然必须以某种方式做到这一点...只需从基础继承(因为您的对象“is-a”)并从那里...
  • 一个pandas.DataFrame 有很多方法可以返回另一个DataFrame。可能很难安排您的Container 退回另一个Container...
  • @Jon 基类不是serializalbe,而是picklable。扩展组件的酸洗比扩展超类更容易。
  • @unutbu,好点子。我想我可以包装每个返回的DataFrame

标签: python inheritance pandas composition


【解决方案1】:

我发现 unbutbu 的答案对我自己的应用程序非常有用,我遇到了在 jupyter 笔记本中正确显示它的问题。我发现在类中添加以下方法解决了这个问题。

def _repr_html_(self):
    return self.contained._repr_html_()

def _repr_latex_(self):
    return self.contained._repr_latex_()

【讨论】:

    【解决方案2】:

    注意事项:

    • DataFrames 有很多属性。如果DataFrame 属性是一个数字,您可能只想返回该数字。但如果DataFrame 属性是DataFrame,您可能想要返回Container。如果DataFrame 属性是Series 或描述符,我们该怎么办?要正确实施Container.__getattr__,你真的 必须为每个属性编写单元测试
    • __getitem__ 也需要进行单元测试。
    • 您还必须定义和单元测试 __setattr____setitem____iter____len__ 等。
    • Pickling 是序列化的一种形式,所以如果 DataFrames 是可腌制的,我不确定 Containers 对序列化有何帮助。

    一些cmets:

    • __getattr__ 仅在属性不在 self.__dict__ 中时调用。所以你的__getattr__ 中不需要if item in self.__dict__

    • self.contained.__getattr__(item) 调用 self.contained__getattr__ 直接方法。这通常不是你想要的 做,因为它绕过了整个 Python 属性查找 机制。例如,它忽略了属性的可能性 可以在self.contained.__dict__,或在__dict__ 之一 self.contained.__class__ 的基数或者如果item 指的是一个 描述符。请改用getattr(self.contained, item)


    import pandas
    import numpy as np
    
    def tocontainer(func):
        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            return Container(result)
        return wrapper
    
    class Container(object):
       def __init__(self, df):
           self.contained = df
       def __getitem__(self, item):
           result = self.contained[item]
           if isinstance(result, type(self.contained)):
               result = Container(result)
           return result
       def __getattr__(self, item):
           result = getattr(self.contained, item)
           if callable(result):
               result = tocontainer(result)
           return result
       def __repr__(self):
           return repr(self.contained)
    

    这里有一些随机代码来测试是否——至少在表面上——Container 正确地委托给DataFrames 并返回Containers

    df = pandas.DataFrame(
        [(1, 2), (1, 3), (1, 4), (2, 1),(2,2,)], columns=['col1', 'col2'])
    df = Container(df)
    df['col1'][3] = 0
    print(df)
    #    col1  col2
    # 0     1     2
    # 1     1     3
    # 2     1     4
    # 3     2     1
    # 4     2     2
    gp = df.groupby('col1').aggregate(np.count_nonzero)
    print(gp)
    #       col2
    # col1      
    # 1        3
    # 2        2
    print(type(gp))
    # <class '__main__.Container'>
    
    print(type(gp[gp.col2 > 2]))
    # <class '__main__.Container'>
    
    tf = gp[gp.col2 > 2].reset_index()
    print(type(tf))
    # <class '__main__.Container'>
    
    result = df[df.col1 == tf.col1]
    print(type(result))
    # <class '__main__.Container'>
    

    【讨论】:

    • 您能否解释一下为什么这比继承更可取(或不可以)?
    • 这很有帮助。我在其他地方找不到很好的讨论。
    • @user1579844:如果 B 类通过添加新方法扩展 A 类并且不覆盖A 类的方法,则使用继承。如果在使用A 的地方可以替换B,则使用继承。使用组合进行依赖注入(允许类B 有一个类A2 而不是类A)。对于这个和其他想法,请参阅this SO answer。有时组合或继承都不正确——有时一个简单的函数是最好的。 (我很想知道这里是否是这种情况。)
    • @unutbu,如果我选择简单的函数解决方案,我该如何自定义 myindicator.plot(),例如?
    • @user1579844:您可以将def myplot(dataframe): ... 定义为模块级函数。
    猜你喜欢
    • 2014-08-17
    • 2012-05-13
    • 2019-11-30
    • 2020-04-21
    • 2018-07-28
    • 1970-01-01
    • 1970-01-01
    • 2021-05-01
    • 2016-03-25
    相关资源
    最近更新 更多