确保python中instance属性的唯一性

时间:2016-01-15 19:41:18

标签: python oop

我编写了一个带有实例属性的类,将其命名为。确保班级的所有实例都具有唯一名称的最佳方法是什么?我是否在类下创建了一个集合,每次创建新实例时,名称都会添加到init定义中的集合中?由于集合是唯一元素的集合,因此我可以验证新实例的名称是否可以成功添加到集合中。

编辑:我希望能够提供名称,而不是为其分配UUID。因此,mementum的方法似乎是最强大的。 jpkotta是我本来会做的。

3 个答案:

答案 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赞成这种方法,因此合并metaclassdescriptor方法涵盖:

  • name在创建课程时由descriptor添加的metaclass类属性
  • 在实例到达name 之前,每个实例初始化
  • __init__ 分配操作的
  • name唯一性

  • set类中存储itertools.counterdescriptor控件名称唯一性,从而消除类本身的污染

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,它会在所有子类中保留相同的名称集,这可能是您想要的,也可能不是。