【问题标题】:Python metaclass default attributesPython 元类默认属性
【发布时间】:2012-08-15 16:58:00
【问题描述】:

我正在尝试在 Python (2.7) 中创建元类,它将传递给对象 __init__ 的参数设置为对象属性。

class AttributeInitType(type):        
    def __call__(self, *args, **kwargs):
        obj = super(AttributeInitType, self).__call__(*args, **kwargs)
        for k, v in kwargs.items():
            setattr(obj, k, v)
        return obj

用法:

class Human(object):
    __metaclass__ = AttributeInitType

    def __init__(self, height=160, age=0, eyes="brown", sex="male"):
        pass

man = Human()

问题:我希望man 实例的默认属性设置为类的__init__。我该怎么做?

更新:我找到了更好的解决方案:

  • 在类创建期间仅检查一次 __init__ 方法
  • 不会覆盖(可能)由类的真实__init__ 设置的属性

代码如下:

import inspect
import copy

class AttributeInitType(type):
    """Converts keyword attributes of the init to object attributes"""
    def __new__(mcs, name, bases, d):
        # Cache __init__ defaults on a class-level
        argspec = inspect.getargspec(d["__init__"])
        init_defaults = dict(zip(argspec.args[-len(argspec.defaults):], argspec.defaults))
        cls = super(AttributeInitType, mcs).__new__(mcs, name, bases, d)
        cls.__init_defaults = init_defaults
        return cls

    def __call__(mcs, *args, **kwargs):
        obj = super(AttributeInitType, mcs).__call__(*args, **kwargs)
        the_kwargs = copy.copy(obj.__class__.__init_defaults)
        the_kwargs.update(kwargs)
        for k, v in the_kwargs.items():
            # Don't override attributes set by real __init__
            if not hasattr(obj, k):
                setattr(obj, k, v)
        return obj

【问题讨论】:

    标签: python metaclass


    【解决方案1】:

    您需要自省__init__ 方法并从中提取任何默认值。 getargspec function 在那里会很有帮助。

    getargspec 函数返回(以及其他)参数名称列表和默认值列表。您可以结合这些来找到给定函数的默认参数规范,然后使用该信息来设置对象的属性:

    import inspect
    
    class AttributeInitType(type):        
        def __call__(self, *args, **kwargs):
            obj = super(AttributeInitType, self).__call__(*args, **kwargs)
            argspec = inspect.getargspec(obj.__init__)
            defaults = dict(zip(argspec.args[-len(argspec.defaults):], argspec.defaults))
            defaults.update(kwargs)
            for key, val in defaults.items():
                setattr(obj, key, val)
            return obj
    

    使用上述元类,您可以省略任何参数,它们将在新实例上设置,或者您可以通过显式传递它们来覆盖它们:

    >>> man = Human()
    >>> man.age
    0
    >>> man.height
    160
    >>> Human(height=180).height
    180
    

    【讨论】:

    • @ZaarHai:如果您有任何位置参数,它们会添加到 args 列表中,但不会添加到 defaults 元组中。
    • 我明白了。谢谢。如果您可以将该注释添加到答案中,那就太好了。
    • @ZaarHai:我链接到 argspec 文档,详细说明了这一点。
    【解决方案2】:

    如果你在创建对象时传递参数,你的情况就有效

    >>> man
    <test.Human object at 0x10a71e810>
    >>> dir(man)
    ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
    >>> man=Human(height=10)
    >>> dir(man)
    ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'height']
    >>> man.height
    10
    

    但它不适用于默认参数。为此,您必须专门从 __init__ 函数对象中提取它们。

    另一种方法是装饰__init__

    【讨论】:

      【解决方案3】:

      我在我的框架中使用 *args/**kwargs、一个类默认字典以及在我的基础对象 init 中调用 attribute_setter 来执行此操作。我觉得这比装饰更简单,而且绝对不如元类复杂。

      Class Base(object):
          defaults = {"default_attribute" : "default_value"}
      
          def __init__(self, *args, **kwargs):
              super(Base, self).__init__()
              self.attribute_setter(self.defaults.items(), *args, **kwargs)
      
          def attribute_setter(self, *args, **kwargs):
              if args: # also allow tuples of name/value pairs
                  for attribute_name, attribute_value in args:
                      setattr(self, attribute_name, attribute_value)
              [setattr(self, name, value) for name, value in kwargs.items()]
      b = Base(testing=True)
      # print b.testing
      # True
      # print b.default_attribute
      # "default_value"
      

      这种组合允许在运行时通过 init 指定任意属性,方法是将它们指定为关键字参数(或作为名称/值对的位置参数元组)。

      类默认字典用于提供默认参数,而不是在 init 的参数列表中显式命名关键字参数。这使得创建新实例的默认属性可以在运行时修改。您可以通过 dict.copy + dict.update “继承”类字典。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-12-08
        • 2012-01-05
        • 1970-01-01
        • 2020-04-09
        • 1970-01-01
        • 1970-01-01
        • 2019-10-21
        相关资源
        最近更新 更多