【问题标题】:What's the most pythonic and elegant way to separate logic of a class and its parent if child class is determined by construction logic?如果子类由构造逻辑确定,那么分离类的逻辑及其父类的最pythonic和优雅的方法是什么?
【发布时间】:2013-09-24 00:53:12
【问题描述】:

我想从"red apple" 等字符串构造类。这将创建Apple 类的实例,它是Fruit 的子类。问题是,color 属性应该属于Fruit,而不是Apple。因此,在我看来,创建对象的自然方式是:

  1. 解析字符串
  2. 创建Fruit(color="red")
  3. 创建Apple()
  4. 以某种方式使其成为一个实体

到目前为止,我有 3 个选择:

  1. 一切都变成参数

    class Fruit(object):
        def __init__(self, color):
            self.color = color
    
        def observe(self):
            print "Looks like a tasty %s fruit" % self.color
    
        @classmethod
        def fromstring(cls, string):
            color, kind = string.split()
            if kind == "apple":
                return Apple(color)
    
    class Apple(Fruit):
        def __init__(self, *args, **kwargs):
            super(Apple, self).__init__(*args, **kwargs)
            self.tasty = True
    
        def bite(self):
            print "I bite into a tasty apple"
    
    fruit = Fruit.fromstring("red apple")
    
  2. color属性是从外部填写的

    class Fruit(object):
        def observe(self):
            print "Looks like a tasty %s fruit" % self.color
    
        @classmethod
        def fromstring(cls, string):
            color, kind = string.split()
            if kind == "apple":
                ins = Apple()
                ins.color = color
                return ins
    
    class Apple(Fruit):
        def __init__(self):
            self.tasty = True
    
        def bite(self):
            print "I bite into a tasty apple"
    
    fruit = Fruit.fromstring("red apple")
    
  3. 最直接的方法:替换__class__

    class Fruit(object):
        def __init__(self, string):
            self.color, kind = string.split()
            if kind == "apple":
                self.__class__ = Apple
                Apple.__init__(self)
    
        def observe(self):
            print "Looks like a tasty %s fruit" % self.color
    
    class Apple(Fruit):
        def __init__(self):
            self.tasty = True
    
        def bite(self):
            print "I bite into a tasty apple"
    
    fruit = Fruit("red apple")
    

跑步

fruit.observe()
fruit.bite()
print type(fruit), fruit.tasty

给出相同的输出:

Looks like a tasty red fruit
I bite into a tasty apple
<class '__main__.Apple'> True

第一种方法可以说是最通用的方法,它需要传递诸如color 之类的参数,在第三种方法中处理得更加优雅。然而,改变__class__ 听起来像是使用高级工具来完成一项平凡的任务。有没有更好的方法来实现目标,或者我最好使用其中一种方法?

更新:我可能必须指出,在现实生活中FruitApple 的初始化程序应该设置的属性数量是变量,大约共 15 个。

【问题讨论】:

  • 水果是否应该提供构造函数?也许基类应该只是一个bass类,并且应该有某种工厂来实例化正确种类的水果,那么Fruits根本不需要知道子类
  • 我只是认为调用 Fruit.fromstring() 会是制作水果的好方法。不,Fruit 不需要注意孩子。
  • “我可能必须指出,在现实生活中FruitApple 的初始化程序应该设置的属性数量是可变的,总共大约15 个。” - 这是一个主要的代码气味。为什么在初始化时设置 15 属性?您能否进一步扩展您的问题,使其更接近“现实生活”?
  • 我相信你正在寻找一个类似于fromstring 的工厂函数,只是你不会拥有它作为Fruit 的成员

标签: python class inheritance


【解决方案1】:

我会将创建逻辑完全从类中提取出来:

  1. 解析字符串
  2. 确定要创建的对象
  3. 创建对象

所以使用下面的代码:

class Fruit(object):
    def __init__(self, color):
        self.color = color

    def observe(self):
        print "Looks like a tasty %s fruit" % self.color

class Apple(Fruit):
    def __init__(self,color):
        super(Apple, self).__init__(color)
        self.tasty = True

    def bite(self):
        print "I bite into a tasty apple"

fruit = None
color,type = "red apple".split()
if type == "apple":
    fruit = Apple(color)
if type == "banana" and color == "blue"
    raise Exception("Welcome to Chernobyl")

编辑:回复您对 dm03514 答案的评论。

此代码与您的“选项 1”之间的主要区别在于,Fruit 不需要知道它的子类。在我的代码中,我可以这样做:

class Banana(Fruit):
    def __init__(self,color):
        if color not in ["yellow","green"]:
            raise Exception("Welcome to Chernobyl")
        super(Banana).__init__(self,color)
        if color = "yellow":
            self.ripe = True
        elif color = "green:"
            self.ripe = False

    def bite(self):
        print "I bite into a %s banana"%["unripe","ripe"][self.ripe]

Fruit 不需要知道我的子类。在您的代码中,对于每种新类型的水果,Fruit 类都需要更新,基本上限制了任何简单的扩展方法。如果你正在设计一个我想要的库,我不能重用 Fruit,因为我不能添加香蕉、橙子或任何你没有的水果不改变你的代码,这与代码重用。

【讨论】:

  • +1 表示放射性香蕉(我投票是因为逻辑分离,而不是香蕉)
  • 看起来您只是将我的类方法移到了类之外:P 我想知道为什么这比其他方法更可取。
  • @squirrel 我更新了代码,说明了为什么从 Fruit 类中删除了代码。 dm03514 也是如此。
  • @LegoStormtroopr 阅读您的回答,我意识到我问这个问题有多糟糕(我认为它几乎是完美的)。我忘了指出Fruit 必须注意的事情的数量是可变的,并不真正取决于AppleBanana。这意味着按照自己的方式完成工作(无疑是最好的方式)需要为Appale__init__ 制作一个参数字典,而后者必须决定哪些参数是他的,哪些属于Fruit 的初始化程序。但我想我无能为力。谢谢,对我糟糕的提问技巧感到抱歉:)
【解决方案2】:

我认为您需要评估基类代表什么。

是否每个水果都需要一种颜色(您的observe 函数会建议它至少需要一个默认值才能在调用时不会导致错误)?如果是这样,它应该是水果构造函数的一部分,并且应该是创建水果所必需的。

根据我的评论,我也对您的基类实例化子类型持怀疑态度。 Fruit 是否应该知道它的所有子类型(例如,参见 legos 答案)?

【讨论】:

  • 每个水果都需要并且会有颜色,输入字符串的某些部分将是统一的。 Lego 的想法本质上是方法#1,但它需要传递color 两次(实际上,将传递可变数量的参数,最多10 个)
【解决方案3】:
class Fruit(object):
    def __init__(self,color):
        self.color = color

    def observe(self):
        print "Looks like a tasty %s fruit" % self.color
    @classmethod
    def fromstring(cls, my_string):
        color, kind = my_string.split()
        my_class = globals().get(kind.capitalize(),Fruit)(color)
        assert isinstance(my_class, Fruit),"Error Unknown Kind %s"%kind
        return my_class

class Apple(Fruit):
    def __init__(self,color):
        self.tasty = True
        Fruit.__init__(self,color)

    def bite(self):
        print "I bite into a tasty apple"

a = Fruit.fromstring("red apple")
print a
a.bite()

【讨论】:

  • 呃,这只是评论还是对问题的回答? IE。传递变量是怎么回事?
猜你喜欢
  • 1970-01-01
  • 2017-06-25
  • 1970-01-01
  • 2022-01-12
  • 1970-01-01
  • 2014-02-26
  • 1970-01-01
  • 2011-04-10
  • 1970-01-01
相关资源
最近更新 更多