Создайте функцию python процедурно (в частности, аргументы) - PullRequest
1 голос
/ 04 марта 2010

Вопрос

Как вы процедурно создаете в 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, пожалуйста.

Ответы [ 2 ]

3 голосов
/ 04 марта 2010

Очень грубо. Принимает kw 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

Эта версия также сообщает вам о неожиданных аргументах ключевых слов

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
0 голосов
/ 04 марта 2010

Вот мой вдохновленный именованный ответ. В настоящее время он открыт для атак с использованием шаблонов, но вам не нужно самостоятельно обрабатывать любые ошибок параметров.

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...