【问题标题】:Python: How do you chain methods on lists of lists of objects?Python:如何在对象列表上链接方法?
【发布时间】:2012-08-23 20:18:30
【问题描述】:

我希望能够在具有子节点和孙节点的节点实例上链接方法。当我在该顶部节点上调用方法时,我希望能够返回该对象的子孙。我还希望能够从最顶层节点获取这些子孙的属性。我有以下数据模型:

class GrandParent(object):
    def __init__(self,name='',age=0,is_retired=True,children=[]):
        self.name = name
        self.age = age
        self.is_retired = is_retired

        if children:
            self.children = children

    def print_mychildren(self):
       for child in self.children:
            print "Child: ",child

    def mychildren(self):
        return self.children

    def __str__(self):
        return "name: %s age: %s is_retired:%s" %(self.name,self.age,self.is_retired)


class Parent(object):
    def __init__(self,name='',age=0,has_mortgage=True,children=[]):
        self.name = name
        self.age = age
        self.has_mortgage = has_mortgage

        if children:
            self.children = children

    def print_mychildren(self):
       for child in self.children:
            print "Child: ",child

    def __str__(self):
        return "name: %s age: %s has_mortgage: %s" %(self.name,self.age,self.has_mortgage)


class Child(object):
    def __init__(self,name='',age=0,has_toys=True):

        self.name = name
        self.age = age
        self.has_toys = has_toys

    def __str__(self):
        return "name: %s age: %s has_toys:%s" %(self.name,self.age,self.has_toys)



if __name__ == '__main__':

    beaver = Child('Beaver',12,True)
    ward = Child('Ward',16,False)

    june = Parent('June',38,True,[beaver,ward])

    grandpa = GrandParent('Grandpa',61,True,[june])

    print grandpa

    grandpa.print_mychildren() # print June

    # Doesn't work
    grandpa.mychildren().print_mychildren() #  I want it to print Ward and Beaver
    # Doesn't work
    print grandpa.mychildren().mychild('Beaver').age # I want it to return an age

请注意,我想将 GrandParent、Parent 和 Child 类分开,因为我想为每个类赋予不同的属性,例如 has_mortgage 或 is_retired。

从上面的数据模型中,我希望能够链接方法以遍历顶部节点的子节点。它看起来像这样:

grandpa.children # returns a list of children
print grandpa.mychild('June').has_mortgage # returns the value
grandpa.mychildren().mychildren() # prints a list of grandchildren
print grandpa.mychildren().mychild('Ward').has_toys # return the value

换句话说,我可以发表声明吗:

print grandpa.mychildren().mychildren()

表现得像:

for child in grandpa.children:
        for grandchild in child.children:
            print grandchild

非常感谢您对此的回答。谢谢。

保罗
伊利诺伊州芝加哥

【问题讨论】:

  • 在我看来这是一个继承的用例。您应该采用每个类的所有通用属性/方法并将它们分组到一个类中。然后,为每个子类(GrandparentParentChild)从该类继承。
  • 我投票以“过于本地化”来结束这个问题,但我的建议是结合 myChildmyChildren 方法。 def myChildren(name=None): 例如。然后传递一个参数将表示您调用该方法的目的。
  • “过于本地化”是什么意思。我同意,子类化将有助于清理代码,但无助于解决所描述的问题
  • 我在底部添加了对我的问题的澄清。基本上,我问的是我是否链接方法,以便它的行为就像我在遍历节点时循环遍历子节点一样。

标签: python list python-2.7


【解决方案1】:

你可以做你想做的事,但这需要一些工作。您需要将您的ParentGrandparent 类的children 值设为自定义容器,以便将对方法和成员变量的访问映射到其内容。

这是一个可能的实现。它可能有一些我没有整理出来的奇怪的极端情况,但它适用于我尝试过的基本内容:

from operator import attrgetter

class mappingContainer(object):
    def __init__(self, contents):
        self.contents = contents

    # sequence protocol methods    
    def __getitem__(self, index):
        return self.contents[index]

    def __setitem__(self, index, value):
        self.contents[index] = value

    def __iter__(self):
        return iter(self.contents)

    def __contains__(self, o):
        return o in self.contents

    def __len__(self):
        return len(self.contents)

    # map attribute access and method calls
    def __getattr__(self, name):
        return mappingContainer(map(attrgetter(name), self.contents))

    def __call__(self, *args, **kwargs):
        return mappingContainer([o(*args, **kwargs) for o in self.contents])

    # string conversions
    def __repr__(self):
        return "mappingContainer(%s)" % repr(self.contents)

这是一个简单的“Person”类,我用来测试它。每个人都有一个名字,mappingContainer 中有零个或多个孩子,以及一个任意命名的方法,该方法打印一些内容并返回一个值。

class Person(object):
    def __init__(self, name, children = None):
        self.name = name
        if not children:
            children = []
        self.children = mappingContainer(children)

    def __repr__(self):
        return self.name

    def someMethod(self):
        print "%s's someMethod()" % str(self)
        return "%s's return value" % str(self)

这是我测试它的方法(用长线包裹,在这里展示):

>>> c1 = Person("Adam")
>>> c2 = Person("Albert")
>>> c3 = Person("Alice")
>>> c4 = Person("Agnes")
>>> p1 = Person("Bob", [c1, c2])
>>> p2 = Person("Betty", [c3,c4])
>>> gp = Person("Charles", [p1, p2])
>>> gp
Charles
>>> gp.children
mappingContainer([Bob, Betty])
>>> gp.children.children
mappingContainer([mappingContainer([Adam, Albert]),
                  mappingContainer([Alice, Agnes])])
>>> gp.children.children.someMethod()
Adam's someMethod()
Albert's someMethod()
Alice's someMethod()
Agnes's someMethod()
mappingContainer([mappingContainer(["Adam's return value",
                                    "Albert's return value"]),
                  mappingContainer(["Alice's return value",
                                    "Agnes's return value"])])

现在这可能不是您想要的,因为您不能通过gp.children.children 直接迭代孙子(虽然函数调用会一直冒泡,但它们的结果仍将嵌套在两层容器中)。我认为 mappingContainer 可能会以某种方式进行调整来解决这个问题,但我不确定它是否值得。

如果出现任何问题,这种链式调用将成为调试的噩梦(除非您是超人,否则代码中的某些内容总会在开发的某个阶段出错)。如果您明确地迭代或递归您拥有的关系层次结构,您的代码将更容易编写和理解。

【讨论】:

  • 感谢您的回复,Blckknght。你提出了一个非常有趣的实现。由于我还不是很先进,所以我必须进一步学习。谢谢你的时间。 :)
【解决方案2】:

一种方法是为列表集合创建一个包装器(有点类似于 jQuery 处理方法链的方式)

代码:

class ChainList(list):
    def __getattribute__(self, name):
        try:
            return object.__getattribute__(self, name)
        except:
            pass

        return ChainList([getattr(child, name) 
            for child in self if name in dir(child)])

    def __call__(self, *args, **kwargs):       
        return ChainList([child(*args, **kwargs)
            for child in self if callable(child)])

class GrandParent(object):
    def __init__(self,name='',age=0,is_retired=True,children=[]):
        self.name = name
        self.age = age
        self.is_retired = is_retired

        if children:
            self.children = ChainList(children)

    def print_mychildren(self):
       for child in self.children:
            print "Child: ",child

    def mychildren(self):
        return self.children

    def __str__(self):
        return "name: %s age: %s is_retired:%s" %(self.name,self.age,self.is_retired)

class Parent(object):
    def __init__(self,name='',age=0,has_mortgage=True,children=[]):
        self.name = name
        self.age = age
        self.has_mortgage = has_mortgage

        if children:
            self.children = ChainList(children)

    def print_mychildren(self):
       for child in self.children:
            print "Child: ",child

    def __str__(self):
        return "name: %s age: %s has_mortgage: %s" %(self.name,self.age,self.has_mortgage)

    def mychild(self, name):
        for child in self.children:
            if child.name == name:
                return child

class Child(object):
    def __init__(self,name='',age=0,has_toys=True):

        self.name = name
        self.age = age
        self.has_toys = has_toys

    def __str__(self):
        return "name: %s age: %s has_toys:%s" %(self.name,self.age,self.has_toys)



if __name__ == '__main__':

    beaver = Child('Beaver',12,True)
    ward = Child('Ward',16,False)

    june = Parent('June',38,True,[beaver,ward])

    grandpa = GrandParent('Grandpa',61,True,[june])

    print grandpa

    grandpa.print_mychildren() # print June

    # Doesn't work
    grandpa.mychildren().print_mychildren() #  I want it to print Ward and Beaver
    # Doesn't work
    print grandpa.mychildren().mychild('Beaver').age # I want it to return an age

主要思想是 ChainList 类并覆盖 __getattribute____call__ 并包装所有需要与此类链接的列表。

我还将 mychild() 添加到 Parent 以使示例正常运行。

【讨论】:

  • 这是一些我不完全理解的 Python 魔法。我必须复习 __getattribute__ 和 __call__。很酷。我很感激 - 谢谢你,nehz。
  • 可能会错误地删除您的评论,但无论如何,当我添加方法时,grandpa.mychild('June').age 似乎对我有用......方法 def 应该与父级
  • 也只是一个建议,这种软件设计不是很常规。考虑一下 Joel Cornett 关于继承的 cmets。
  • 是的,谢谢。我有一个错字。我想对于长期代码维护和遵循最佳实践,继承是最好的。虽然我很高兴 Python 允许我灵活地实现这种特定行为,至少对于原型设计而言。
【解决方案3】:

对我来说,这听起来很像您在谈论模型和模型关系。看看 Django。 Django Models 会做你想做的事,你可以定义模型和query across those relations 之间的关系。

【讨论】:

  • 这和 Django 有什么关系?没有迹象表明他希望这些类永久保存在数据库中,或者这甚至是一种选择。这可以使用 OO 模式来完成,而无需在其周围引入不必要的 Web 框架。
  • 这当然可以使用 OO 来完成。我只是不确定需要多少编码 - 或者我实际上担心这将需要大量编码,而 Django 或多或少会开箱即用。但保罗需要决定是否值得。但无论如何,我很高兴看到 OO 解决方案。
  • 嗨。我希望 Python 能让我使用已经是 Python 一部分的特殊方法,而不是来自 Django。所以是的,我想在 OO 中看到它。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-27
  • 1970-01-01
  • 1970-01-01
  • 2012-01-13
  • 1970-01-01
相关资源
最近更新 更多