0

我想以更简单的方式为我的库提供用户 API,以区分我传递给函数的不同类型的参数。所有参数组都在前面定义(现在我有 3 个组),但它们的属性需要在运行时构建。我可以在 Django ORM 样式中执行此操作,其中双下划线分隔参数的 2 部分。但它非常不可读。例子:

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

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

更好的方法是执行此 SQLAlchemy,但仅比较属性,并且所有参数都已在前面定义。例子:

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 我的第一个测试,它使用相等运算符(但它永远不应该以这种方式使用):

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')

它工作正常,但 using==暗示我想比较一些东西并且我想分配价值。

4

3 回答 3

1

注意:我不知道我有多喜欢你想要的这个模式。但我知道一件烦人的事情是调用所有的导入api_function。例如from api import POST, API, api_function

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

但是您要求的另一种方式肯定是:

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'}
于 2013-09-10T22:06:51.913 回答
0

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

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__.

于 2013-09-10T22:44:47.133 回答
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'))

感谢您的关注和回答。

于 2013-09-12T08:07:09.923 回答