Python元类的默认属性

3

我正在尝试创建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
3个回答

3
您需要自省__init__方法,并从中提取任何默认值。 在此过程中,getargspec函数将非常有用。 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

1
@ZaarHai:如果您有任何位置参数,它们将添加到“args”列表中,但不会添加到“defaults”元组中。 - Martijn Pieters
我明白了,谢谢。如果您能在答案中添加那个注释就太好了。 - Zaar Hai
@ZaarHai:我链接了argspec文档,其中详细说明了这一点。 - Martijn Pieters

0

在我的框架中,我使用*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的参数列表中明确命名的关键字参数。这使得新实例将被创建的默认属性可以在运行时进行修改。您可以通过dict.copy + dict.update“继承”类字典。


0

如果你在对象创建时传递参数,那么你的情况就可以工作。

>>> 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__进行装饰。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接