I have several classes. The desired behavior on an instance creation is that an instance is assigned an ID. For simplicity, let us assume that IDs should start at 0 and increase by 1 with every instance creation. For each of these several classes, the IDs should be incremented independently.
I know how to do this in C++. I have actually also done that in Python, but I do not like it as much as the C++ solution, and I am wondering whether it is due to my limited knowledge of Python (little more than 6 weeks), or whether there is a better, more Pythonic way.
In C++, I have implemented this both using inheritance, and using composition. Both implementations use the Curiously Recurring Template Pattern (CRPT) idiom. I slightly prefer the inheritance way:
#include <iostream>
template<class T>
class Countable{
static int counter;
public:
int id;
Countable() : id(counter++){}
};
template<class T>
int Countable<T>::counter = 0;
class Counted : public Countable<Counted>{};
class AnotherCounted: public Countable<AnotherCounted>{};
int main(){
Counted element0;
Counted element1;
Counted element2;
AnotherCounted another_element0;
std::cout << "This should be 2, and actually is: " << element2.id << std::endl;
std::cout << "This should be 0, and actually is: " << another_element0.id << std::endl;
}
to the composion way:
#include <iostream>
template<class T>
class Countable{
static int counter;
public:
int id;
Countable() : id(counter++){}
};
template<class T>
int Countable<T>::counter = 0;
class Counted{
public:
Countable<Counted> counterObject;
};
class AnotherCounted{
public:
Countable<AnotherCounted> counterObject;
};
int main(){
Counted element0;
Counted element1;
Counted element2;
AnotherCounted another_element0;
std::cout << "This should be 2, and actually is: " << element2.counterObject.id << std::endl;
std::cout << "This should be 0, and actually is: " << another_element0.counterObject.id << std::endl;
}
Now, in python, there are no templates which would give me different counters for each class. Thus, I wrapped the countable class to a function, and obtained the following implementation: (inheritance way)
def Countable():
class _Countable:
counter = 0
def __init__(self):
self.id = _Countable.counter
_Countable.counter += 1
return _Countable
class Counted ( Countable() ) :
pass
class AnotherCounted( Countable() ):
pass
element0 = Counted()
element1 = Counted()
element2 = Counted()
another_element0 = AnotherCounted()
print "This should be 2, and actually is:", element2.id
print "This should be 0, and actually is:", another_element0.id
and the composition way:
def Countable():
class _Countable:
counter = 0
def __init__(self):
self.id = _Countable.counter
_Countable.counter += 1
return _Countable
class Counted ( Countable() ) :
counterClass = Countable()
def __init__(self):
self.counterObject = Counted.counterClass()
class AnotherCounted( Countable() ):
counterClass = Countable()
def __init__(self):
self.counterObject = self.counterClass()
element0 = Counted()
element1 = Counted()
element2 = Counted()
another_element0 = AnotherCounted()
print "This should be 2, and actually is:", element2.counterObject.id
print "This should be 0, and actually is:", another_element0.counterObject.id
What troubles me is this. In C++, I have a good idea what I am doing, and e.g. I see no problems even if my classes actually inherit multiply (not just from Countable<> templated class) - everything is very simple.
Now, in Python, I see the following issues:
1) when I use composition, I instantiate the counting class like that:
counterClass = Countable()
I have to do this for every class, and this is possibly error-prone.
2) when I use inheritance, I will bump to further troubles when I will want to ihnerit multiply. Note that above, I have not defined the __init__
's of Counted nor of AnotherCounted, but if I inherited multiply I would have to call base class constructors explicitly, or using super(). I do not like this (yet?) I could also use metaclasses, but my knowledge is limited there and it seems that it adds complexity rather than simplicity.
In conclusion, I think that composition way is probably better for Python implementation, despite the issue with having to explicitly define the counterClass class attribute with Countable().
I would appreciate your opinion on validity of my conclusion.
I would also appreciate hints on better solutions than mine.
Thank you.