确保python中实例属性的唯一性

Spi*_*or8 2 python oop

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

编辑:我希望能够提供名称而不是为其分配 UUID。所以 mementum 的方法似乎是最稳健的。jpkotta 的就是我会做的。

mem*_*tum 6

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

class MyClass(object):

    def __init__(self, name, *args, **kwargs):
        self.name = name
Run Code Online (Sandbox Code Playgroud)

显然,实例可以与 this 有相同的名称。让我们使用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)
Run Code Online (Sandbox Code Playgroud)

哪个输出:

class MyClass(object):

    def __init__(self, name, *args, **kwargs):
        self.name = name
Run Code Online (Sandbox Code Playgroud)

使用该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)
Run Code Online (Sandbox Code Playgroud)

输出:

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)
Run Code Online (Sandbox Code Playgroud)

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))
Run Code Online (Sandbox Code Playgroud)

具有预期的输出(名称不能在__init__分配期间重复使用)

a.name: hello
b.name: goodbye
Duplicate Name caught
Run Code Online (Sandbox Code Playgroud)

编辑:

由于OP赞成这种方法,因此组合metaclass方法descriptor涵盖:

  • name在类创建期间descriptor添加的类属性metaclass
  • name在实例到达之前每个实例初始化__init__
  • name赋值操作也具有唯一性

  • 在类内部存储setitertools.counter控制名称唯一性descriptor,从而消除类本身的污染

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)
Run Code Online (Sandbox Code Playgroud)

其输出预期:

a.name: MyClass_0
b.name: MyClass_1
c.name: MyClass_2
Run Code Online (Sandbox Code Playgroud)