0

The question that I posted initially was lacking so here is an explanation that I hope will satisfy everyone and cancel some down votes that I received on it.

I would like to have an intelligence inside a class, that with the creation of an instance, it is decided that the instance would be of a different class, which is one of the subclasses of this class, according to some logic.

More specifically, I am making a Magic Square solver as a learning exercise and:

  • I want to have a MagicSquare class that will contain the logic of a MagicSquare.
  • I want to have OddMagicSquare and EvenMagicSquare subclasses of that class that will contain the logics of solving these two types of Magic Squares.
  • I want to be able to call the creation of a MagicSquare, providing it's size, n, and have the intelligence within MagicSquare determine which subclass to create an instance of, instead of the generic, top, class MagicSquare.

I understand that the intelligence to determine which subclass (OddMagicSquare/EvenMagicSquare) to create an instance of can be (and perhaps would be easier to implement if it would be) outside of MagicSquare. The reason that I want it to be inside MagicSquare is perhaps a gut feeling. I have a hunch that it would be more clean and tidy this way because the logic of determining which kind of Magic Square a certain Magic Square is, seems to me, to belong in the MagicSquare class.

false
  • 10,264
  • 13
  • 101
  • 209
Shahar 'Dawn' Or
  • 2,713
  • 1
  • 26
  • 38
  • 3
    This smells like an [XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Why do you want to re-class it? Why not use a "factory function" (or, equivalently, a `@classmethod` as an alternate constructor) that constructs an object of the right class in the first place? Or a `__new__` method? Why wait until you've gotten to `__init__` and then change the class? – abarnert Nov 01 '13 at 21:03
  • @abarnert I don't specifically want to re-class. I want the code that determines which subclass the object will be an instance of to be in the top, `Car`, class. Is there a reason for it not to be in `__init__`? Would it be better in `__new__`? – Shahar 'Dawn' Or Nov 01 '13 at 21:36
  • Yes, there is a reason for it not to be in `__init__`. Because by the time you've gotten into the base's `__init__`, it's obviously too late to just construct the object normally because you're already in the middle of object construction. `__new__` would probably be better than `__init__`, but an alternate constructor `@classmethod` (or some other variation on the theme—a factory, a Cocoa-style "class cluster" constructor, …) would probably be even _better_. – abarnert Nov 01 '13 at 21:49
  • Meanwhile, you still haven't told us _why_ you want to do this, or what you're _actually_ trying to do. You're giving us bits and pieces of information that we can use to make wild guesses at what you might actually want, and then asking us which implementation would best fit that unseen design. – abarnert Nov 01 '13 at 21:50
  • @abarnert, I appreciate your commenting. I've rewritten both my question and my answer. Would you mind sharing your thoughts on them, please? – Shahar 'Dawn' Or Nov 05 '13 at 09:41
  • I'll write a new answer about your class design. – abarnert Nov 05 '13 at 18:59

4 Answers4

2

Not every thing have to be a class.
Execution in the Kingdom of Nouns
why not use a factory function ?

class Car(object):
....

class SuperCar(Car):
....

def CarFactory(max_speed):
     if max_speed > 100:
         return SuperCar()
     else: 
         return Car()
yossi
  • 12,945
  • 28
  • 84
  • 110
2

First, since you didn't give an example, here's the familiar toy minimal class hierarchy:

class Base(object):
    def __init__(self):
        print("I'm a base")

class Child(Base):
    def __init__(self):
        super(Child, self).__init__()
        print("I'm also a child")

Now, presumably you want to decide whether to be a Child or not in the middle of, or right after, Base.__init__.

The best way to do that is to not do it; just move the choice earlier. Then you don't need to do any re-classing; you just construct the class you actually want, and know it's going to be initialized as appropriate for that class.

You can either use a factory function:

def basefactory(child=False):
    if child:
        return Child()
    else:
        return Base()

… or an "alternate constructor" method:

class Base(object):
    def __init__(self):
        print("I'm a base")
    @classmethod
    def make(cls, child=False):
        if child:
            return Child()
        else:
            return cls()

Or a custom __new__ method:

class Base(object):
    def __init__(self):
        print("I'm a base")
    def __new__(cls, child=False):
        return super(Base, cls).__new__(Child if child else cls)

The advantage of all of these mechanisms is that __init__ works the way it's supposed to. If someone writes a normal, Pythonic __init__ method that correctly calls its superclass, it won't get trapped in an endless loop.

The only reason to re-__class__ something in the base __init__ would be if you wanted to explicitly prevent the child's __init__ getting called. That's a very rare case, but if it's what you actually want, it's easy:

class Base(object):
    def __init__(self, child=False):
        super(Base, self).__init__()
        if child:
            self.__class__ = Child
abarnert
  • 354,177
  • 51
  • 601
  • 671
0

Initially I've supplied an answer which is based on changing self.__class__ during __init__(). It was explained to me in the comments that this should be avoided.

Now I am providing an answer that that is based on redefining the base class' __new__:

class Car(object):
    """The base class."""

    def __new__(cls, max_speed):
        if cls is Car and max_speed > 100:
            return object.__new__(SuperCar, max_speed)
        else:
            return object.__new__(cls, max_speed)

    def __init__(self, max_speed):
        self.max_speed = max_speed

class SuperCar(Car):
    """The sub class."""

    pass   

The code responsible for the creation of an instance is in the built in(1) __new__ method. It is called when a class is called (e.g. my_car = Car(100)) and passed the arguments that were given in that call.

When the instanciation of a Car or a SuperCar (due to the inheritance of __new__) is called, the redefined __new__ checks whether the instance to be created is of cls Car and also has a max_speed greater than 100.

If so, it creates an instance of SuperCar, instead of Car, using object.__new__(SuperCar, max_speed) and returns that.

Otherwise, it calls upon the normal, default, built in, __new__ for the creation of this instance, using object.__new__(cls, max_speed) and returns that. This catches the expected case where not max_speed > 100 and also the case where this redefined __new__ is called by the subclass SuperCar (like in my_ferrari = SuperCar(250)).

For the record, here's my former answer:

When you want an instance's class to be changed to one of it's subclasses, depending on some condition, upon creation of that instance, you can use some logic in __init__, like so:

class Car(object):
    def __init__(self, max_speed_kph):
        if self.__class__ == Car:
            if max_speed_kph > 230:
                self.__class__ = SuperCar
                self.__init__()

class SuperCar(Car):
    def __init__(self):
        print 'this car is fast!'

The self.__init__() is for re-initialisation of the instance as it's new class.

Footnotes:

  1. Is this really a built-in? I can't see it in the "Built-in functions" topic of Python Documentation.
Shahar 'Dawn' Or
  • 2,713
  • 1
  • 26
  • 38
  • 2
    Checking `self.__class__.__name__` is a really bad idea. Why not just check `self.__class__ == Car`? – abarnert Nov 01 '13 at 21:04
  • And your second half is also a bad idea. Typically you expect the base class `__init__` to call the parent class, but in this case that would cause infinite recursion. If you really want to construct a `SuperCar`, just construct a `SuperCar`; don't construct a `Car`, turn it into a `SuperCar`, and then manually `__init__` it. The only reason re-classing really makes sense is when you want to _avoid_ the subclass `__init__`. – abarnert Nov 01 '13 at 21:05
  • @abarnert I've edited to check `self.__class__` and not `self.__class__.__name__` . Can you please rephrase 'Typically you expect the base class `__init__` to call the parent class' for me? I don't get it because `__init__` is not a class. – Shahar 'Dawn' Or Nov 01 '13 at 21:16
  • I actually stated that wrong ("base" and "parent" are the same thing—you want the _child_'s `__init__`, not the base's, to call the parent's). But the point is, a typical Python class hierarchy has all of the child classes doing `super(Child, self).__init__(some, subset, of, my, args)`. – abarnert Nov 01 '13 at 21:19
  • Oh, and "call the parent class" is a common shortcut for "call _the appropriate method on_ the parent class" (although in Python it's more often put as "call the super" or similar, because it's generally done through `super`). – abarnert Nov 01 '13 at 21:21
  • @abarnert, I appreciate your commenting on this. Would you mind taking a look at my newly changed answer? – Shahar 'Dawn' Or Nov 05 '13 at 09:38
0

I think you're misusing subclasses here.

A subclass is supposed to represent a subtype—something whose interface is visibly specialized. That isn't true in the case of odd vs. even magic squares. They both have the same methods, and do the same (caller-visible) things in response to those methods, and so on.

If the difference between an odd magic square and an even one is not what they do, but how they do it, then they aren't subclasses, there's just a single class with multiple policies.

There may be a reasonable MagicSquareSolver type, whose user is the MagicSquare type, which has to interact differently with odd and even subtypes. In that case, you want to make the separate policies into separate classes. You could base them on an abstract base class (which is rarely necessary in Python, but I'll show it anyway just for the hell of it):

class MagicSquareSolver(metaclass=abc.ABCMeta:
    def __init__(self, square):
        self.square = square
    @abc.abstractmethod
    def solve(self):
        pass

class OddMagicSquareSolver(MagicSquareSolver):
    def __init__(self, square):
        super().__init__(square)
        do_odd_specific_stuff(self)
    def solve(self):
        do_odd_specific_solution(self)

class EvenMagicSquareSolver(MagicSquareSolver):
    def __init__(self, square):
        super().__init__(square)
        do_even_specific_stuff(self)
    def solve(self):
        do_even_specific_solution(self)

class MagicSquare:
    def __init__(self, n):
        self.n = n
        self.cells = self.make_cells()
        if n % 2:
            self.solver = OddMagicSquareSolver(self)
        else:
            self.solver = EvenMagicSquareSolver(self)
    def solve(self):
        return self.solver.solve()

However, I think even this is likely overkill. What is the interface to solving a magic square, even from the square's point of view, beyond a single solve method? There might be an answer to that question if, e.g., you're planning to visualize the solution process or turn it into a helper for a GUI game (although even in that case, a solve_one_step method, or a solve generator method, might still be all you need; look at some of the open source Sudoku games for examples). But likely there's nothing.

And this is what yossi's answer (and the rant he linked to) are getting at: if the interface of a class is just a single method named do_it (or a more useful name that has exactly the same information as the class name itself), and there's no state that needs to live outside of an invocation of that method, you don't have a class at all, you have a function, clumsily hidden away. Some languages (notably Java and C#) force you to be clumsy like that, but Python does not; you can just write this:

class MagicSquare:
    def __init__(self, n):
        self.n = n
        self.fill_squares()
        self.cells = self.make_cells()
    def solve(self):
        if n % 2:
            return self._solve_odd()
        else:
            return self._solve_even()
    def _solve_odd(self):
        stuff()
    def _solve_even(self):
        other_stuff()
abarnert
  • 354,177
  • 51
  • 601
  • 671