元类的一些(具体的)用例是什么?

我有一个朋友喜欢使用元类,并经常提供它们作为解决方案。

我认为您几乎从来不需要使用元类。为什么?因为我认为如果你对一个类做这样的事情,你可能应该对一个对象做这样的事情。一个小的重新设计/重构是必要的。

能够使用元类已经导致许多地方的许多人将类作为某种二流对象使用,这对我来说似乎是灾难性的。编程是否将被元编程所取代?不幸的是,班级装修工人的加入使它更容易被接受。

因此,我非常想知道 Python 中元类的有效(具体)用例。或者是为了了解为什么有时候变异类比变异对象更好。

我要开始了:

有时候用第三方 能够 以某种方式使类变异。

(这是我能想到的唯一一个案例,而且不是具体的)

35086 次浏览

您永远不能绝对 需要使用元类,因为您总是可以构造一个类,使用您想要修改的类的继承或聚合来完成您想要完成的任务。

也就是说,在 Smalltalk 和 Ruby 中能够修改现有类非常方便,但 Python 不喜欢直接这样做。

Python 中有一个优秀的元分类 DeveloperWorks 文章可能会有所帮助,维基百科文章也很不错。

我唯一一次在 Python 中使用元类是在为 Flickr API 编写包装器时。

我的目标是抓取 Flickr 的 api 网站并动态生成一个完整的类层次结构,以允许使用 Python 对象访问 API:

# Both the photo type and the flickr.photos.search API method
# are generated at "run-time"
for photo in flickr.photos.search(text=balloons):
print photo.description

所以在这个例子中,因为我从网站上生成了整个 Python Flickr API,所以我实际上不知道运行时的类定义。能够动态生成类型非常有用。

元类的目的不是用元类/类来代替类/对象的区别,而是以某种方式改变类定义(以及它们的实例)的行为。实际上,这是为了改变类语句的行为,这种改变对于您的特定域可能比默认域更有用。我用它们做的事情是:

  • 跟踪子类,通常是为了注册处理程序。这在使用插件样式设置时非常方便,您希望通过子类化和设置一些类属性来为特定事物注册处理程序。例如。假设您为各种音乐格式编写了一个处理程序,其中每个类为其类型实现了适当的方法(play/get 标记等)。为新类型添加处理程序将变为:

    class Mp3File(MusicFile):
    extensions = ['.mp3']  # Register this type as a handler for mp3 files
    ...
    # Implementation of mp3 methods go here
    

    元类然后维护 {'.mp3' : MP3File, ... }等的字典,并在通过工厂函数请求处理程序时构造适当类型的对象。

  • 改变行为。您可能希望为某些属性赋予特殊的含义,从而在它们出现时改变行为。例如,您可能希望查找名为 _get_foo_set_foo的方法,并透明地将它们转换为属性。作为一个真实的例子,这是是我为了给出更多类似 C 的结构定义而编写的一个菜谱。元类用于将声明的项转换为结构格式字符串、处理继承等,并生成一个能够处理它的类。

    对于其他实际示例,请查看各种 ORM,如 这是炼金术 ORM 或 Sqlobject。同样,其目的是解释具有特定含义的定义(这里是 SQL 列定义)。

元类不能取代编程!它们只是一个技巧,可以自动化或使一些任务更加优雅。一个很好的例子就是 色素语法突显库。它有一个名为 RegexLexer的类,该类允许用户将一组词法规则定义为类上的正则表达式。元类用于将定义转换为有用的解析器。

它们就像盐一样,很容易用多了。

元类可以方便地用 Python 构造领域特定语言。具体的例子是 Django,这是 SQLObject 对数据库模式的声明性语法。

伊恩 · 比金的 保守的元类中的一个基本例子:

我使用的元类 主要是为了支持 编程的声明式样。用于 例如,考虑一个验证 模式:

class Registration(schema.Schema):
first_name = validators.String(notEmpty=True)
last_name = validators.String(notEmpty=True)
mi = validators.MaxLength(1)
class Numbers(foreach.ForEach):
class Number(schema.Schema):
type = validators.OneOf(['home', 'work'])
phone_number = validators.PhoneNumber()

其他技巧: 用 Python 构建 DSL 的要素(pdf)。

编辑(由 Ali 编辑) : 我更喜欢使用集合和实例进行此操作的示例。重要的事实是实例,它们赋予了您更多的权力,并消除了使用元类的理由。值得进一步注意的是,您的示例使用了类和实例的混合,这肯定表明您不能仅仅使用元类来完成所有工作。并创造了一个真正的非统一的方式做到这一点。

number_validator = [
v.OneOf('type', ['home', 'work']),
v.PhoneNumber('phone_number'),
]


validators = [
v.String('first_name', notEmpty=True),
v.String('last_name', notEmpty=True),
v.MaxLength('mi', 1),
v.ForEach([number_validator,])
]

它并不完美,但是已经几乎没有魔法,不需要元类,并且提高了一致性。

让我们从蒂姆 · 彼得的经典名言开始:

元类比99% 更加神奇 用户应该担心的问题。如果 你想知道你是否需要他们,你 (实际上需要的人) 他们肯定地知道他们 需要他们,不需要一个 蒂姆 · 彼得斯(Tim Peters) (c.l.p post 2002-12-22)

尽管如此,我还是(定期)遇到了元类的真正用法。我想到的是 Django,其中所有的模型都是从模型继承而来的。模特。模特。模型,反过来,做了一些严重的魔术,包装您的数据库模型与 Django 的 ORM 良好。这种奇迹是通过元类发生的。它创建各种各样的异常类、管理器类等等。

请参见 django/db/model/base.py 类 ModelBase ()作为本文的开头。

我使用元类的方法是为类提供一些属性:

class NameClass(type):
def __init__(cls, *args, **kwargs):
type.__init__(cls, *args, **kwargs)
cls.name = cls.__name__

将把 姓名属性放在将元类设置为指向 NameClass 的每个类上。

我有一门课是讲非交互式绘图的作为 Matplotlib 的前沿课程。然而,有时人们想做交互式绘图。只有几个函数,我发现我能够增加数字计数,调用手动绘制等,但我需要做这些之前和之后,每绘图调用。因此,为了创建一个交互式绘图包装器和一个屏幕外绘图包装器,我发现通过元类包装适当的方法来实现这一点要比执行以下操作更有效率:

class PlottingInteractive:
add_slice = wrap_pylab_newplot(add_slice)

这个方法不能跟上 API 的变化等等,但是在重新设置 class 属性之前迭代 __init__中的 class 属性的方法更有效率,并且可以保持最新:

class _Interactify(type):
def __init__(cls, name, bases, d):
super(_Interactify, cls).__init__(name, bases, d)
for base in bases:
for attrname in dir(base):
if attrname in d: continue # If overridden, don't reset
attr = getattr(cls, attrname)
if type(attr) == types.MethodType:
if attrname.startswith("add_"):
setattr(cls, attrname, wrap_pylab_newplot(attr))
elif attrname.startswith("set_"):
setattr(cls, attrname, wrap_pylab_show(attr))

当然,也许有更好的方法来做到这一点,但我发现这是有效的。当然,这也可以在 __new____init__中完成,但这是我发现的最直接的解决方案。

我昨天也是这么想的,完全同意。在我看来,使代码更具有声明性的尝试通常会使代码库更难维护、更难阅读并且不那么 Python 化,从而导致代码中的复杂性。 它通常还需要大量的 copy.copy () ing (来维护继承和从一个类到另一个实例的复制) ,这意味着您必须在许多地方查看发生了什么(总是从元类向上查看) ,这也违背了 Python 的原则。 我一直在挑选 formencode 和 sql 炼金术代码,看看这种声明式风格是否值得,显然不值得。这样的样式应该留给描述符(如属性和方法)和不可变数据。 Ruby 对这种声明式样式有更好的支持,我很高兴核心 python 语言没有沿着这条路走下去。

我可以看到他们的使用调试,添加一个元类到所有的基类,以获得更丰富的信息。 我还发现它们只在(非常)大的项目中使用,以去除一些样板代码(但不清晰)。对于 例子来说,sql 炼金术确实在其他地方使用它们,根据类定义中的属性值向所有子类添加特定的自定义方法 例如玩具

class test(baseclass_with_metaclass):
method_maker_value = "hello"

可以有一个元类,该元类在该类中生成一个具有基于“ hello”的特殊属性的方法(比如在字符串末尾添加“ hello”的方法)。确保不必在每个子类中编写方法,而只需定义 method _ maker _ value,这对可维护性是有好处的。

但是这种需求非常罕见,而且只减少了一点点输入,因此除非您有足够大的代码库,否则不值得考虑。

元类的唯一合法用例是防止其他爱管闲事的开发人员接触您的代码。一旦一个爱管闲事的开发人员掌握了元类并开始使用您的元类,那么再加入一两个级别来阻止它们。如果这不起作用,那么开始使用 type.__new__或者使用递归元类的一些方案。

但是我见过这种混淆,姜戈就是一个很好的例子

合理的元类使用模式是在定义一个类时执行一次操作,而不是在实例化同一个类时重复执行。

当多个类共享相同的特殊行为时,重复 __metaclass__=X显然比重复特殊用途代码和/或引入 ad-hoc 共享超类要好。

但是,即使只有一个特殊的类,而且没有可预见的扩展,元类的 __new____init__比在类定义主体中混合使用专用代码和普通的 defclass语句来初始化类变量或其他全局数据更简单。

这是一个次要的用途,但是... ... 我发现元类的一个有用之处是在创建子类时调用函数。我将其编码为一个元类,用于查找 __initsubclass__属性: 每当创建一个子类时,定义该方法的所有父类都会用 __initsubclass__(cls, subcls)调用。这允许创建一个父类,然后将所有子类注册到某个全局注册中心,在定义子类时对它们进行不变检查,执行后期绑定操作,等等。.所有这些都不必手动调用函数 或者来创建自定义元类来执行这些单独的任务。

提醒你一下,我已经慢慢意识到这种行为的隐含神奇性有些不受欢迎,因为如果脱离上下文来看一个类定义是出乎意料的... ... 所以我已经放弃了使用这种解决方案,除了为每个类和实例初始化一个 __super属性之外。

当多个线程试图与某些 GUI 库交互时,它们会遇到麻烦。tkinter就是这样一个例子; 虽然可以显式地处理事件和队列的问题,但是以一种完全忽略问题的方式使用这个库要简单得多。看,元类的魔力。

在某些情况下,能够动态地无缝地重写整个库,使其在多线程应用程序中按预期正常工作非常有帮助。Safetkinter模块在 线盒模块提供的元类的帮助下完成这项工作——不需要事件和队列。

threadbox的一个简洁的方面是它不在乎克隆什么类。它提供了一个示例,说明如果需要,元类可以触及所有基类。元类的另一个好处是,它们也在继承类上运行。编写自己的程序——为什么不呢?

最近,我不得不使用一个元类来帮助以声明的方式围绕一个数据库表定义 SQLAlchemy 模型,该数据库表中填充了来自 http://census.ire.org/data/bulkdata.html的美国人口普查数据

IRE 为人口普查数据表提供 数据库外壳程序,这些数据表会按人口普查局编制的 p012015、 p012016、 p012017等变数命名原则编制整数栏。

我希望 a)能够使用 model_instance.p012017语法访问这些列,b)能够相当明确地说明我在做什么,c)不必在模型上显式地定义几十个字段,所以我对 SQLAlchemy 的 DeclarativeMeta进行子类化,以迭代遍历一系列列,并自动创建与这些列对应的模型字段:

from sqlalchemy.ext.declarative.api import DeclarativeMeta


class CensusTableMeta(DeclarativeMeta):
def __init__(cls, classname, bases, dict_):
table = 'p012'
for i in range(1, 49):
fname = "%s%03d" % (table, i)
dict_[fname] = Column(Integer)
setattr(cls, fname, dict_[fname])


super(CensusTableMeta, cls).__init__(classname, bases, dict_)

然后,我可以将这个元类用于我的模型定义,并访问模型上的自动枚举字段:

CensusTableBase = declarative_base(metaclass=CensusTableMeta)


class P12Tract(CensusTableBase):
__tablename__ = 'ire_p12'


geoid = Column(String(12), primary_key=True)


@property
def male_under_5(self):
return self.p012003


...

似乎有一个合法的用途描述了 给你-用元类重写 Python Docstring。

最近有人问我同样的问题,我想出了几个答案。我希望可以重新启动这个线程,因为我想详细介绍一些提到的用例,并添加一些新的用例。

我见过的大多数元类只做两件事:

  1. 注册(向数据结构添加类) :

    models = {}
    
    
    class ModelMetaclass(type):
    def __new__(meta, name, bases, attrs):
    models[name] = cls = type.__new__(meta, name, bases, attrs)
    return cls
    
    
    class Model(object):
    __metaclass__ = ModelMetaclass
    

    每当你子类 Model时,你的类就会在 models字典中注册:

    >>> class A(Model):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...>,
    'B': <__main__.B class at 0x...>}
    

    这也可以通过类装饰器来实现:

    models = {}
    
    
    def model(cls):
    models[cls.__name__] = cls
    return cls
    
    
    @model
    class A(object):
    pass
    

    或具有明确的注册功能:

    models = {}
    
    
    def register_model(cls):
    models[cls.__name__] = cls
    
    
    class A(object):
    pass
    
    
    register_model(A)
    

    实际上,这几乎是一样的: 您不喜欢提到类装饰器,但是对于类的函数调用来说,它实际上只不过是一种语法糖,因此没有什么神奇之处。

    无论如何,在这种情况下,元类的优势在于继承,因为它们适用于任何子类,而其他解决方案只适用于显式修饰或注册的子类。

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
    
  2. Refactoring (modifying class attributes or adding new ones):

    class ModelMetaclass(type):
    def __new__(meta, name, bases, attrs):
    fields = {}
    for key, value in attrs.items():
    if isinstance(value, Field):
    value.name = '%s.%s' % (name, key)
    fields[key] = value
    for base in bases:
    if hasattr(base, '_fields'):
    fields.update(base._fields)
    attrs['_fields'] = fields
    return type.__new__(meta, name, bases, attrs)
    
    
    class Model(object):
    __metaclass__ = ModelMetaclass
    

    每当你定义一些 Field属性,它们都会被注入它们的名字(例如,为了获得更多信息的错误消息) ,然后分组成一个 _fields字典(为了方便迭代,不必每次都查看所有的类属性和它的基类属性) :

    >>> class A(Model):
    ...     foo = Integer()
    ...
    >>> class B(A):
    ...     bar = String()
    ...
    >>> B._fields
    {'foo': Integer('A.foo'), 'bar': String('B.bar')}
    

    同样,这可以通过一个类装饰器来完成(不需要继承) :

    def model(cls):
    fields = {}
    for key, value in vars(cls).items():
    if isinstance(value, Field):
    value.name = '%s.%s' % (cls.__name__, key)
    fields[key] = value
    for base in cls.__bases__:
    if hasattr(base, '_fields'):
    fields.update(base._fields)
    cls._fields = fields
    return cls
    
    
    @model
    class A(object):
    foo = Integer()
    
    
    class B(A):
    bar = String()
    
    
    # B.bar has no name :(
    # B._fields is {'foo': Integer('A.foo')} :(
    

    或者明确地说:

    class A(object):
    foo = Integer('A.foo')
    _fields = {'foo': foo} # Don't forget all the base classes' fields, too!
    

    虽然,与你倡导的可读性和可维护的非元编程相反,这种方法更加麻烦、冗余和容易出错:

    class B(A):
    bar = String()
    
    
    # vs.
    
    
    class B(A):
    bar = String('bar')
    _fields = {'B.bar': bar, 'A.foo': A.foo}
    

Having considered the most common and concrete use cases, the only cases where you absolutely HAVE to use metaclasses are when you want to modify the class name or list of base classes, because once defined, these parameters are baked into the class, and no decorator or function can unbake them.

class Metaclass(type):
def __new__(meta, name, bases, attrs):
return type.__new__(meta, 'foo', (int,), attrs)


class Baseclass(object):
__metaclass__ = Metaclass


class A(Baseclass):
pass


class B(A):
pass


print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

这在定义名称相似或继承树不完整的类时发出警告的框架中可能很有用,但除了实际更改这些值之外,我想不出其他理由。也许大卫 · 比兹利可以。

无论如何,在 Python 3中,元类也有 __prepare__方法,它允许您将类主体计算成 dict以外的映射,从而支持有序属性、重载属性和其他非常酷的东西:

import collections


class Metaclass(type):


@classmethod
def __prepare__(meta, name, bases, **kwds):
return collections.OrderedDict()


def __new__(meta, name, bases, attrs, **kwds):
print(list(attrs))
# Do more stuff...


class A(metaclass=Metaclass):
x = 1
y = 2


# prints ['x', 'y'] rather than ['y', 'x']

class ListDict(dict):
def __setitem__(self, key, value):
self.setdefault(key, []).append(value)


class Metaclass(type):


@classmethod
def __prepare__(meta, name, bases, **kwds):
return ListDict()


def __new__(meta, name, bases, attrs, **kwds):
print(attrs['foo'])
# Do more stuff...


class A(metaclass=Metaclass):


def foo(self):
pass


def foo(self, x):
pass


# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>

您可能会争辩说,有序属性可以通过创建计数器来实现,重载可以通过默认参数来模拟:

import itertools


class Attribute(object):
_counter = itertools.count()
def __init__(self):
self._count = Attribute._counter.next()


class A(object):
x = Attribute()
y = Attribute()


A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
key = lambda (k, v): v._count)

class A(object):


def _foo0(self):
pass


def _foo1(self, x):
pass


def foo(self, x=None):
if x is None:
return self._foo0()
else:
return self._foo1(x)

除了更难看之外,它的灵活性也更差: 如果您想要有序的文字属性,比如整数和字符串,该怎么办?如果 Nonex的有效值该怎么办?

这里有一个创造性的方法来解决第一个问题:

import sys


class Builder(object):
def __call__(self, cls):
cls._order = self.frame.f_code.co_names
return cls


def ordered():
builder = Builder()
def trace(frame, event, arg):
builder.frame = frame
sys.settrace(None)
sys.settrace(trace)
return builder


@ordered()
class A(object):
x = 1
y = 'foo'


print A._order # ['x', 'y']

这里有一个创造性的方法来解决第二个问题:

_undefined = object()


class A(object):


def _foo0(self):
pass


def _foo1(self, x):
pass


def foo(self, x=_undefined):
if x is _undefined:
return self._foo0()
else:
return self._foo1(x)

但是,这比一个简单的元类(尤其是第一个元类,它真的会融化你的大脑)更具有巫术色彩。我的观点是,你把元类看作是不熟悉的和违反直觉的,但是你也可以把它们看作是编程语言进化的下一步: 你只需要调整你的心态。毕竟,您可以在 C 中完成所有工作,包括用函数指针定义结构并将其作为函数的第一个参数传递。一个第一次看到 C + + 的人可能会说,“这是什么魔法?为什么编译器隐式地将 this传递给方法,而不是传递给常规函数和静态函数?对于你的论点,最好是明确而详尽的。”。但是,一旦你掌握了面向对象程序设计,它就会变得更加强大,我想,这种准面向方面的编程也是如此。一旦你理解了元类,它们实际上非常简单,那么为什么不在方便的时候使用它们呢?

最后,元类很酷,编程应该很有趣。一直使用标准编程构造和设计模式是枯燥乏味的,会阻碍您的想象力。享受生活吧!这里有一个元类,专门为你准备的。

class MetaMetaclass(type):
def __new__(meta, name, bases, attrs):
def __new__(meta, name, bases, attrs):
cls = type.__new__(meta, name, bases, attrs)
cls._label = 'Made in %s' % meta.__name__
return cls
attrs['__new__'] = __new__
return type.__new__(meta, name, bases, attrs)


class China(type):
__metaclass__ = MetaMetaclass


class Taiwan(type):
__metaclass__ = MetaMetaclass


class A(object):
__metaclass__ = China


class B(object):
__metaclass__ = Taiwan


print A._label # Made in China
print B._label # Made in Taiwan

剪辑

这是一个相当老的问题,但它仍然得到赞同,所以我想我应该添加一个链接到一个更全面的答案。如果您想了解更多关于元类及其用法的信息,我刚刚发表了一篇关于它的文章 给你

我曾经在二进制解析器中使用过它们,以使其更易于使用。您定义一个消息类,其中包含连接上存在的字段的属性。 它们需要按照声明的方式进行排序,以便从中构建最终的有线格式。如果使用有序的命名空间 dict,可以使用元类来实现这一点。事实上,它在元类的例子中:

Https://docs.python.org/3/reference/datamodel.html#metaclass-example

但是一般来说: 如果您真的需要增加元类的复杂性,请非常仔细地进行评估。

来自“ Dan Gittik”的回答很酷

最后的例子可以澄清很多事情,我把它改为 python 3并给出一些解释:

class MetaMetaclass(type):
def __new__(meta, name, bases, attrs):
def __new__(meta, name, bases, attrs):
cls = type.__new__(meta, name, bases, attrs)
cls._label = 'Made in %s' % meta.__name__
return cls


attrs['__new__'] = __new__
return type.__new__(meta, name, bases, attrs)


#China is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class China(MetaMetaclass, metaclass=MetaMetaclass):
__metaclass__ = MetaMetaclass


#Taiwan is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class Taiwan(MetaMetaclass, metaclass=MetaMetaclass):
__metaclass__ = MetaMetaclass


#A is a normal class and it's __new__ method would be changed by China(metaclass)
class A(metaclass=China):
__metaclass__ = China


#B is a normal class and it's __new__ method would be changed by Taiwan(metaclass)
class B(metaclass=Taiwan):
__metaclass__ = Taiwan




print(A._label)  # Made in China
print(B._label)  # Made in Taiwan


  • 一切都是对象,所以类就是对象
  • 类对象是由元类创建的
  • 从 type 继承的所有类都是元类
  • 元类可以控制类的创建
  • 元类也可以控制元类的创建(因此它可以永远循环)
  • 这是元编程... 你可以在运行时控制类型系统
  • 同样,一切都是对象,这是一个统一的系统,键入 create type,键入 create instance

另一个用例是当您希望能够修改类级属性并确保它只影响手边的对象时。在实践中,这意味着要“合并”元类和类实例化的各个阶段,从而导致您只处理它们自己的(唯一的)类实例。

当(为了关注 可读性多态性)我们希望 动态定义 property返回值(可能)来自基于(经常变化的)实例级属性的计算,其中 只能在类级别上完成也就是说。在元类实例化之后和类实例化之前。

Pydantic 是一个用于数据验证和设置管理的库,它在运行时强制执行类型提示,并在数据无效时提供用户友好的错误。它使用元类进行 BaseModel 和数值范围验证。

在工作中,我遇到了一些代码,它们有一个由类定义的几个阶段的流程。这些步骤的顺序由元类控制,元类在定义类时将这些步骤添加到列表中。这将被抛出,并通过将它们添加到列表中来设置顺序。

我知道这是一个老问题了,但是这里有一个用例,如果只想根据传递给构造函数的参数创建一个类的单个实例,那么这个用例是非常有价值的。

实例单例 我使用这段代码在 Z-Wave 网络上创建一个设备的单例实例。无论我创建多少个实例,如果相同的值被传递给构造函数,如果一个具有完全相同值的实例存在,那么这就是返回的结果。

import inspect




class SingletonMeta(type):
# only here to make IDE happy
_instances = {}


def __init__(cls, name, bases, dct):
super(SingletonMeta, cls).__init__(name, bases, dct)
cls._instances = {}


def __call__(cls, *args, **kwargs):
sig = inspect.signature(cls.__init__)
keywords = {}


for i, param in enumerate(list(sig.parameters.values())[1:]):
if len(args) > i:
keywords[param.name] = args[i]
elif param.name not in kwargs and param.default != param.empty:
keywords[param.name] = param.default
elif param.name in kwargs:
keywords[param.name] = kwargs[param.name]
key = []
for k in sorted(list(keywords.keys())):
key.append(keywords[k])
key = tuple(key)


if key not in cls._instances:
cls._instances[key] = (
super(SingletonMeta, cls).__call__(*args, **kwargs)
)


return cls._instances[key]




class Test1(metaclass=SingletonMeta):


def __init__(self, param1, param2='test'):
pass




class Test2(metaclass=SingletonMeta):


def __init__(self, param3='test1', param4='test2'):
pass




test1 = Test1('test1')
test2 = Test1('test1', 'test2')
test3 = Test1('test1', 'test')


test4 = Test2()
test5 = Test2(param4='test1')
test6 = Test2('test2', 'test1')
test7 = Test2('test1')


print('test1 == test2:', test1 == test2)
print('test2 == test3:', test2 == test3)
print('test1 == test3:', test1 == test3)
print('test4 == test2:', test4 == test2)
print('test7 == test3:', test7 == test3)
print('test6 == test4:', test6 == test4)
print('test7 == test4:', test7 == test4)
print('test5 == test6:', test5 == test6)


print('number of Test1 instances:', len(Test1._instances))
print('number of Test2 instances:', len(Test2._instances))

输出

test1 == test2: False
test2 == test3: False
test1 == test3: True
test4 == test2: False
test7 == test3: False
test6 == test4: False
test7 == test4: True
test5 == test6: False
number of Test1 instances: 2
number of Test2 instances: 3

现在有人可能会说不用元类就可以完成,我知道如果修饰了 _ _ init _ _ 方法就可以完成。我不知道还有别的办法。下面的代码将返回一个包含所有相同数据的类似实例,它不是一个单例实例,而是创建一个新实例。因为它使用相同的数据创建了一个新的实例,所以需要采取额外的步骤来检查实例的相等性。最后,它比使用元类消耗更多的内存,并且使用元类不需要采取额外的步骤来检查相等性。

class Singleton(object):
_instances = {}


def __init__(self, param1, param2='test'):
key = (param1, param2)
if key in self._instances:
self.__dict__.update(self._instances[key].__dict__)
else:
self.param1 = param1
self.param2 = param2
self._instances[key] = self




test1 = Singleton('test1', 'test2')
test2 = Singleton('test')
test3 = Singleton('test', 'test')


print('test1 == test2:', test1 == test2)
print('test2 == test3:', test2 == test3)
print('test1 == test3:', test1 == test3)


print('test1 params', test1.param1, test1.param2)
print('test2 params', test2.param1, test2.param2)
print('test3 params', test3.param1, test3.param2)


print('number of Singleton instances:', len(Singleton._instances))

输出

test1 == test2: False
test2 == test3: False
test1 == test3: False
test1 params test1 test2
test2 params test test
test3 params test test
number of Singleton instances: 2

如果需要检查删除或添加新实例,元类方法非常适合使用。

    import inspect




class SingletonMeta(type):
# only here to make IDE happy
_instances = {}


def __init__(cls, name, bases, dct):
super(SingletonMeta, cls).__init__(name, bases, dct)
cls._instances = {}


def __call__(cls, *args, **kwargs):
sig = inspect.signature(cls.__init__)
keywords = {}


for i, param in enumerate(list(sig.parameters.values())[1:]):
if len(args) > i:
keywords[param.name] = args[i]
elif param.name not in kwargs and param.default != param.empty:
keywords[param.name] = param.default
elif param.name in kwargs:
keywords[param.name] = kwargs[param.name]
key = []
for k in sorted(list(keywords.keys())):
key.append(keywords[k])
key = tuple(key)


if key not in cls._instances:
cls._instances[key] = (
super(SingletonMeta, cls).__call__(*args, **kwargs)
)


return cls._instances[key]




class Test(metaclass=SingletonMeta):


def __init__(self, param1, param2='test'):
pass




instances = []


instances.append(Test('test1', 'test2'))
instances.append(Test('test1', 'test'))


print('number of instances:', len(instances))


instance = Test('test2', 'test3')
if instance not in instances:
instances.append(instance)


instance = Test('test1', 'test2')
if instance not in instances:
instances.append(instance)


print('number of instances:', len(instances))

输出

number of instances: 2
number of instances: 3

下面是一种在实例不再使用后删除已创建的实例的方法。

    import inspect
import weakref




class SingletonMeta(type):
# only here to make IDE happy
_instances = {}


def __init__(cls, name, bases, dct):
super(SingletonMeta, cls).__init__(name, bases, dct)


def remove_instance(c, ref):
for k, v in list(c._instances.items())[:]:
if v == ref:
del cls._instances[k]
break
                    

cls.remove_instance = classmethod(remove_instance)
cls._instances = {}


def __call__(cls, *args, **kwargs):
sig = inspect.signature(cls.__init__)
keywords = {}


for i, param in enumerate(list(sig.parameters.values())[1:]):
if len(args) > i:
keywords[param.name] = args[i]
elif param.name not in kwargs and param.default != param.empty:
keywords[param.name] = param.default
elif param.name in kwargs:
keywords[param.name] = kwargs[param.name]
key = []
for k in sorted(list(keywords.keys())):
key.append(keywords[k])
key = tuple(key)


if key not in cls._instances:
instance = super(SingletonMeta, cls).__call__(*args, **kwargs)


cls._instances[key] = weakref.ref(
instance,
instance.remove_instance
)


return cls._instances[key]()




class Test1(metaclass=SingletonMeta):


def __init__(self, param1, param2='test'):
pass




class Test2(metaclass=SingletonMeta):


def __init__(self, param3='test1', param4='test2'):
pass




test1 = Test1('test1')
test2 = Test1('test1', 'test2')
test3 = Test1('test1', 'test')


test4 = Test2()
test5 = Test2(param4='test1')
test6 = Test2('test2', 'test1')
test7 = Test2('test1')


print('test1 == test2:', test1 == test2)
print('test2 == test3:', test2 == test3)
print('test1 == test3:', test1 == test3)
print('test4 == test2:', test4 == test2)
print('test7 == test3:', test7 == test3)
print('test6 == test4:', test6 == test4)
print('test7 == test4:', test7 == test4)
print('test5 == test6:', test5 == test6)


print('number of Test1 instances:', len(Test1._instances))
print('number of Test2 instances:', len(Test2._instances))




print()
del test1
del test5
del test6


print('number of Test1 instances:', len(Test1._instances))
print('number of Test2 instances:', len(Test2._instances))

输出

test1 == test2: False
test2 == test3: False
test1 == test3: True
test4 == test2: False
test7 == test3: False
test6 == test4: False
test7 == test4: True
test5 == test6: False
number of Test1 instances: 2
number of Test2 instances: 3


number of Test1 instances: 2
number of Test2 instances: 1

如果您查看输出,您将注意到 Test1实例的数量没有改变。这是因为 test1和 test3是相同的实例,我只删除了 test1,所以代码中仍然有对 test1实例的引用,结果是 test1实例没有被删除。

另一个很好的特性是,如果实例只使用提供的参数来完成任务,那么您可以使用元类来促进在完全不同的计算机上或在同一台计算机上的不同进程中远程创建实例。参数可以简单地通过套接字或命名管道传递,并且可以在接收端创建类的副本。