What is the most pythonic and elegant way to separate the logic of a class and its parent if the child class is determined by the construction logic?

I want to build classes from strings such as "red apple" . This would create an instance of the Apple class, which is a subclass of Fruit . The fact is that the color attribute must belong to Fruit , not Apple . Therefore, the natural way to create an object seems to me as follows:

  • parse string
  • create Fruit(color="red")
  • create Apple()
  • somehow make it the only object

So far I have 3 options:

  • Everything becomes parameters

     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") 
  • The color attribute is filled outside

     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") 
  • The easiest way: replace __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") 

Launch

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

gives the same result:

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

The first method, perhaps the most universal, requires passing around arguments such as color , which are processed much more elegantly in the third method. However, changing __class__ sounds like using advanced tools for a worldly task. Is there a better way to achieve the goal, or am I better off using one of these things?

UPDATE . I should probably point out that in real life the number of attributes that must be set by the Fruit and Apple initializers is a variable, only about 15.

+4
source share
3 answers

I completely pulled the creation logic from the classes:

  • parse string
  • define an object to create
  • create object

So using the following code:

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

edit: In response to your comment on dm03514's answer.

The main difference between this code and your "option 1" is that in this Fruit don’t need to know about your subclasses. In my code, I can do this:

 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 does not need my subclass. In your code, for each new type of fruit, the Fruit class must be updated, which significantly limits any simple way to expand it. If you were developing the library that I wanted, I could not reuse Fruits, since I cannot add banana, orange, or any fruit that you don’t have without changing the code that contradicts code reuse.

+8
source

I think you need to evaluate what a base class is.

Does each fruit need a color (your observe function tells you that at least a default value is needed so as not to cause an error if it is called)? If so, it should be part of the fruit constructor and should be necessary to create the fruit.

From my comment, I am also at the heart of the subtypes of creating a base class. Should Fruit know all of its subtypes (e.g. legos )?

+3
source
 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() 
+1
source

All Articles