实现具有自动递增实例属性的类的最 Pythonic 方式是啥?

Posted

技术标签:

【中文标题】实现具有自动递增实例属性的类的最 Pythonic 方式是啥?【英文标题】:What is the most Pythonic way of implementing classes with auto-incrementing instance attributes?实现具有自动递增实例属性的类的最 Pythonic 方式是什么? 【发布时间】:2013-06-18 15:40:11 【问题描述】:

我有几节课。实例创建所需的行为是为实例分配一个 ID。为简单起见,我们假设 ID 应该从 0 开始,并随着每个实例的创建增加 1。对于这几个类中的每一个,ID 应该独立递增。

我知道如何在 C++ 中做到这一点。我实际上也在 Python 中做过,但我不像 C++ 解决方案那样喜欢它,我想知道这是否是由于我对 Python 的了解有限(略超过 6 周),或者是否有更好的,更 Pythonic 的方式。

在 C++ 中,我使用继承和组合实现了这一点。两种实现都使用奇怪重复的模板模式 (CRPT) 习惯用法。我稍微偏爱继承方式:

#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; 

对作曲方式:

#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; 

现在,在 python 中,没有模板可以为每个类提供不同的计数器。于是,我把可数类包装成一个函数,得到如下实现:(继承方式)

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

及组成方式:

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

让我烦恼的是这个。在 C++ 中,我很清楚自己在做什么,例如即使我的类实际上继承了 multiply (不仅仅是来自 Countable 模板类),我也看不到任何问题——一切都非常简单。

现在,在 Python 中,我看到了以下问题:

1) 当我使用组合时,我会像这样实例化计数类:

counterClass = Countable()

我必须为每个班级都这样做,这可能容易出错。

2) 当我使用继承时,当我想要 ihnerit 乘法时会遇到更多麻烦。请注意,在上面,我没有定义 Counted 的 __init__'s 和 AnotherCounted,但是如果我继承了 multiply,我将不得不显式调用基类构造函数,或者使用 super()。我不喜欢这个(还没有?)我也可以使用元类,但我的知识有限,似乎它增加了复杂性而不是简单性。

总之,我认为组合方式可能更适合 Python 实现,尽管存在必须使用 Countable() 显式定义 counterClass 类属性的问题。

感谢您对我的结论的有效性提出意见。

我也希望得到比我更好的解决方案的提示。

谢谢。

【问题讨论】:

【参考方案1】:

我会使用__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

【讨论】:

漂亮的解决方案。感谢您的分享。我学到了一些新的有用的东西。 考虑一下,由于counter(它是一个int)的不变性,这可以工作。顺便说一句,似乎可以在不使用__new__ 而是使用__init__(self) 并将计数器称为type(self).counter 的情况下获得相同的行为。我现在想知道的是:我怎样才能解决类似的问题,但是现在counter 是一个列表,应该附加一个元素 1 以便上面的代码在第一个对象创建后生成 [],在第二个对象创建后生成 [1] , [1, 1] 在第三个之后,等等? 如果你使用__init__,你必须记住在子类中调用super().__init__。您真的需要这样的列表吗?使用[1] * self.id 按需创建它太慢了吗?在第一次创建之前,该列表计数器应该是什么? 并不是我真的需要这份清单。我只是在考虑如何在 python 中模拟具有静态成员的 C++ 模板类。所以,在python中,假设我有一个类属性l=list()的类Base,每次我从Base继承时,我希望派生类有一个独立类属性l,以@987654334开头@。到目前为止,我使用基类中的 dict 实现了这一点,其键是派生类,值是“克隆”属性。这就是背景。但我觉得我需要先深入研究 Python 文档并彻底阅读。 对不起,由于类是一等对象,Python 继承与 C++ 太不一样了。如果您想自动将类属性添加到子类,最好的选择是元类(控制其他类创建的类,有点像您的 Countable 函数)。【参考方案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

【讨论】:

以上是关于实现具有自动递增实例属性的类的最 Pythonic 方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章

获取从抽象类继承并在属性中具有特定值的类的实例

什么是依赖注入的 Pythonic 方式?

Python中类的声明,使用,属性,实例属性,计算属性及继承,重写

g++不能对具有虚拟方法的类的实例的运行时大小进行更多优化吗?

在“绑定”数据网格视图中设置工具提示

JavaScript的类