动态定义 class' __init__ 参数(使用先前定义的字典)

Defining a class' __init__ arguments dynamically (using a previously defined dictionary)

有没有办法使用字典定义 class 的参数?

例如,假设我有一本包含一堆键值对的字典。每个键都应该是 class 的一个参数,每个值都应该是该参数的默认值。

就代码而言,我正在寻找这样的东西:

d = {'apples':0,'bananas':1}
class A:
    def __init__(self,k=v for k,v in d.items())
        print('apples',apples)
        print('bananas',bananas)
A(4)

应该输出:

-> apples 4
-> bananas 1

我不确定本地是否存在这样的东西,但我喜欢这个概念并且很想了解它。

无论如何,这是一个手动解决方案:

d = {'apples':0,'bananas':1}
class A:
    def __init__(self, *args, **kwargs):
        for arg, k in zip(args, d):
            setattr(self, k, arg)

        for k, v in kwargs.items():
            if hasattr(self, k):
                raise TypeError("multiple values for argument '{}'".format(k))
            elif k not in d:
                raise TypeError("unexpected keyword argument '{}'".format(k))
            setattr(self, k, v)

        for k, default_v in d.items():
            if not hasattr(self, k):
                setattr(self, k, default_v)

        print('apples', self.apples)
        print('bananas', self.bananas)


A(4)

# apples 4
# bananas 1

A(bananas=5)

# apples 0
# bananas 5

A(oranges=18)

# TypeError: unexpected keyword argument 'oranges'

可能需要 python >= 3.6(不确定版本)才能正常工作。我认为字典之前不能保证被订购。

一个选项是定义默认值,然后从 kwargsdefaults 中选择密钥并将其推送到 class __dict__

这样,传递给 class 的任何不是字典中的键的参数都将被忽略。类似于:

class A():
    def __init__(self, **kwargs):
        defaults = {'apples': 0, 'bananas': 1}
        for key, val in defaults.items():
            # Add value from kwargs if set, otherwise use default value
            self.__dict__[key] = kwargs.get(key, val)
        print(self.apples, self.bananas)

A()
# 0, 1

A(apples=5, bananas=6)
# 5, 6

A(apples=5, carrots=10)
# 5, 1

唯一的缺点是必须将参数作为关键字参数传递给 class - 普通参数将不起作用。

EDIT 可以使用这样一个事实,即命令命令用 *args 做同样的事情,但它更像是一个黑客:

class A():
    def __init__(self, *args):
        defaults = {'apples': 0, 'bananas': '1'}
        for i, key in enumerate(defaults.keys()):
          try:
            # Get the value from args by index
            self.__dict__[key] = args[i]
          except IndexError:
            # Use the default value
            self.__dict__[key] = defaults[key]
        print(self.apples, self.bananas)

A()
# 0, 1

A(5)
# 5, 1

A(5, 6, 7)
# 5, 6

您问的是:

Is there a way to define a class's arguments using a dictionary?

确实是这样。动态构建 class 通常使用所谓的 metaclass,即实例为其他 classes.

的 class

下面是一个可以满足您要求的示例。除了使用字典定义 class' __init__() 方法的参数外,它还生成方法的主体,它只打印出所有参数及其值。我添加这个是因为你从来没有回答我在评论中问你是否想要这样做的问题。

简而言之,它的作用是生成定义函数所需的源代码,该函数具有所需的参数,后跟打印它们的代码行。完成后,将使用内置的 exec() function to obtain the result of executing it (i.e. the executable byte-code of a function object). Then lastly, a dictionary entry with a key of "__init__" and a value of the executable byte-code is added to the class' dictionary (classdict) before passing it on to the built-in type() 函数执行代码以构建可用的 class 对象。

呸!解释比代码更复杂。 ;¬)

class MyMetaClass(type):
    def __new__(cls, name, bases, classdict, **kwargs):
        initargs = classdict.pop('initargs', None)
        if not initargs:
            INIT_METHOD = f"def __init__(self):\n\tprint('{name}.__init__() called')\n"
        else:
            args = ", ".join(f"{k}={v}" for (k, v) in initargs.items())
            body = "\n".join(f"\tprint({k!r}, {k})" for k in initargs.keys())
            INIT_METHOD = (f'def __init__(self, ' + f'{args}' + '):\n'
                           f'\tprint("{name}.__init__() called")\n'
                           f'{body}\n')
        result = {'__builtins__' : None, 'print': print}  # Must explicitly list any builtins.
        exec(INIT_METHOD, result)
        init_method = result['__init__']
        classdict.update({'__init__': init_method})
        return type.__new__(cls, name, bases, classdict, **kwargs)


class A(metaclass=MyMetaClass):
    initargs = {'apples': 0, 'bananas': 1}


A(4)

输出:

A.__init__() called
apples 4
bananas 1