I have a base class Numero
and numerous specialized subclasses (decimal, float, times, lat/lon, etc). The base class and subclasses are instantiated from the string representation:
valor1 = NumeroEntero("10")
valor2 = NumeroDecimal("10.1")
valor3 = NumeroPosicion("40.423261°N 3.712594°W")
What I'm looking for is to be able to simply say
valor1 = Numero("10") => type(valor1) es NumeroEntero
valor2 = Numero("10.1") => type(valor2) es NumeroDecimal
valor3 = Numero("40.423261°N 3.712594°W") => type(valor3) es NumeroPosicion
and obtain as a result three objects of different subclasses.
This (illegal) code shows what I intend:
class Numero:
def __init__(self, valor: str):
if '.' in valor:
return NumeroDecimal(valor)
else:
return NumeroEntero(valor)
class NumeroEntero(Numero):
def __init__(self, valor: str):
self.valor = int(valor)
class NumeroDecimal(Numero):
def __init__(self, valor: str):
self.valor = float(valor)
var1 = Numero("10") => type(var1) == NumeroEntero
var2 = Numero("10.0") => type(var2) == NumeroDecimal
I could just write a function that receives the text, examines it and returns the appropriate object, but I find that inelegant.
Does anyone know a hack to solve it?
What you need is a factory , that is, a method or function that can create objects.
__init__()
it is not a factory, it cannot create objects, it can only initialize previously created objects. In fact__init__()
it never returns anything (and if it does, the return value is ignored).But every class has a method
__new__()
that is responsible for creating the object, and which Python calls before calling__init__()
. It is in that method where you must do your "magic".The problem is that the thing is not as simple as:
The reason why it doesn't work is that when trying to instantiate an object through the syntax
NumeroDecimal()
, python will callNumeroDecimal.__new__()
, and since it is not implemented, it will call the one__new__()
of its base class, so we end up entering again byNumero.__new__()
and we are in a infinite recursion loop that terminates when it breaks the stack.That forces us to use this other slightly more cumbersome syntax:
Result:
By doing it this way:
super().__new__(NumeroDecimal)
we are calling the__new__()
from the base class ofNumero
. In this case there is "no" base class, so your base class is actuallyobject
. This is the Python class that every other class derives from, and it contains the method__new__()
that ultimately creates the objects (allocates memory, etc... that stuff that happens behind the scenes). The method__new__()
always receives as its first parameter the class it is supposed to instantiate, although it can instantiate something else, as our factory does.More details
When the code does for example:
the following happens:
Numero.__new__(Numero, "10")
and collects the value that it returns in a temporary variable, let 's call itobj
(ifNumero
it does not implement__new__()
, the one__new__()
of its base class will be called, but passing itNumero
as the first parameter)obj.__init__("10")
and returnobj
obj
to the variablen
In your case, step 1 is going to cause a call to
super().__new__(NumeroEntero)
, that is, a call toobject.__new__(NumeroEntero)
. This function is implemented internally by python and makes the necessary memory allocation for an object of typeNumeroEntero
(not yet initialized), and returns that reference, which is returned by your function as a result of theNumero.__new__()
An unforeseen side effect
As explained, we now have a new problem. We cannot force the instance to be
NumeroDecimal
orNumeroEntero
at will. For example:Although we have asked to instantiate a
NumeroDecimal
, the result has been aNumeroEntero
. It is foreseeable that this happens because theNumeroDecimal.__new__()
, is not implemented, so it goes to its base class, it is calledNumero.__new__()
and therefore it is the factory that decides the type, based on the string "10".To avoid this problem we should make the factory decide the type based on the string, only if it is being used with the syntax
Numero("10")
, but not if it is reached from the syntaxNumerDecimal("10")
. How to differentiate these cases? Well, by the first parametercls
that the factory receives, which would beNumero
in the first case butNumeroDecimal
in the second.If
cls
it IS NOTNumero
, then the factory must return an object of the type specified incls
and if it isNumero
, the factory will decide intelligently according to the value of the received string.So that:
And now we can see that everything works as it should: