2

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.

4

2 回答 2

4

我会使用__new__,这样你就不必记住在__init__

class Countable(object):
    counter = 0
    def __new__(cls, *a, **kw):
        instance = super(Countable, cls).__new__(cls, *a, **kw)
        instance.id = cls.counter + 1
        cls.counter = instance.id
        return instance


class A(Countable):
    pass


class B(Countable):
    pass


print A().id, A().id   # 1 2
print B().id           # 1
于 2013-06-18T15:56:18.117 回答
2

我可能会使用一个简单的类装饰器......

import itertools
def countable(cls):
    cls.counter = itertools.count()
    return cls

@countable
class Foo(object):
    def __init__(self):
        self.ID = next(self.__class__.counter)

@countable
class Bar(Foo):
    pass

f = Foo()
print f.ID
b = Bar()
print b.ID

如果你真的想以“花哨”的方式做到这一点,你可以使用元类:

import itertools
class Countable(type):
    def __new__(cls,name,bases,dct):
        dct['counter'] = itertools.count()
        return super(Countable,cls).__new__(cls,name,bases,dct)

class Foo(object):
    __metaclass__ = Countable
    def __init__(self):
        self.ID = next(self.__class__.counter)

class Bar(Foo):
    pass

f = Foo()
print f.ID
b = Bar()
print b.ID
于 2013-06-18T15:50:55.127 回答