2016-01-15 3 views
1

Я написал класс с атрибутом экземпляра, назову его именем. Каков наилучший способ обеспечить, чтобы все экземпляры класса имели уникальные имена? Я создаю под классом, набор и каждый раз, когда создается новый экземпляр, имя добавляется к набору в определении init? Поскольку набор представляет собой набор уникальных элементов, я могу поэтому проверить, можно ли успешно добавить имя нового экземпляра в набор.Обеспечить уникальность атрибута instance в python

EDIT: Я хотел иметь возможность указать имя вместо присвоения ему UUID. Таким образом, подход mementum кажется самым надежным. jpkotta - это то, что я бы сделал.

+2

Это звучит как нечто более строгое соблюдение на другом уровне, если вообще. Если вы будете применять его так, вы в конце концов обнаружите, что «подождите, я хочу, чтобы имена были уникальными для [определенных структур данных/логических разделов программы]», или «подождите, я удалил старый объект с этим именем, почему я не могу повторно использовать это имя сейчас? " – user2357112

+0

Вы будете упорствовать в этом? – IanAuld

+0

После вашего редактирования я добавил комбинацию подклассов «metaclass» и «descriptor», которые пытаются использовать лучшее из обоих миров. В моих собственных проектах «метаклассы» даже удаляют параметры до того, как они коснутся '__init__', чтобы облегчить необходимость конечным пользователям справляться с этими параметрами. – mementum

ответ

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 

РЕДАКТИРОВАТЬ:

Поскольку ОП выступает этот подход, в сочетании 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 
+0

TLDR - предотвращает ли это' b.name = a.name'? – Aprillion

+0

OP хотел контролировать создание ... не модификацию. Нет, нет.Вы должны (или можете) использовать «дескриптор» для управления этим – mementum

+0

. Добавленный подход на основе дескриптора – mementum

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' 
+0

Хотя это, безусловно, обеспечит уникальность, возможно, это не то, что хочет OP. Похоже, они хотят иметь возможность объявлять «имя» при создании экземпляра. Необходимы дополнительные разъяснения. – IanAuld

+0

прекрасно решает эту проблему: «Я написал класс с атрибутом экземпляра, назову его именем. Каков наилучший способ гарантировать, что все экземпляры класса будут иметь уникальные имена?» – rbp

+0

согласитесь, что это часть * наилучшего способа *, чтобы сделать то, что сказал OP - 'self.name' должно быть uneditable, хотя .. никаких двусмысленностей в требованиях, как указано. возможность объявления пользовательского 'name' несовместима с обеспечением уникальности, поскольку ничего не сказано о том, что должно произойти при попытке объявить то же имя или изменить существующее имя. – Aprillion

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 константой.

Если вы являетесь подклассом Foo, он сохраняет одинаковый набор имен во всех подклассах, что может быть или не быть тем, что вы хотите.

Смежные вопросы