元类有哪些(具体)用例?

发布于 2024-07-10 10:44:37 字数 403 浏览 7 评论 0 原文

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

我认为你几乎永远不需要使用元类。 为什么? 因为我认为如果你对一个类做类似的事情,你可能应该对一个对象做同样的事情。 一个小的重新设计/重构是有序的。

能够使用元类导致很多地方的很多人将类用作某种二流对象,这对我来说似乎是灾难性的。 编程会被元编程取代吗? 不幸的是,类装饰器的添加使其更容易被接受。

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

我将开始:

有时使用第三方时 图书馆很有用,能够 以某种方式改变类。

(这是我能想到的唯一情况,也不具体)

I have a friend who likes to use metaclasses, and regularly offers them as a solution.

I am of the mind that you almost never need to use metaclasses. Why? because I figure if you are doing something like that to a class, you should probably be doing it to an object. And a small redesign/refactor is in order.

Being able to use metaclasses has caused a lot of people in a lot of places to use classes as some kind of second rate object, which just seems disastrous to me. Is programming to be replaced by meta-programming? The addition of class decorators has unfortunately made it even more acceptable.

So please, I am desperate to know your valid (concrete) use-cases for metaclasses in Python. Or to be enlightened as to why mutating classes is better than mutating objects, sometimes.

I will start:

Sometimes when using a third-party
library it is useful to be able to
mutate the class in a certain way.

(This is the only case I can think of, and it's not concrete)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(21

秋意浓 2024-07-17 10:44:37

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

我见过的大多数元类都会执行以下两件事之一:

  1. 注册(将类添加到数据结构中):

    模型 = {} 
    
      类模型元类(类型): 
          def __new__(元、名称、基础、属性): 
              模型 [名称] = cls = 类型.__new__(元、名称、基础、属性) 
              返回CLS 
    
      类模型(对象): 
          __metaclass__ = 模型元类 
      

    每当您子类化 Model 时,您的类都会在 models 字典中注册:

    <前><代码>>>> A类(型号):
    ... 经过
    ...
    >>>>> B(A)类:
    ... 经过
    ...
    >>>>> 楷模
    {'A': <__main__.0x 处的类...>,
    'B': <__main__.B 类位于 0x...>}

    这也可以通过类装饰器来完成:

    模型 = {} 
    
      定义模型(cls): 
          模型[cls.__name__] = cls 
          返回CLS 
    
      @模型 
      A类(对象): 
          经过 
      

    或者使用显式注册函数:

    模型 = {} 
    
      def register_model(cls​​): 
          模型[cls.__name__] = cls 
    
      A类(对象): 
          经过 
    
      注册模型(A) 
      

    实际上,这几乎是一样的:你提到了类装饰器,但它实际上只不过是类上函数调用的语法糖,所以没有什么魔力。

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

    <前><代码>>>> B(A)类:
    ... 经过
    ...
    >>>>> 楷模
    {'A': <__main__.0x 处的类...> # 没有 B :(

  2. 重构(修改类属性或添加新属性):

    类 ModelMetaclass(类型): 
          def __new__(元、名称、基础、属性): 
              字段 = {} 
              对于 attrs.items() 中的键、值: 
                  if isinstance(值, 字段): 
                      value.name = '%s.%s' % (名称, 键) 
                      字段[键] = 值 
              对于碱基中的碱基: 
                  如果 hasattr(base, '_fields'): 
                      fields.update(base._fields) 
              attrs['_fields'] = 字段 
              返回类型.__new__(元、名称、基础、属性) 
    
      类模型(对象): 
          __metaclass__ = 模型元类 
      

    每当您子类化 Model 并定义一些 Field 属性时,它们都会被注入其名称(例如,用于提供更多信息的错误消息),并分组到 >_fields 字典(为了方便迭代,不必每次都查看所有类属性及其所有基类属性):

    <前><代码>>>> A类(型号):
    ... foo = 整数()
    ...
    >>>>> B(A)类:
    ... 酒吧 = 字符串()
    ...
    >>>>> B._fields
    {'foo': 整数('A.foo'), 'bar': 字符串('B.bar')}

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

    def 模型(cls): 
          字段={} 
          对于 vars(cls).items() 中的键、值: 
              if isinstance(值, 字段): 
                  value.name = '%s.%s' % (cls.__name__, key) 
                  字段[键] = 值 
          对于 cls.__bases__ 中的基数: 
              如果 hasattr(base, '_fields'): 
                  fields.update(base._fields) 
          cls._fields = 字段 
          返回CLS 
    
      @模型 
      A类(对象): 
          foo = 整数() 
    
      B(A)类: 
          条=字符串() 
    
      # B.bar 没有名字:( 
      # B._fields 是 {'foo': Integer('A.foo')} :( 
      

    或者明确地:

    A类(对象): 
          foo = Integer('A.foo') 
          _fields = {'foo': foo} # 也不要忘记所有基类的字段! 
      

    尽管与您提倡的可读和可维护的非元编程相反,这更加麻烦、冗余且容易出错:

    B 类(A): 
          条=字符串() 
    
      # 对比 
    
      B(A)类: 
          栏 = 字符串('栏') 
          _fields = {'B.bar': 酒吧, 'A.foo': A.foo} 
      

考虑了最常见和最 常见的具体用例中,您绝对必须使用元类的唯一情况是当您想要修改类名称或基类列表时,因为一旦定义,这些参数就会被烘焙到类中,并且没有装饰器或函数可以取消烘焙它们。

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__方法,它可以让你将类体计算为字典以外的映射,从而支持有序属性、重载属性、和其他很酷的东西

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

编辑

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

I was asked the same question recently, and came up with several answers. I hope it's OK to revive this thread, as I wanted to elaborate on a few of the use cases mentioned, and add a few new ones.

Most metaclasses I've seen do one of two things:

  1. Registration (adding a class to a data structure):

    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
    

    Whenever you subclass Model, your class is registered in the models dictionary:

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

    This can also be done with class decorators:

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

    Or with an explicit registration function:

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

    Actually, this is pretty much the same: you mention class decorators unfavorably, but it's really nothing more than syntactic sugar for a function invocation on a class, so there's no magic about it.

    Anyway, the advantage of metaclasses in this case is inheritance, as they work for any subclasses, whereas the other solutions only work for subclasses explicitly decorated or registered.

    >>> 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
    

    Whenever you subclass Model and define some Field attributes, they are injected with their names (for more informative error messages, for example), and grouped into a _fields dictionary (for easy iteration, without having to look through all the class attributes and all its base classes' attributes every time):

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

    Again, this can be done (without inheritance) with a class decorator:

    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')} :(
    

    Or explicitly:

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

    Although, on the contrary to your advocacy for readable and maintainable non-meta programming, this is much more cumbersome, redundant and error prone:

    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

This may be useful in frameworks for issuing warnings whenever classes with similar names or incomplete inheritance trees are defined, but I can't think of a reason beside trolling to actually change these values. Maybe David Beazley can.

Anyway, in Python 3, metaclasses also have the __prepare__ method, which lets you evaluate the class body into a mapping other than a dict, thus supporting ordered attributes, overloaded attributes, and other wicked cool stuff:

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...>

You might argue ordered attributes can be achieved with creation counters, and overloading can be simulated with default arguments:

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)

Besides being much more ugly, it's also less flexible: what if you want ordered literal attributes, like integers and strings? What if None is a valid value for x?

Here's a creative way to solve the first problem:

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']

And here's a creative way to solve the second one:

_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)

But this is much, MUCH voodoo-er than a simple metaclass (especially the first one, which really melts your brain). My point is, you look at metaclasses as unfamiliar and counter-intuitive, but you can also look at them as the next step of evolution in programming languages: you just have to adjust your mindset. After all, you could probably do everything in C, including defining a struct with function pointers and passing it as the first argument to its functions. A person seeing C++ for the first time might say, "what is this magic? Why is the compiler implicitly passing this to methods, but not to regular and static functions? It's better to be explicit and verbose about your arguments". But then, object-oriented programming is much more powerful once you get it; and so is this, uh... quasi-aspect-oriented programming, I guess. And once you understand metaclasses, they're actually very simple, so why not use them when convenient?

And finally, metaclasses are rad, and programming should be fun. Using standard programming constructs and design patterns all the time is boring and uninspiring, and hinders your imagination. Live a little! Here's a metametaclass, just for you.

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

Edit

This is a pretty old question, but it's still getting upvotes, so I thought I'd add a link to a more comprehensive answer. If you'd like to read more about metaclasses and their uses, I've just published an article about it here.

聚集的泪 2024-07-17 10:44:37

元类的目的不是用元类/类来取代类/对象的区别——而是以某种方式改变类定义(以及它们的实例)的行为。 实际上,它是以比默认方式对您的特定域更有用的方式更改类语句的行为。 我使用它们的用途是:

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

    类 Mp3File(音乐文件): 
          extensions = ['.mp3'] # 将此类型注册为 mp3 文件的处理程序 
          ... 
          # mp3方法的实现在这里 
      

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

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

    对于其他现实世界的示例,请查看各种 ORM,例如 sqlalchemy 的 ORM 或 sqlobject。 同样,目的是用特定含义解释定义(此处为 SQL 列定义)。

The purpose of metaclasses isn't to replace the class/object distinction with metaclass/class - it's to change the behaviour of class definitions (and thus their instances) in some way. Effectively it's to alter the behaviour of the class statement in ways that may be more useful for your particular domain than the default. The things I have used them for are:

  • Tracking subclasses, usually to register handlers. This is handy when using a plugin style setup, where you wish to register a handler for a particular thing simply by subclassing and setting up a few class attributes. eg. suppose you write a handler for various music formats, where each class implements appropriate methods (play / get tags etc) for its type. Adding a handler for a new type becomes:

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

    The metaclass then maintains a dictionary of {'.mp3' : MP3File, ... } etc, and constructs an object of the appropriate type when you request a handler through a factory function.

  • Changing behaviour. You may want to attach a special meaning to certain attributes, resulting in altered behaviour when they are present. For example, you may want to look for methods with the name _get_foo and _set_foo and transparently convert them to properties. As a real-world example, here's a recipe I wrote to give more C-like struct definitions. The metaclass is used to convert the declared items into a struct format string, handling inheritance etc, and produce a class capable of dealing with it.

    For other real-world examples, take a look at various ORMs, like sqlalchemy's ORM or sqlobject. Again, the purpose is to interpret defintions (here SQL column definitions) with a particular meaning.

弃爱 2024-07-17 10:44:37

我有一个处理非交互式绘图的类,作为 Matplotlib 的前端。 然而,有时人们想要进行交互式绘图。 我发现仅使用几个函数就可以增加图形计数、手动调用绘图等,但我需要在每次绘图调用之前和之后执行这些操作。 因此,要创建交互式绘图包装器和离屏绘图包装器,我发现通过元类、包装适当的方法来完成此操作比执行以下操作更有效:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

此方法不跟上 API 更改等,但是在重新设置类属性之前迭代 __init__ 中的类属性会更有效并保持最新状态:

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__ 中完成,但这是我发现最简单的解决方案。

I have a class that handles non-interactive plotting, as a frontend to Matplotlib. However, on occasion one wants to do interactive plotting. With only a couple functions I found that I was able to increment the figure count, call draw manually, etc, but I needed to do these before and after every plotting call. So to create both an interactive plotting wrapper and an offscreen plotting wrapper, I found it was more efficient to do this via metaclasses, wrapping the appropriate methods, than to do something like:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

This method doesn't keep up with API changes and so on, but one that iterates over the class attributes in __init__ before re-setting the class attributes is more efficient and keeps things up to date:

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))

Of course, there might be better ways to do this, but I've found this to be effective. Of course, this could also be done in __new__ or __init__, but this was the solution I found the most straightforward.

清秋悲枫 2024-07-17 10:44:37

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

元类比 99% 更有魔力
用户应该担心的问题。 如果
你想知道你是否需要它们,你
不(真正需要的人
他们确信他们
需要它们,并且不需要
解释原因)。 蒂姆·彼得斯
(clp 2002-12-22 发表)

话虽如此,我(定期)遇到了元类的真正用法。 我想到的是在 Django 中,所有模型都继承自 models.Model。 models.Model 反过来又发挥了一些重要作用,用 Django 的 ORM 优点来包装您的数据库模型。 这种魔力是通过元类发生的。 它创建各种类型的异常类、管理器类等。

请参阅 django/db/models/base.py、类 ModelBase() 了解故事的开头。

Let's start with Tim Peter's classic quote:

Metaclasses are deeper magic than 99%
of users should ever worry about. If
you wonder whether you need them, you
don't (the people who actually need
them know with certainty that they
need them, and don't need an
explanation about why). Tim Peters
(c.l.p post 2002-12-22)

Having said that, I have (periodically) run across true uses of metaclasses. The one that comes to mind is in Django where all of your models inherit from models.Model. models.Model, in turn, does some serious magic to wrap your DB models with Django's ORM goodness. That magic happens by way of metaclasses. It creates all manner of exception classes, manager classes, etc. etc.

See django/db/models/base.py, class ModelBase() for the beginning of the story.

谜兔 2024-07-17 10:44:37

元类的唯一合法用例是防止其他爱管闲事的开发人员接触您的代码。 一旦爱管闲事的开发人员掌握了元类并开始研究你的元类,就再添加一两个级别以将他们拒之门外。 如果这不起作用,请开始使用 type.__new__ 或使用递归元类的某种方案。

(写得半开玩笑,但我见过这种混淆的做法。Django 就是一个完美的例子)

The only legitimate use-case of a metaclass is to keep other nosy developers from touching your code. Once a nosy developer masters metaclasses and starts poking around with yours, throw in another level or two to keep them out. If that doesn't work, start using type.__new__ or perhaps some scheme using a recursive metaclass.

(written tongue in cheek, but I've seen this kind of obfuscation done. Django is a perfect example)

晚雾 2024-07-17 10:44:37

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

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

但即使只有一个特殊类并且没有可预见的扩展,元类的 __new__ 和 __init__ 也是一种比混合专用代码更干净的初始化类变量或其他全局数据的方法以及类定义主体中的普通 defclass 语句。

A reasonable pattern of metaclass use is doing something once when a class is defined rather than repeatedly whenever the same class is instantiated.

When multiple classes share the same special behaviour, repeating __metaclass__=X is obviously better than repeating the special purpose code and/or introducing ad-hoc shared superclasses.

But even with only one special class and no foreseeable extension, __new__ and __init__ of a metaclass are a cleaner way to initialize class variables or other global data than intermixing special-purpose code and normal def and class statements in the class definition body.

鹿港巷口少年归 2024-07-17 10:44:37

元类可以方便地在 Python 中构建领域特定语言。 具体的例子是 Django、SQLObject 的数据库模式声明性语法。

来自 Ian Bicking 的保守元类的基本示例:

我使用过的元类是
主要是为了支持某种
声明式编程风格。 为了
实例,考虑验证
架构:

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)。

编辑(阿里):我更喜欢使用集合和实例执行此操作的示例。 重要的事实是实例,它为您提供了更多功能,并消除了使用元类的理由。 还值得注意的是,您的示例混合使用了类和实例,这肯定表明您不能仅使用元类来完成所有操作。 并创建了一种真正不统一的方法。

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,])
]

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

Metaclasses can be handy for construction of Domain Specific Languages in Python. Concrete examples are Django, SQLObject 's declarative syntax of database schemata.

A basic example from A Conservative Metaclass by Ian Bicking:

The metaclasses I've used have been
primarily to support a sort of
declarative style of programming. For
instance, consider a validation
schema:

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()

Some other techniques: Ingredients for Building a DSL in Python (pdf).

Edit (by Ali): An example of doing this using collections and instances is what I would prefer. The important fact is the instances, which give you more power, and eliminate reason to use metaclasses. Further worth noting that your example uses a mixture of classes and instances, which is surely an indication that you can't just do it all with metaclasses. And creates a truly non-uniform way of doing it.

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,])
]

It's not perfect, but already there is almost zero magic, no need for metaclasses, and improved uniformity.

笑着哭最痛 2024-07-17 10:44:37

昨天我也在想同样的事情并且完全同意。 在我看来,由于试图使其更具声明性而导致代码变得更加复杂,通常会使代码库更难维护、更难阅读,而且Python风格也更少。
它通常还需要大量的 copy.copy()ing(以维护继承并从类复制到实例),并且意味着您必须在很多地方查看发生了什么(总是从元类向上查看),这违背了蟒纹也。
我一直在浏览 formencode 和 sqlalchemy 代码,看看这样的声明式风格是否值得,但显然不值得。 这种风格应该留给描述符(例如属性和方法)和不可变数据。
Ruby 对这种声明式风格有更好的支持,我很高兴核心 Python 语言没有走这条路。

我可以看到它们用于调试,向所有基类添加元类以获得更丰富的信息。
我还看到它们仅在(非常)大型项目中使用,以消除一些样板代码(但会失去清晰度)。 sqlalchemy for 示例确实在其他地方使用它们,根据类定义中的属性值向所有子类添加特定的自定义方法
例如,玩具示例

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

可以有一个元类,该元类在该类中生成具有基于“hello”的特殊属性的方法(例如将“hello”添加到字符串末尾的方法)。 确保您不必在创建的每个子类中编写方法,而只需定义 method_maker_value ,这对于可维护性可能会有好处。

不过,这种需求非常罕见,而且只会减少一些输入,除非您有足够大的代码库,否则并不值得考虑。

I was thinking the same thing just yesterday and completely agree. The complications in the code caused by attempts to make it more declarative generally make the codebase harder to maintain, harder to read and less pythonic in my opinion.
It also normally requires a lot of copy.copy()ing (to maintain inheritance and to copy from class to instance) and means you have to look in many places to see whats going on (always looking from metaclass up) which goes against the python grain also.
I have been picking through formencode and sqlalchemy code to see if such a declarative style was worth it and its clearly not. Such style should be left to descriptors (such as property and methods) and immutable data.
Ruby has better support for such declarative styles and I am glad the core python language is not going down that route.

I can see their use for debugging, add a metaclass to all your base classes to get richer info.
I also see their use only in (very) large projects to get rid of some boilerplate code (but at the loss of clarity). sqlalchemy for example does use them elsewhere, to add a particular custom method to all subclasses based on an attribute value in their class definition
e.g a toy example

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

could have a metaclass that generated a method in that class with special properties based on "hello" (say a method that added "hello" to the end of a string). It could be good for maintainability to make sure you did not have to write a method in every subclass you make instead all you have to define is method_maker_value.

The need for this is so rare though and only cuts down on a bit of typing that its not really worth considering unless you have a large enough codebase.

末蓝 2024-07-17 10:44:37

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

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

# 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,所以我真的不知道运行时的类定义。 能够动态生成类型非常有用。

The only time I used metaclasses in Python was when writing a wrapper for the Flickr API.

My goal was to scrape flickr's api site and dynamically generate a complete class hierarchy to allow API access using Python objects:

# 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

So in that example, because I generated the entire Python Flickr API from the website, I really don't know the class definitions at runtime. Being able to dynamically generate types was very useful.

风蛊 2024-07-17 10:44:37

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

它们就像盐; 很容易用太多。

Metaclasses aren't replacing programming! They're just a trick which can automate or make more elegant some tasks. A good example of this is Pygments syntax highlighting library. It has a class called RegexLexer which lets the user define a set of lexing rules as regular expressions on a class. A metaclass is used to turn the definitions into a useful parser.

They're like salt; it's easy to use too much.

故事灯 2024-07-17 10:44:37

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

在某些情况下,能够动态无缝地重写整个库,使其在多线程应用程序中按预期正常工作可能非常有帮助。 safetkinter 模块在 threadbox 模块——不需要事件和队列。

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

Some GUI libraries have trouble when multiple threads try to interact with them. tkinter is one such example; and while one can explicitly handle the problem with events and queues, it can be far simpler to use the library in a manner that ignores the problem altogether. Behold -- the magic of metaclasses.

Being able to dynamically rewrite an entire library seamlessly so that it works properly as expected in a multithreaded application can be extremely helpful in some circumstances. The safetkinter module does that with the help of a metaclass provided by the threadbox module -- events and queues not needed.

One neat aspect of threadbox is that it does not care what class it clones. It provides an example of how all base classes can be touched by a metaclass if needed. A further benefit that comes with metaclasses is that they run on inheriting classes as well. Programs that write themselves -- why not?

本王不退位尔等都是臣 2024-07-17 10:44:37

您永远不会绝对需要使用元类,因为您始终可以使用要修改的类的继承或聚合来构造一个类来执行您想要的操作。

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

有一个很棒的 关于 Python 元类的 DeveloperWorks 文章可能会有所帮助。 维基百科文章也相当不错。

You never absolutely need to use a metaclass, since you can always construct a class that does what you want using inheritance or aggregation of the class you want to modify.

That said, it can be very handy in Smalltalk and Ruby to be able to modify an existing class, but Python doesn't like to do that directly.

There's an excellent DeveloperWorks article on metaclassing in Python that might help. The Wikipedia article is also pretty good.

盗心人 2024-07-17 10:44:37

我使用元类的方式是为类提供一些属性。 举个例子:

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

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

The way I used metaclasses was to provide some attributes to classes. Take for example:

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

will put the name attribute on every class that will have the metaclass set to point to NameClass.

枕头说它不想醒 2024-07-17 10:44:37

这是一个次要用途,但是...我发现元类有用的一件事是在创建子类时调用函数。 我将其编码到一个元类中,该元类查找 __initsubclass__ 属性:每当创建子类时,定义该方法的所有父类都会使用 __initsubclass__(cls, subcls) 进行调用。 这允许创建一个父类,然后使用某个全局注册表注册所有子类,在定义子类时运行不变检查,执行后期绑定操作等......所有这些都无需手动调用函数 创建执行这些单独职责的自定义元类。

请注意,我慢慢地意识到这种行为的隐含魔力有点不可取,因为如果脱离上下文查看类定义,这是意想不到的......所以我已经不再使用该解决方案来解决除此之外的任何严重问题为每个类和实例初始化一个 __super 属性。

This is a minor use, but... one thing I've found metaclasses useful for is to invoke a function whenever a subclass is created. I codified this into a metaclass which looks for an __initsubclass__ attribute: whenever a subclass is created, all parent classes which define that method are invoked with __initsubclass__(cls, subcls). This allows creation of a parent class which then registers all subclasses with some global registry, runs invariant checks on subclasses whenever they are defined, perform late-binding operations, etc... all without have to manually call functions or to create custom metaclasses that perform each of these separate duties.

Mind you, I've slowly come to realize the implicit magicalness of this behavior is somewhat undesirable, since it's unexpected if looking at a class definition out of context... and so I've moved away from using that solution for anything serious besides initializing a __super attribute for each class and instance.

謌踐踏愛綪 2024-07-17 10:44:37

我最近不得不使用元类来帮助以声明方式围绕数据库表定义 SQLAlchemy 模型,该数据库表填充有来自 http://census.ire.org/data/bulkdata.html

IRE 提供数据库 shell,它按照人口普查局 p012015、p012016、p012017 等的命名约定创建整数列。

我希望 a) 能够使用以下方式访问这些列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

    ...

I recently had to use a metaclass to help declaratively define an SQLAlchemy model around a database table populated with U.S. Census data from http://census.ire.org/data/bulkdata.html

IRE provides database shells for the census data tables, which create integer columns following a naming convention from the Census Bureau of p012015, p012016, p012017, etc.

I wanted to a) be able to access these columns using a model_instance.p012017 syntax, b) be fairly explicit about what I was doing and c) not have to explicitly define dozens of fields on the model, so I subclassed SQLAlchemy's DeclarativeMeta to iterate through a range of the columns and automatically create model fields corresponding to the columns:

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_)

I could then use this metaclass for my model definition and access the automatically enumerated fields on the model:

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

    ...
早乙女 2024-07-17 10:44:37

There seems to be a legitimate use described here - Rewriting Python Docstrings with a Metaclass.

单调的奢华 2024-07-17 10:44:37

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

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

Pydantic is a library for data validation and settings management that enforces type hints at runtime and provides user friendly errors when data is invalid. It makes use of metaclasses for its BaseModel and for number range validation.

At work I encountered some code that had a process that had several stages defined by classes. The ordering of these steps was controlled by metaclasses that added the steps to a list as the classes were defined. This was thrown out and the order was set by adding them to a list.

妄司 2024-07-17 10:44:37

我必须将它们用于二进制解析器一次,以使其更易于使用。 您可以使用线路上存在的字段的属性来定义消息类。
它们需要按照声明的方式进行排序,以从中构建最终的有线格式。 如果您使用有序的命名空间字典,则可以使用元类来做到这一点。 事实上,它在元类的示例中:

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

但总的来说:如果您确实需要增加元类的复杂性,请非常仔细地评估。

I had to use them once for a binary parser to make it easier to use. You define a message class with attributes of the fields present on the wire.
They needed to be ordered in the way they were declared to construct the final wire format from it. You can do that with metaclasses, if you use an ordered namespace dict. In fact, its in the examples for Metaclasses:

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

But in general: Very carefully evaluate, if you really really need the added complexity of metaclasses.

梦过后 2024-07-17 10:44:37

@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

  • 一切都是对象,所以类是对象
  • 类对象是由元类创建的
  • 所有从类型继承的类都是元类
  • 元类可以控制类创建
  • 元类也可以控制元类创建(因此它可以永远循环)
  • 这是元编程...您可以再次在运行时控制类型系统
  • ,一切都是对象,这是一个统一的系统,类型创建类型,然后类型创建实例

the answer from @Dan Gittik is cool

the examples at the end could clarify many things,I changed it to python 3 and give some explanation:

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

  • everything is object,so class is object
  • class object is created by metaclass
  • all class inheritted from type is metaclass
  • metaclass could control class creating
  • metaclass could control metaclass creating too(so it could loop for ever)
  • this's metaprograming...you could control the type system at running time
  • again,everything is object,this's a uniform system,type create type,and type create instance
静若繁花 2024-07-17 10:44:37

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

当(出于对 可读性多态性)我们想要动态定义 < a href="https://stackoverflow.com/questions/17330160/how-does-the-property-decorator-work">属性返回值(可能)来自基于(经常变化的)实例级属性的计算,只能在类级别完成在元类实例化之后和类实例化之前。

Another use case is when you want to be able to modify class-level attributes and be sure that it only affects the object at hand. In practice, this implies "merging" the phases of metaclasses and classes instantiations, thus leading you to deal only with class instances of their own (unique) kind.

I also had to do that when (for concerns of readibility and polymorphism) we wanted to dynamically define propertys which returned values (may) result from calculations based on (often changing) instance-level attributes, which can only be done at the class level, i.e. after the metaclass instantiation and before the class instantiation.

不醒的梦 2024-07-17 10:44:37

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

实例单例
我使用此代码在 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 实例不会被删除。

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

I know this is an old question But here is a use case that is really invaluable if wanting to create only a single instance of a class based on the parameters passed to the constructor.

Instance singletons
I use this code for creating a singleton instance of a device on a Z-Wave network. No matter how many times I create an instance if the same values are passed to the constructor if an instance with the exact same values exists then that is what gets returned.

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))

output

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

Now someone might say it can be done without the use of a metaclass and I know it can be done if the __init__ method is decorated. I do not know of another way to do it. The code below while it will return a similiar instance that contains all of the same data it is not a singleton instance, a new instance gets created. Because it creates a new instance with the same data there wuld need to be additional steps taken to check equality of instances. I n the end it consumes more memory then using a metaclass and with the meta class no additional steps need to be taken to check equality.

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))

output

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

The metaclass approach is really nice to use if needing to check for the removal or addition of a new instance as well.

    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))

output

number of instances: 2
number of instances: 3

Here is a way to remove an instance that has been created after the instance is no longer in use.

    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))

output

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

if you look at the output you will notice that the number of Test1 instances has not changed. That is because test1 and test3 are the same instance and I only deleted test1 so there is still a reference to the test1 instance in the code and as a result of that the test1 instance does not get removed.

Another nice feature of this is if the instance uses only the supplied parameters to do whatever it is tasked to do then you can use the metaclass to facilitate remote creations of the instance either on a different computer entirely or in a different process on the same machine. the parameters can simply be passed over a socket or a named pipe and a replica of the class can be created on the receiving end.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文