魔术分配自定义参数

时间:2013-09-10 21:28:14

标签: python python-2.7

我想为我的库提供用户API,更容易区分我传递给函数的不同类型的参数。所有参数组都是先前定义的(现在我有3组),但是它们的属性需要在运行时构造。我可以在Django ORM样式中执行此操作,其中双下划线分隔2部分参数。但这是非常难以理解的。例如:

def api_function(**kwargs):
    """ Separate passed arguments """

api_function(post__arg1='foo', api__arg1='bar', post_arg2='foo2')

更好的方法做这个SQLAlchemy,但只是为了比较属性,所有args都是先前定义的。例如:

class API(object):
    arg1 = Arg()
    arg2 = Arg()
class Post(object): #...
def api_function(*args):
    """ Separate passed arguments """

api_function(POST.arg1=='foo', API.arg1=='bar', POST.arg2=='foo2')

我想要达成的是这样的行为:

class API(object): # Magic
class POST(object): # Magic
def api_function(*args):
    """ Separate passed arguments """

api_function(POST.arg1='foo', API.arg1='bar', POST.arg2='foo2')

我尝试了什么:

  • 使用已定义的__setattr__声明元模型,但会在评估SyntaxError: keyword can't be an expression上升起
  • 声明__set__,但它是为已知属性而设计的

我的问题是:

  • 在Python中甚至可以像第三个片段一样工作吗?
  • 如果没有,在第三个片段中是否有任何非常接近的解决方案?最好的方法应该使用赋值运算符API.arg1='foo',最差的API(arg1='foo')

要求 - 至少应该在Python 2.7上运行。很高兴使用Python 3.2。

EDIT1 我的第一个测试是使用相等运算符(但 NEVER 应该以这种方式使用):

class APIMeta(type):
    def __getattr__(cls, item):
        return ApiData(item, None)

class API(object):
    __metaclass__ = APIMeta

    def __init__(self, key, value):
        self.key = key
        self.value = value

    def __str__(self):
        return "{0}={1}".format(self.key, self.value)

    def __eq__(self, other):
        self.value = other
        return self

def print_api(*api_data):
    for a in api_data:
        print(str(a))

print_api(API.page=='3', API=='bar')

它工作正常,但使用==表示我想要比较某些内容,并且我想分配值。

3 个答案:

答案 0 :(得分:1)

注意:我不知道我有多喜欢这种架构。但是我知道一个令人讨厌的事情就是调用api_function的所有导入。例如。 from api import POST, API, api_function

正如我在评论中所说,第一种方式是不可能的。这是因为赋值(=)是一个语句而不是表达式,因此它不能返回值。 Source

但你要求的另一种方式是:

class POST(object):
    def __init__(self, **kwargs):
        self.args = kwargs
    # You'll also probably want to make this function a little safer.
    def __getattr__(self, name):
        return self.args[name]

def api_function(*args):
    # Update this to how complicated the handling needs to be
    # but you get the general idea...
    post_data = None
    for a in args:
        if isinstance(a, POST):
            post_data = a.args
    if post_data is None:
        raise Exception('This function needs a POST object passed.')
    print post_data

使用它:

>>> api_function('foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in api_function
Exception: This function needs a POST object passed.

>>> api_function(POST(arg1='foo'))
{'arg1': 'foo'}

>>> api_function(POST(arg1='foo',
...                   arg2='bar'
...                  )
...             )
{'arg1': 'foo', 'arg2': 'bar'}

答案 1 :(得分:0)

这是我的解决方案。它不是最好的设计,因为参数石斑鱼的结构嵌套得很深,所以我很感激它的反馈:

class ArgumentGrouper(object):
    """Transforms a function so that you can apply arguments in named groups.

    This system isn't tested as thoroughly as something with so many moving
    parts should be. Use at own risk.

    Usage:
    @ArgumentGrouper("foo", "bar")
    def method(regular_arg, foo__arg1, bar__arg2):
        print(regular_arg + foo__arg1 + bar__arg2)

    method.foo(", ").bar("world!")("Hello")()  # Prints "Hello, world!"
    """

    def __call__(self, func):
        """Decorate the function."""
        return self.Wrapper(func, self.argument_values)

    def __init__(self, *argument_groups):
        """Constructor.

        argument_groups -- The names of argument groups in the function.
        """
        self.argument_values = {i: {} for i in argument_groups}

    class Wrapper(object):
        """This is the result of decorating the function. You can call group
        names as function to supply their keyword arguments.
        """

        def __call__(self, *args):
            """Execute the decorated function by passing any given arguments
            and predefined group arguments.
            """
            kwargs = {}
            for group, values in self.argument_values.items():
                for name, value in values.items():
                    # Add a new argument in the form foo__arg1 to kwargs, as
                    # per the supplied arguments.
                    new_name = "{}__{}".format(
                        group,
                        name
                    )
                    kwargs[new_name] = value

            # Invoke the function with the determined arguments.
            return self.func(*args, **kwargs)

        def __init__(self, func, argument_values):
            """Constructor.

            func -- The decorated function.
            argument_values -- A dict with the current values for group
                arguments. Must be a reference to the actual dict, since each
                WrappedMethod uses it.
            """
            self.func = func
            self.argument_values = argument_values

        def __getattr__(self, name):
            """When trying to call `func.foo(arg1="bar")`, provide `foo`. TODO:
            This would be better handled at initialization time.
            """
            if name in self.argument_values:
                return self.WrappedMethod(name, self, self.argument_values)
            else:
                return self.__dict__[name]

        class WrappedMethod(object):
            """For `func.foo(arg1="bar")`, this is `foo`. Pretends to be a
            function that takes the keyword arguments to be supplied to the
            decorated function.
            """
            def __call__(self, **kwargs):
                """`foo` has been called, record the arguments passed."""
                for k, v in kwargs.items():
                    self.argument_values[self.name][k] = v
                return self.wrapper

            def __init__(self, name, wrapper, argument_values):
                """Constructor.

                name -- The name of the argument group. (This is the string
                    "foo".)
                wrapper -- The decorator. We need this so that we can return it
                    to chain calls.
                argument_values -- A dict with the current values for group
                    arguments. Must be a reference to the actual dict, since
                    each WrappedMethod uses it.
                """
                self.name = name
                self.wrapper = wrapper
                self.argument_values = argument_values

# Usage:
@ArgumentGrouper("post", "api")
def api_function(regular_arg, post__arg1, post__arg2, api__arg3):
    print("Got regular args {}".format(regular_arg))
    print("Got API args {}, {}, {}".format(post__arg1, post__arg2, api__arg3))

api_function.post(
    arg1="foo", arg2="bar"
).api(
    arg3="baz"
)
api_function("foo")

然后,用法:

@ArgumentGrouper("post", "api")
def api_function(regular_arg, post__arg1, post__arg2, api__arg3):
    print("Got regular args {}".format(regular_arg))
    print("Got API args {}, {}, {}".format(post__arg1, post__arg2, api__arg3))

api_function.post(
    arg1="foo", arg2="bar"
).api(
    arg3="baz"
)
api_function("foo")

输出:

Got regular args foo
Got API args foo, bar, baz

通过内省来刮取参数组名称应该很简单。

你会注意到参数命名约定被硬编码到WrappedMethod中,所以你必须确保你没关系。

您也可以在一个声明中调用它:

api_function.post(
    arg1="foo", arg2="bar"
).api(
    arg3="baz"
)("foo")

或者你可以添加一个专用的run方法来调用它,它只取代Wrapper.__call__

答案 2 :(得分:0)

Python不允许在任何其他代码中使用赋值运算符,因此:

(a=1)
func((a=1))

会上升SyntaxError。这意味着不可能以这种方式使用它。此外:

func(API.arg1=3)

将会认为赋值的左侧是参数API.arg1,它在Python中不是变量的有效名称。唯一的解决方案是以SQLAlchemy风格制作它:

func({
    API.arg1: 'foo',
    API.arg2: 'bar',
    DATA.arg1: 'foo1',
})

func(**{
    API.arg1: 'foo',
    API.arg2: 'bar',
    DATA.arg1: 'foo1',
})

或仅仅:

func( API(arg1='foo', arg2='bar'), POST(arg1='foo1'), POST(arg2='bar1'))

感谢您的关注和回答。