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

Python 元类的一些(具体)用例是什么?,python,metaclass,Python,Metaclass,我有一个朋友喜欢使用元类,并定期提供元类作为解决方案 我认为你几乎不需要使用元类。为什么?因为我认为如果你对一个类做这样的事情,你可能应该对一个对象做这样的事情。一个小的重新设计/重构已经准备就绪 能够使用元类已经导致很多地方的很多人将类用作某种二流对象,这对我来说是灾难性的。编程将被元编程所取代吗?不幸的是,类装饰器的添加使它更容易被接受 所以,我非常想知道Python中元类的有效(具体)用例。或者了解为什么变异类有时比变异对象更好 我将开始: 有时在使用第三方时 图书馆能够 以某种方式对类进

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

我认为你几乎不需要使用元类。为什么?因为我认为如果你对一个类做这样的事情,你可能应该对一个对象做这样的事情。一个小的重新设计/重构已经准备就绪

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

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

我将开始:

有时在使用第三方时 图书馆能够 以某种方式对类进行变异

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

您永远不需要绝对使用元类,因为您总是可以使用要修改的类的继承或聚合来构造一个做您想要做的事情的类

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


Python中有一个非常好的关于元分类的方法可能会有所帮助。这也很好。

我唯一一次在Python中使用元类是在为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,所以我真的不知道运行时的类定义。能够动态生成类型非常有用

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

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

    class Mp3File(MusicFile):
        extensions = ['.mp3']  # Register this type as a handler for mp3 files
        ...
        # Implementation of mp3 methods go here
    
    然后,元类维护一个
    {.mp3':MP3File,…}
    等的字典,并在您通过工厂函数请求处理程序时构造适当类型的对象

  • 改变行为。您可能希望对某些属性附加一个特殊的含义,从而在它们出现时改变行为。例如,您可能希望查找名为
    \u get\u foo
    \u set\u foo
    的方法,并将它们透明地转换为属性。作为一个现实世界的例子,我写了一个配方来给出更多类似C的结构定义。元类用于将声明的项转换为结构格式字符串、处理继承等,并生成一个能够处理它的类

    对于其他现实世界的例子,请看一下各种ORM,比如ORM或。同样,其目的是解释具有特定含义的定义(这里是SQL列定义)


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


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

      对于在Python中构造特定于域的语言来说非常方便。具体的例子是Django,SQLObject的数据库模式声明语法

      Ian Bicking的一个基本示例:

      我使用的元类是 主要是为了支持一种 声明式编程风格。对于 实例,考虑验证 模式:

      其他一些技巧:(pdf)

      Edit(by 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,])
      ]
      

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

      让我们从Tim Peter的经典名言开始:

      元类比99%更具魔力 很多用户都应该担心。如果 你不知道你是否需要它们,是吗 不要(那些真正需要帮助的人) 他们确信他们 需要它们,而不需要一个 解释原因)。蒂姆·彼得斯 (2002-12-22年后的c.l.p)

      话虽如此,我(定期)遇到了元类的真正用法。我想到的是Django,您的所有模型都继承自models.Model。反过来,Model会用Django的ORM特性来包装您的DB模型。这种魔法是通过元类实现的。它创建各种类型的异常类、管理器类等


      请参见django/db/models/base.py,class ModelBase()了解故事的开头

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

      class NameClass(type):
          def __init__(cls, *args, **kwargs):
             type.__init__(cls, *args, **kwargs)
             cls.name = cls.__name__
      
      将把name属性设置为ever
      class NameClass(type):
          def __init__(cls, *args, **kwargs):
             type.__init__(cls, *args, **kwargs)
             cls.name = cls.__name__
      
      class PlottingInteractive:
          add_slice = wrap_pylab_newplot(add_slice)
      
      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))
      
      class test(baseclass_with_metaclass):
          method_maker_value = "hello"
      
      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
      
          ...
      
      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
      
      >>> 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 :(
      
      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
      
      >>> 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}
      
      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
      
      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)
      
      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)
      
      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
      
      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