Вы можете управлять созданием экземпляра с помощью метаклассов (например) и убедиться, что это имя уникально. Давайте предположим, что метод __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
РЕДАКТИРОВАТЬ:
Поскольку ОП выступает этот подход, в сочетании 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
Это звучит как нечто более строгое соблюдение на другом уровне, если вообще. Если вы будете применять его так, вы в конце концов обнаружите, что «подождите, я хочу, чтобы имена были уникальными для [определенных структур данных/логических разделов программы]», или «подождите, я удалил старый объект с этим именем, почему я не могу повторно использовать это имя сейчас? " – user2357112
Вы будете упорствовать в этом? – IanAuld
После вашего редактирования я добавил комбинацию подклассов «metaclass» и «descriptor», которые пытаются использовать лучшее из обоих миров. В моих собственных проектах «метаклассы» даже удаляют параметры до того, как они коснутся '__init__', чтобы облегчить необходимость конечным пользователям справляться с этими параметрами. – mementum