动态定义类的__init__参数(使用以前定义的字典)



是否有一种方法可以使用字典定义类的参数?

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

在代码方面,我正在寻找这样的东西:

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

你问:

是否有一种方法可以使用字典定义类的参数?

确实如此。动态构建类通常使用所谓的元类来完成,即一个类的实例是其他类。

下面是一个你想要的例子。除了使用字典定义类的__init__()方法的参数外,它还生成方法体,该方法体只是打印出它的所有参数及其值。我加上这一点是因为你从来没有回答过我在评论中问你是否想这样做的问题。

简单地说,它的作用是生成定义一个函数所需的源代码,该函数具有所需的参数,然后是打印它们的代码行。一旦完成,代码将使用内置的exec()函数执行以获得执行结果(即函数对象的可执行字节码)。最后,一个键值为"__init__",值为可执行字节码的字典条目被添加到类的字典(classdict)中,然后将其传递给内置的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):ntprint('{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

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

这样传递给类的任何参数,如果不是字典中的键,将被忽略。比如:

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

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

编辑可以使用字典排序的事实来与*args做同样的事情,但这有点像一个hack:

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

我不确定本地是否存在这样的东西,尽管我喜欢这个概念,并希望了解它。

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

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(不确定版本)才能工作。我认为我们以前不能保证能定购这批字典。

最新更新