[英]Create a python function procedurally (specifically the arguments)
如何在Python中以程序方式創建一個函數,該函數采用特定的命名參數,但允許這些參數名稱受數據驅動?
假設您要創建一個類裝飾器with_init
,它添加一個具有特定命名參數的__init__
方法,以使以下兩個類等效。
class C1(object):
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
@with_init('x y z')
class C2(object):
pass
我的第一次嘗試是通過使函數接受*args
而不是特定的命名參數來作弊:
class with_init(object):
def __init__(self, params):
self.params = params.split()
def __call__(self, cls):
def init(cls_self, *args):
for param, value in zip(self.params, args):
setattr(cls_self, param, value)
cls.__init__ = init
return cls
它在某些情況下有效:
>>> C1(1,2,3)
<__main__.C1 object at 0x100c410>
>>> C2(1,2,3)
<__main__.C2 object at 0x100ca70>
但在其他方面則沒有那么多:
>>> C2(1,2,3,4) # Should fail, but doesn't.
<__main__.C2 object at 0x100cc90>
>>> C2(x=1, y=2, z=3) # Should succeed, but doesn't.
Traceback (most recent call last):
File "<string>", line 1, in <fragment>
TypeError: init() got an unexpected keyword argument 'y'
當然,我可以將代碼添加到嵌套的init
函數中,以嘗試檢查每種可能的情況,但是似乎應該有一種更簡單的方法。
我注意到, collections.namedtuple
通過使字符串傳遞給exec
避免了這些問題。 對我來說,這似乎很繞圈,但這也許就是解決方案。
with_init.__call__
的正確實現是什么?
注意:我想要一個Python 2.x解決方案。
非常粗略。 這接受kw args並檢查以確保args的數量正確
def __call__(self, cls):
def init(cls_self, *args, **kw):
if len(args)+len(kw) != len(self.params):
raise RuntimeError("Wrong number of arguments")
for param, value in zip(self.params, args):
setattr(cls_self, param, value)
vars(cls_self).update(kw)
cls.__init__ = init
return cls
這個版本有一些改進
def __call__(self, cls):
def init(cls_self, *args, **kw):
for param, value in zip(self.params, args):
if param in kw:
raise TypeError("Multiple values for %s"%param)
kw[param]=value
if len(args) > len(self.params) or set(kw) != set(self.params):
raise TypeError("Wrong number of arguments")
vars(cls_self).update(kw)
cls.__init__ = init
return cls
此版本還告訴您有關意外關鍵字args的信息
def __call__(self, cls):
def init(cls_self, *args, **kw):
for param, value in zip(self.params, args):
if param in kw:
raise TypeError("Multiple values for %s"%param)
kw[param]=value
unexpected_args = list(set(kw)-set(self.params))
if unexpected_args:
raise TypeError("Unexpected args %s"%unexpected_args)
missing_args = list(set(self.params)-set(kw))
if missing_args:
raise TypeError("Expected args %s"%missing_args)
vars(cls_self).update(kw)
cls.__init__ = init
return cls
這是我namedtuple啟發的答案。 目前,它可以接受模板注入攻擊,但您不必自己處理任何參數錯誤。
def __call__(self, cls):
paramtxt = ', '.join(['self'] + self.params)
bodytxt = '\n\t'.join('self.%(param)s = %(param)s' % locals() for param in self.params)
template = 'def __init__(%(paramtxt)s):\n\t%(bodytxt)s' % locals()
namespace = dict()
exec template in namespace
cls.__init__ = namespace['__init__']
return cls
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.