我编写了一个带有实例属性的类,将其命名为。确保班级的所有实例都具有唯一名称的最佳方法是什么?我是否在类下创建了一个集合,每次创建新实例时,名称都会添加到init定义中的集合中?由于集合是唯一元素的集合,因此我可以验证新实例的名称是否可以成功添加到集合中。
编辑:我希望能够提供名称,而不是为其分配UUID。因此,mementum的方法似乎是最强大的。 jpkotta是我本来会做的。
答案 0 :(得分:1)
您可以使用元类控制实例创建(例如),并确保名称是唯一的。我们假设__init__
方法采用没有默认值的参数name
class MyClass(object):
def __init__(self, name, *args, **kwargs):
self.name = name
显然,实例可以与此具有相同的名称。让我们使用metaclass
(使用兼容的Python 2/3语法)
class MyMeta(type):
_names = set()
@classmethod
def as_metaclass(meta, *bases):
'''Create a base class with "this metaclass" as metaclass
Meant to be used in the definition of classes for Py2/3 syntax equality
Args:
bases: a list of base classes to apply (object if none given)
'''
class metaclass(meta):
def __new__(cls, name, this_bases, d):
# subclass to ensure super works with our methods
return meta(name, bases, d)
return type.__new__(metaclass, str('tmpcls'), (), {})
def __call__(cls, name, *args, **kwargs):
if name in cls._names:
raise AttributeError('Duplicate Name')
cls._names.add(name)
return type.__call__(cls, name, *args, **kwargs)
class MyClass(MyMeta.as_metaclass()):
def __init__(self, name, *args, **kwargs):
self.name = name
a = MyClass('hello')
print('a.name:', a.name)
b = MyClass('goodbye')
print('b.name:', b.name)
try:
c = MyClass('hello')
except AttributeError:
print('Duplicate Name caught')
else:
print('c.name:', c.name)
哪个输出:
a.name: hello
b.name: goodbye
Duplicate Name caught
使用metaclass
技术甚至可以避免将name
作为参数,并且可以为每个实例自动生成名称。
import itertools
class MyMeta(type):
_counter = itertools.count()
@classmethod
def as_metaclass(meta, *bases):
'''Create a base class with "this metaclass" as metaclass
Meant to be used in the definition of classes for Py2/3 syntax equality
Args:
bases: a list of base classes to apply (object if none given)
'''
class metaclass(meta):
def __new__(cls, name, this_bases, d):
# subclass to ensure super works with our methods
return meta(name, bases, d)
return type.__new__(metaclass, str('tmpcls'), (), {})
def __call__(cls, *args, **kwargs):
obj = type.__call__(cls, *args, **kwargs)
obj.name = '%s_%d' % (cls.__name__, next(cls._counter))
return obj
class MyClass(MyMeta.as_metaclass()):
pass
a = MyClass()
print('a.name:', a.name)
b = MyClass()
print('b.name:', b.name)
c = MyClass()
print('c.name:', c.name)
输出:
a.name: MyClass_0
b.name: MyClass_1
c.name: MyClass_2
要完成问题并回答关于阻止a.name = b.name
(或已使用的任何其他名称)的评论,可以使用基于descriptor
的方法
class DescName(object):
def __init__(self):
self.cache = {None: self}
def __get__(self, obj, cls=None):
return self.cache[obj]
def __set__(self, obj, value):
cls = obj.__class__
if value in cls._names:
raise AttributeError('EXISTING NAME %s' % value)
try:
cls._names.remove(self.cache[obj])
except KeyError: # 1st time name is used
pass
cls._names.add(value)
self.cache[obj] = value
class MyClass(object):
_names = set()
name = DescName()
def __init__(self, name, *args, **kwargs):
self.name = name
a = MyClass('hello')
print('a.name:', a.name)
b = MyClass('goodbye')
print('b.name:', b.name)
try:
c = MyClass('hello')
except AttributeError:
print('Duplicate Name caught')
else:
print('c.name:', c.name)
a.name = 'see you again'
print('a.name:', a.name)
try:
a.name = b.name
except AttributeError:
print('CANNOT SET a.name to b.name')
else:
print('a.name %s = %s b.name' % (a.name, b.name))
使用预期输出(名称在__init__
或分配期间无法重复使用)
a.name: hello
b.name: goodbye
Duplicate Name caught
a.name: see you again
CANNOT SET a.name to b.name
编辑:
由于OP赞成这种方法,因此合并metaclass
和descriptor
方法涵盖:
name
在创建课程时由descriptor
添加的metaclass
类属性name
之前,每个实例初始化__init__
分配操作的 name
唯一性
在set
类中存储itertools.counter
和descriptor
控件名称唯一性,从而消除类本身的污染
import itertools
class MyMeta(type):
class DescName(object):
def __init__(self, cls):
self.cache = {None: self, cls: set()}
self.counter = {cls: itertools.count()}
def __get__(self, obj, cls=None):
return self.cache[obj]
def __set__(self, obj, value):
self.setname(obj, value)
def setname(self, obj, name=None):
cls = obj.__class__
name = name or '%s_%d' % (cls.__name__, next(self.counter[cls]))
s = self.cache[cls]
if name in s:
raise AttributeError('EXISTING NAME %s' % name)
s.discard(self.cache.get(obj, None))
s.add(name)
self.cache[obj] = name
def __new__(meta, name, bases, dct):
cls = super(MyMeta, meta).__new__(meta, name, bases, dct)
cls.name = meta.DescName(cls) # add the name class attribute
return cls
@classmethod
def as_metaclass(meta, *bases):
class metaclass(meta):
def __new__(cls, name, this_bases, d):
# subclass to ensure super works with our methods
return meta(name, bases, d)
return type.__new__(metaclass, str('tmpcls'), (), {})
def __call__(cls, *args, **kwargs):
# Instead of relying on type we do the new and init calls
obj = cls.__new__(cls, *args, **kwargs)
cls.name.setname(obj)
obj.__init__(*args, **kwargs)
return obj
class MyClass(MyMeta.as_metaclass()):
def __init__(self, *args, **kwargs):
print('__init__ with name:', self.name)
a = MyClass()
b = MyClass()
c = MyClass()
a.name = 'my new name'
print('a.name:', a.name)
try:
a.name = b.name
except AttributeError as e:
print(e)
else:
print('a.name %s == %s b.name' % (a.name, b.name))
预期输出的内容:
__init__ with name: MyClass_0
__init__ with name: MyClass_1
__init__ with name: MyClass_2
a.name: my new name
EXISTING NAME MyClass_1
答案 1 :(得分:1)
Python 2.7.10 (default, Oct 23 2015, 18:05:06)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from uuid import uuid4
>>>
>>> class class_with_unique_name(object):
... def __init__(self):
... self.name = str(uuid4())
...
...
>>> class_with_unique_name().name
'e2ce6b4e-8989-4390-b044-beb71d834385'
>>> class_with_unique_name().name
'ff277b1b-b149-47f6-9dc9-f9faecd18d11'
>>> class_with_unique_name().name
'63f70cbc-4f0a-4c8b-a35f-114bc5b8bc8d'
>>> class_with_unique_name().name
'a6f95523-ae43-4900-9366-022326474210'
>>> class_with_unique_name().name
'4e7c1200-bd45-427e-bcf0-e643b41f6347'
>>> class_with_unique_name().name
'58fa246e-4f99-49d4-9420-68234e24c921'
>>> class_with_unique_name().name
'7c86b351-fdb9-40c1-8021-b93c70e8e24d'
答案 2 :(得分:0)
class Foo():
_names = set()
@property
def name(self):
return self._name
def __init__(self, name):
if name in Foo._names:
raise NameError("Already used name '%s'." % name)
self._name = name
Foo._names.add(name)
对我来说,这比弄乱元类等更简单。如果你需要为几个类做这个,那么元类更有意义。
使name成为没有write方法的属性会使赋值失败,从而有效地使name
保持不变。
如果您继承Foo
,它会在所有子类中保留相同的名称集,这可能是您想要的,也可能不是。