Я пытался реализовать синглтон-классы для своего проекта и интересный пост в StackOverflow на том же

Создание синглтона в Python

Я решил пойти с упомянутым подходом метаклассов ..

Теперь .. Я попытался добавить метод для получения и очистки экземпляров (в случае, если пользователь хочет избавиться от текущего экземпляра и создать новый ..):

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

    def getInstance(cls):
        print("Class is {}".format(cls.__name__))

        if not cls in cls._instances:
            raise LookupError("No instance of the class {cls} create yet.".format(cls.__name__))

        return cls._instances[cls]

    def clearInstance(cls):
        cls._instances.pop(cls, None)


class someClass(metaclass=Singleton):
    def __init__(self,val):
        self.value = val

Я могу успешно создавать объекты ..

In [9]: sc = someClass(1)

In [10]: sc.value
Out[10]: 1

Но когда я делаю dir(someClass), 2 метода не отображаются:

In [14]: dir(someClass)
Out[14]:
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']

Тем не менее я могу назвать методы ..

In [13]: someClass.getInstance()

Class is someClass

Out[13]: <__main__.someClass at 0x7f728b180860>

Во всех примерах метакласса, которые я вижу в сети, я вижу реализованные методы __new__, __init__ и __call__, но я не вижу добавленных дополнительных методов. Правильно ли добавлять методы в метакласс?

Я также попробовал небольшой вариант приведенного выше кода метакласса:

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

    @classmethod
    def getInstance(cls):
        print("Class is {}".format(cls.__name__))

        if not cls in cls._instances:
            raise LookupError("No instance of the class {cls} create yet.".format(cls.__name__))

        return cls._instances[cls]

    @classmethod
    def clearInstance(cls):
        cls._instances.pop(cls, None)

Помечены 2 метода как метод класса.

Теперь, когда я пытаюсь позвонить им:

In [2]: sc = someClass(1)

In [3]: someClass.getInstance()

Class is Singleton
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-3-c83fe01aa254> in <module>()
----> 1 someClass.getInstance()

<ipython-input-1-9efb6548d92d> in getInstance(cls)
     12
     13                 if not cls in cls._instances:
---> 14                         raise LookupError("No instance of the class {cls} create yet.".format(cls.__name__))
     15
     16                 return cls._instances[cls]

KeyError: 'cls'

Как видите, печать class is гласит Singleton, когда я украшаю ее как classmethod. В противном случае он показывает правильный класс. Я не понимаю это поведение, кто-то может объяснить?

1
Arun Kaliraja Baskaran 30 Авг 2017 в 12:46

3 ответа

Лучший ответ

Могут ли метаклассы python иметь методы?

Да.


Но когда я делаю dir (someClass), 2 метода не отображаются

Вопреки тому, во что вы могли поверить, dir не показывает все :

Поскольку dir() предоставляется главным образом для удобства использования в интерактивном приглашении, он пытается предоставить интересный набор имен больше, чем он пытается предоставить строго или последовательно определенный набор имен, и его подробное поведение может изменяться в зависимости от релизы. Например, атрибуты метакласса отсутствуют в списке результатов, когда аргумент является классом .


Как вы можете видеть, класс «print» говорит, что его Singleton, когда я украшаю его как метод класса.

Не украшайте его classmethod! Это определенно говорит , что вы хотите, чтобы метод работал с самим Singleton или подклассами Singleton, а не с экземплярами Singleton. Классы с метаклассом Singleton являются экземплярами из singleton; тот факт, что они являются классами, не является причиной для того, чтобы classmethod использовать методы Singleton.

3
user2357112 supports Monica 30 Авг 2017 в 22:42

Могут ли метаклассы python иметь методы?

Да, как показывает ваш первый пример, они могут иметь методы, и их можно вызывать в классах, которые реализуют ваш метакласс.

Например, в python-3.x метакласс type реализует атрибут mro:

>>> object.mro()
[object]

Но вы не можете получить к ним доступ в следующих случаях:

>>> object().mro()
AttributeError: 'object' object has no attribute 'mro'

Но когда я делаю dir(someClass), 2 метода не отображаются.

dir вызывает type.__dir__, и это просто показывает ограниченное количество методов:

Если объект является объектом типа или класса, список содержит имена его атрибутов и рекурсивные атрибуты его баз.

Никаких упоминаний о методах метакласса здесь нет. Это потому, что они скрыты по умолчанию.

Вот почему вы также не видите метод mro:

>>> 'mro' in dir(object)
False

Однако dir позволяет настроить то, что вы видите, так что вы можете просто переопределить это. Поскольку __dir__ вызывается для "класса экземпляра", а ваш "метакласс является типом вашего класса", вы должны реализовать его в своем метаклассе:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]
    def getInstance(cls):
        print("Class is {}".format(cls.__name__))
        if not cls in cls._instances:
            raise LookupError("No instance of the class {cls} create yet.".format(cls.__name__))
        return cls._instances[cls]
    def clearInstance(cls):
        cls._instances.pop(cls, None)

    def __dir__(self):
        normal_dir = type.__dir__(self)
        # Also include all methods that don't start with an underscore and
        # not "mro".
        normal_dir.extend([f for f in dir(type(self)) 
                           if not f.startswith('_') and f != 'mro'])
        return normal_dir

class someClass(metaclass=Singleton):
    def __init__(self,val):
        self.value = val

>>> dir(someClass)
[..., 'clearInstance', 'getInstance']

Теперь эти методы вашего класса видны, когда вы вызываете dir.

Правильно ли добавлять методы в метакласс?

Это немного зависит от контекста. Я бы сказал, что можно добавить методы в метакласс. Тем не менее, они должны использоваться редко.

Как вы можете видеть, класс print говорит, что его Singleton, когда я его украшаю как classmethod. В противном случае он показывает правильный класс. Я не понимаю это поведение, кто-то может объяснить?

Это очевидно, если вы думаете об этом. Singleton - это класс вашего someClass, и когда вы сделаете это classmethod, аргумент cls будет Singleton. Однако класс, который был добавлен в _instances, это someClass. Я могу видеть, откуда это происходит, хотя. Все ваши методы принимают аргумент cls. Это, вероятно, заставило вас поверить, что они «похожи» на методы класса (и они в некотором роде, но не метакласса, а класса, который реализует метакласс!).

Но это просто соглашение, потому что, как self является типичным именем аргумента для экземпляра class, так и cls является типичным именем аргумента для экземпляра metaclass , Если в вашем метаклассе есть методы класса, первый аргумент, вероятно, должен называться metacls. Также исправление небольшой проблемы с str.format (по этой причине он выбрасывает KeyError вместо LookupError):

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
            print(cls._instances)  # print the dict after creating an instance
        return cls._instances[cls]
    @classmethod
    def getInstance(metacls):
        print("Class is {}".format(metacls))
        if not metacls in metacls._instances:
            raise LookupError("No instance of the class {0} create yet.".format(metacls.__name__))
        return metacls._instances[metacls]
    @classmethod
    def clearInstance(metacls):
        metacls._instances.pop(metacls, None)

class someClass(metaclass=Singleton):
    def __init__(self,val):
        self.value = val

>>> sc = someClass(1)
{<class '__main__.someClass'>: <__main__.someClass object at 0x00000235844F8CF8>}
>>> someClass.getInstance()
Class is <class '__main__.Singleton'>
LookupError: No instance of the class Singleton create yet.

Таким образом, вы добавляете «класс» к диктовке, но затем проверяете, есть ли метакласс в диктовке (а это не так).

Обычно пользовательские classmethods (кроме тех, которые должны / могли бы быть методами класса, например __prepare__) в метаклассе, не имеют особого смысла, потому что вам редко требуется тип класса вашего экземпляра.

4
MSeifert 31 Авг 2017 в 17:14

Классы являются экземплярами своих метаклассов. Так же, как экземпляры классов не имеют методы класса в качестве атрибутов, но, тем не менее, могут вызывать их, классы не имеют методы метакласса в качестве атрибутов.

3
Ignacio Vazquez-Abrams 30 Авг 2017 в 10:32