确保python实例属性的唯一性

2024-10-02 08:28:57 发布

您现在位置:Python中文网/ 问答频道 /正文

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

编辑:我希望能够提供名称,而不是为其分配UUID。所以mementum的方法似乎是最有力的。杰科塔的是我会做的。在


Tags: 实例方法name名称元素编辑属性定义
3条回答

您可以使用元类控制实例创建(例如),并确保名称是唯一的。假设__init__方法接受一个没有默认值的参数name

class MyClass(object):

    def __init__(self, name, *args, **kwargs):
        self.name = name

显然,实例可以与此同名。让我们使用metaclass(使用兼容的python2/3语法)

^{pr2}$

哪些输出:

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和{}的组合方法包括:

  • name类属性作为descriptor在类创建过程中由metaclass添加
  • name在实例到达__init__之前每个实例初始化一次
  • name赋值操作的唯一性

  • descriptor类中存储控制名称唯一性的set和{}来消除类本身的污染

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

相关问题 更多 >

    热门问题