Skip to main content
 首页 » 编程设计

python之元类可以有方法吗

2025年05月04日10duanxz

我正在尝试为我的项目实现单例类,并在 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) 时,这两个方法没有显示:

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' 

如您所见,当我将它装饰为 classmethod 时,class is print 显示其 Singleton。否则它显示正确的类。我不明白这种行为,有人可以解释一下吗?

请您参考如下方法:

Can python metaclasses have methods?

是的,正如您的第一个示例所示,它们可以具有方法,并且可以在实现您的元类的类上调用它们。

例如在 python-3.x 中,元类 type 实现了 mro 属性:

>>> object.mro() 
[object] 

但是你不能在实例上访问它们:

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

But when I do dir(someClass) the 2 methods are not displayed.

dir调用 type.__dir__ 并且只显示了有限数量的方法:

If the object is a type or class object, the list contains the names of its attributes, and recursively of the attributes of its bases.

这里没有提到元类的方法。那是因为它们在默认情况下是隐藏的。

这就是为什么您也看不到 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 时,您的类的这些方法是可见的。

Is it correct to add methods to the metaclass?

这在一定程度上取决于上下文。我会说将方法添加到元类中很好。但是,这些应该很少使用。

As you can see the class is print says its Singleton when I decorate it as classmethod. Otherwise it shows the correct class. I don't understand this behavior, can someone explain?

仔细想想就明白了。 Singleton 是您的 someClass 的类,当您将其设为 classmethod 时,cls 参数将为 单例。但是,已添加到 _instances 的类是 someClass。我可以看到它来自哪里。您的所有方法都采用 cls 参数。这可能让您相信它们“像”类方法(它们在某种程度上是,但不是元类而是实现元类的类!)。

但这只是一个约定,因为 self 是 class 实例的典型参数名称,cls 也是典型的元类 实例的参数名称。当元类上有类方法时,第一个参数可能应该称为 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(除了那些应该/可能是 classmethods 例如 __prepare__)没有多大意义,因为你很少需要类的类型您的实例。