簡體   English   中英

程序性地創建python函數(特別是參數)

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM