Лучший способ кодировать одиночный код, который принимает аргументы - PullRequest
0 голосов
/ 25 октября 2019

Мне было интересно, есть ли более элегантный и чистый способ кодирования Singleton декоратора ниже. Я пытался кодировать его как метакласс на основе type и добавлять metaclass=Singleton к классам Uid и Gid, но это невозможно для сложных объектов из-за этого сообщения об ошибке:

TypeError: конфликт метаклассов: метакласс производного класса должен быть (нестрогим) подклассом метаклассов всех его баз.

from pwd import getpwuid
from grp import getgrgid
from collections import UserString
from weakref import WeakValueDictionary


class NaiveSingleton:
    # We have to use WeakValueDictionary so objects can be garbage-collected
    instances = WeakValueDictionary()

    def __init__(self, cls):
        self.cls = cls

    def __call__(self, *args, **kwargs):
        key = (args, frozenset(kwargs.items()))
        if key not in self.instances:
            instance = self.cls(*args, **kwargs)
            self.instances[key] = instance
        return self.instances[key]


class Singleton:    # pylint: disable=no-member
    """
    Singleton decorator to avoid having multiple objects handling the same args
    """
    def __new__(cls, klass):
        # We must use WeakValueDictionary() to let the instances be garbage-collected
        _dict = dict(cls.__dict__, **{'cls': klass, 'instances': WeakValueDictionary()})
        singleton = type(klass.__name__, cls.__bases__, _dict)
        return super().__new__(singleton)

    def __instancecheck__(self, other):
        return isinstance(other, self.cls)

    def __call__(self, *args, **kwargs):
        key = (args, frozenset(kwargs.items()))
        if key not in self.instances:
            instance = self.cls(*args, **kwargs)
            self.instances[key] = instance
        return self.instances[key]


@Singleton
class Uid(UserString, str):     # str is added at the end so issubclass(obj, str) works
    _name = None

    @property
    def name(self):
        if self._name is None:
            self._name = getpwuid(int(self.data)).pw_name
        return self._name

@Singleton
class Gid(UserString, str):     # str is added at the end so issubclass(obj, str) works
    _name = None

    @property
    def name(self):
        if self._name is None:
            self._name = getgrgid(int(self.data)).gr_name
        return self._name

uid = Uid('0')
gid = Gid('0')

# The id's would be the same if we use the NaiveSingleton
print(id(uid), id(gid))
print(uid.name, gid.name)
...