实现具有自动递增实例属性的类的最 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 方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章
Python中类的声明,使用,属性,实例属性,计算属性及继承,重写