元类有方法吗?

时间:2017-08-30 09:46:17

标签: python python-3.x methods metaclass

我试图为我的项目实现单例类,并在同一个StackOverflow中创建一个有趣的帖子

Creating a singleton in 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时所说的this.state.data.length> 0。否则它会显示正确的类。我不明白这种行为,有人可以解释一下吗?

3 个答案:

答案 0 :(得分:3)

类是其元类的实例。就像类的实例没有类的方法作为属性但仍然可以调用它们一样,类没有元类的方法作为属性。

答案 1 :(得分:3)

  

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时,您的班级可以看到这些方法。

  

向元类添加方法是否正确?

这取决于上下文。我想说将方法添加到元类是很好的。但是,这些应该很少使用。

  

正如你所看到的那样,当我把它装饰成classmethod时,它就是它的Singleton。否则它会显示正确的类。我不明白这种行为,有人可以解释一下吗?

如果你考虑一下这很明显。 Singleton是您someClass的一个类,当您创建classmethod时,cls参数将为Singleton。但是,已添加到_instances的课程为someClass。我可以看到它来自哪里。您的所有方法都采用cls参数。这可能让你相信它们是“类似”类方法(它们在某种程度上,但不是元类,而是实现元类的类!)。

但它只是一种约定,因为selfclass实例的典型参数名称,clsmetaclass实例的典型参数名称。 1}}。当你的元类上有类方法时,第一个参数应该被称为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.

所以你将“class”添加到dict中,然后检查元类是否在dict中(它不是)。

通常自定义classmethods(除了那些应该/可能是类方法,例如__prepare__)在元类上没有多大意义,因为你很少需要你的实例类的类型。

答案 2 :(得分:3)

  

python元类可以有方法吗?

  

但是当我做dir(someClass)时,没有显示2种方法

与您可能认为的相反,dir doesn't show everything

  

因为dir()主要是为了方便在交互式提示中使用而提供的,所以它试图提供一组有趣的名称,而不是尝试提供严格或一致定义的名称集,以及它的详细行为可能会在不同的版本例如,当参数是类时,元类属性不在结果列表中。

  

正如你所看到的那样,当我把它作为classmethod装饰时,它就是它的Singleton。

请勿使用classmethod进行装饰! 明确指出您希望该方法在Singleton本身或Singleton的子类上运行,而不是Singleton的实例。以Singleton作为其元类的类是singleton的实例;它们是类的事实并不是classmethodSingleton方法上的理由。