推荐用于函数参数处理的 Python 模块?

发布于 2025-01-08 17:11:56 字数 2363 浏览 0 评论 0原文

有许多用于解析和协调命令行选项的 Python 模块(argparse、getopt、blargs 等)。 Python 拥有良好的内置功能/习惯用法,可用于处理各种函数参数(例如,默认值、*varargs、**keyword_args)。但是,当我阅读各种项目的顶级函数代码时,我发现函数参数的纪律和标准化明显少于命令行参数。

对于简单的函数来说,这不是问题;内置的论证功能非常有效并且绰绰有余。但是有很多功能丰富的模块,其顶级函数提供了许多不同的参数和选项(一些是互补的或排他的)、不同的操作模式、默认值、覆盖等——也就是说,它们具有参数复杂性接近命令行参数。他们似乎主要以临时方式处理他们的论点。

考虑到命令行处理模块的数量,以及它们随着时间的推移变得多么精致,我期望至少有一些模块可以简化复杂函数参数的争论。但我搜索了 PyPi、stackoverflow 和 Google,但没有成功。那么...您有推荐的函数(不是命令行!)参数处理模块吗?

---用示例更新---

很难给出一个真正简单的具体示例,因为只有在处理复杂的模块时用例才会出现。但这里有一个在代码中解释问题的机会:具有默认值的格式化程序模块可以在格式化程序实例化中或在调用函数/方法时覆盖。由于只有几个选项,已经有大量的选项处理废话,并且选项名称重复得令人恶心。

defaults = { 'indent':     4,
              'prefix':    None,
              'suffix':    None,
              'name':      'aFormatter',
              'reverse':   False,
              'show_name': False
            }

class Formatter(object):

    def __init__(self, **kwargs):
        self.name    = kwargs.get('name',    defaults['name'])
        self.indent  = kwargs.get('indent',  defaults['indent'])
        self.prefix  = kwargs.get('prefix',  defaults['prefix'])
        self.suffix  = kwargs.get('suffix',  defaults['suffix'])
        self.reverse = kwargs.get('reverse', defaults['reverse'])
        self.show_name = kwargs.get('show_name', defaults['show_name'])

    def show_lower(self, *args, **kwargs):
        indent = kwargs.get('indent', self.indent) or 0
        prefix = kwargs.get('prefix', self.prefix) 
        suffix = kwargs.get('suffix', self.suffix)
        reverse = kwargs.get('reverse', self.reverse)
        show_name = kwargs.get('show_name', self.show_name)

        strings = []
        if show_name:
            strings.append(self.name + ": ")
        if indent:
            strings.append(" " * indent)
        if prefix:
            strings.append(prefix)
        for a in args:
            strings.append(a.upper() if reverse else a.lower())
        if suffix:
            strings.append(suffix)
        print ''.join(strings)

if __name__ == '__main__':
    fmt = Formatter()
    fmt.show_lower("THIS IS GOOD")
    fmt.show_lower("THIS", "IS", "GOOD")
    fmt.show_lower('this IS good', reverse=True)
    fmt.show_lower("something!", show_name=True)

    upper = Formatter(reverse=True)
    upper.show_lower("this is good!")
    upper.show_lower("and so is this!", reverse=False)

There are many Python modules for parsing and coordinating command line options (argparse, getopt, blargs, etc). And Python is blessed with good built-in features/idioms for handling varied function arguments (e.g., default values, *varargs, **keyword_args). But when I read various projects' code for top-level functions, I see notably less discipline and standardization of function arguments than command line arguments.

For simple functions, this isn't an issue; the built-in argument features work great and are more than sufficient. But there are a lot of functionally rich modules whose top-level functions provide lots of different arguments and options (some complementary or exclusive), different modes of operation, defaults, over-rides, etc.--that is, they have argument complexity approaching that of command line arguments. And they seem to largely handle their arguments in ad hoc ways.

Given the number of command line processing modules out there, and how refined they've become over time, I'd expect at least a few modules for simplifying the wrangling of complicated function arguments. But I've searched PyPi, stackoverflow, and Google without success. So...are there function (not command line!) argument handling modules you would recommend?

---update with example---

It's hard to give a truly simple concrete example because the use case doesn't appear until you're dealing with a sophisticated module. But here's a shot at explaining the problem in code: A formatter module with defaults that can be overridden in formatter instantiation, or when the function/method is called. For having only a few options, there's already an awful lot of option-handling verbiage, and the option names are repeated ad nauseam.

defaults = { 'indent':     4,
              'prefix':    None,
              'suffix':    None,
              'name':      'aFormatter',
              'reverse':   False,
              'show_name': False
            }

class Formatter(object):

    def __init__(self, **kwargs):
        self.name    = kwargs.get('name',    defaults['name'])
        self.indent  = kwargs.get('indent',  defaults['indent'])
        self.prefix  = kwargs.get('prefix',  defaults['prefix'])
        self.suffix  = kwargs.get('suffix',  defaults['suffix'])
        self.reverse = kwargs.get('reverse', defaults['reverse'])
        self.show_name = kwargs.get('show_name', defaults['show_name'])

    def show_lower(self, *args, **kwargs):
        indent = kwargs.get('indent', self.indent) or 0
        prefix = kwargs.get('prefix', self.prefix) 
        suffix = kwargs.get('suffix', self.suffix)
        reverse = kwargs.get('reverse', self.reverse)
        show_name = kwargs.get('show_name', self.show_name)

        strings = []
        if show_name:
            strings.append(self.name + ": ")
        if indent:
            strings.append(" " * indent)
        if prefix:
            strings.append(prefix)
        for a in args:
            strings.append(a.upper() if reverse else a.lower())
        if suffix:
            strings.append(suffix)
        print ''.join(strings)

if __name__ == '__main__':
    fmt = Formatter()
    fmt.show_lower("THIS IS GOOD")
    fmt.show_lower("THIS", "IS", "GOOD")
    fmt.show_lower('this IS good', reverse=True)
    fmt.show_lower("something!", show_name=True)

    upper = Formatter(reverse=True)
    upper.show_lower("this is good!")
    upper.show_lower("and so is this!", reverse=False)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

一个人的夜不怕黑 2025-01-15 17:11:56

当我第一次读到你的问题时,我心想你是在要求一个创可贴模块,但它不存在,因为没有人愿意编写一个使糟糕设计持续存在的模块。

但我意识到情况比这更复杂。创建您所描述的模块的目的是创建可重用的通用代码。现在,很可能有一些接口相当复杂。但这些接口恰恰是一般情况代码可能无法轻松处理的接口。它们很复杂,因为它们解决具有许多特殊情况的问题域。

换句话说,如果一个接口确实无法重构,那么它可能需要大量自定义的特殊情况代码,这些代码的可预测性不足以值得在模块中进行概括。相反,如果一个接口可以很容易地用您所描述的那种模块进行修补,那么它也可能可以被重构——在这种情况下它应该是这样。

When I first read your question, I thought to myself that you're asking for a band-aid module, and that it doesn't exist because nobody wants to write a module that enables bad design to persist.

But I realized that the situation is more complex than that. The point of creating a module such as the one you describe is to create reusable, general-case code. Now, it may well be that there are some interfaces that are justifiably complex. But those interfaces are precisely the interfaces that probably can't be handled easily by general-case code. They are complex because they address a problem domain with a lot of special cases.

In other words, if an interface really can't be refactored, then it probably requires a lot of custom, special-case code that isn't predictable enough to be worth generalizing in a module. Conversely, if an interface can easily be patched up with a module of the kind you describe, then it probably can also be refactored -- in which case it should be.

﹏雨一样淡蓝的深情 2025-01-15 17:11:56
  1. 我认为命令行解析和函数参数处理没有太多共同点。命令行的主要问题是唯一可用的数据结构是字符串的平面列表,并且您没有像函数头这样的工具可用于定义每个字符串的含义。在Python函数的头中,您可以为每个参数命名,可以接受容器作为参数,可以定义默认参数值等。命令行解析库的作用实际上是为命令行提供一些Python 为函数调用提供的功能:为参数命名、分配默认值、转换为所需的类型等。在 Python 中,所有这些功能都是内置的,因此您不需要库即可获得这种便利。

  2. 关于您的示例,可以通过多种方式使用该语言提供的功能来改进此设计。您可以使用默认参数值而不是 defaults 字典,您可以将所有标志封装在 FormatterConfig 类中,并且只传递一个参数,而不是一遍又一遍地传递所有这些参数。但我们假设您想要的正是示例代码中给出的接口。实现此目的的一种方法是使用以下代码:

    类配置(字典):
        def __init__(自我,配置):
            dict.__init__(自我,配置)
            self.__dict__ = 自我
    
    def get_config(kwargs, 默认值):
        配置=默认值.copy()
        配置.更新(kwargs)
        返回配置(配置)
    
    类格式化程序(对象):
    
        def __init__(self, **kwargs):
            self.config = get_config(kwargs, 默认值)
    
        def show_lower(self, *args, **kwargs):
            config = get_config(kwargs, self.config)
    
            字符串=[]
            如果config.show_name:
                strings.append(config.name + ": ")
            strings.append(" " * config.indent)
            如果配置.前缀:
                strings.append(config.prefix)
            对于参数中的 a:
                strings.append(a.upper() if config.reverse else a.lower())
            如果配置.后缀:
                strings.append(config.suffix)
            打印“”.join(字符串)
    

    Python 提供了很多工具来进行此类参数处理。因此,即使我们决定不使用其中一些(例如默认参数),我们仍然可以避免太多重复。

  1. I don't think command line parsing and function argument processing have much in common. The main issue with the command line is that the only available data structure is a flat list of strings, and you don't have an instrument like a function header available to define what each string means. In the header of a Python function, you can give names to each of the parameters, you can accept containers as parameters, you can define default argument values etc. What a command line parsing library does is actually providing for the command line some of the features Python offers for function calls: give names to parameters, assign default values, convert to the desired types etc. In Python, all these features are built-in, so you don't need a library to get to that level of convenience.

  2. Regarding your example, there are numerous ways how this design can be improved by using the features the language offers. You can use default argument values instead of your defaults dictionary, you can encapsulate all the flags in a FormatterConfig class and only pass one argument instead of all those arguments again and again. But let's just assume you want exactly the interface you gave in the example code. One way to achieve this would be the following code:

    class Config(dict):
        def __init__(self, config):
            dict.__init__(self, config)
            self.__dict__ = self
    
    def get_config(kwargs, defaults):
        config = defaults.copy()
        config.update(kwargs)
        return Config(config)
    
    class Formatter(object):
    
        def __init__(self, **kwargs):
            self.config = get_config(kwargs, defaults)
    
        def show_lower(self, *args, **kwargs):
            config = get_config(kwargs, self.config)
    
            strings = []
            if config.show_name:
                strings.append(config.name + ": ")
            strings.append(" " * config.indent)
            if config.prefix:
                strings.append(config.prefix)
            for a in args:
                strings.append(a.upper() if config.reverse else a.lower())
            if config.suffix:
                strings.append(config.suffix)
            print "".join(strings)
    

    Python offers a lot of tools to do this kind of argument handling. So even if we decide not to use some of them (like default arguments), we still can avoid to repeat ourselves too much.

暗藏城府 2025-01-15 17:11:56

如果您的 API 非常复杂,您认为使用某些模块来处理传递给您的选项会更容易,那么实际的解决方案很可能是简化您的 API。事实上,有些模块有非常复杂的方式来调用东西,这是一种耻辱,而不是一个特性。

If your API is so complex you think it would be easier to use some module to process the options that were passed you, there's a good chance the actual solution is to simplify your API. The fact some modules have very complex ways to call stuff is a shame, not a feature.

天赋异禀 2025-01-15 17:11:56

它在开发人员手中,但是如果您正在制作一个可能对其他一些项目有用或将在其他用户之间发布的库,那么我认为首先您需要确定您的问题并对其进行分析,

很好地记录您的功能,这很好尽量减少参数的数量,
为函数参数提供默认值,用户可能难以指定到底需要传递什么。

对于某些复杂的要求,您可以提供特殊的类方法,这些方法可以被高级编程覆盖,或者被真正想要实现他们正在使用库的东西的高级用户覆盖,继承总是存在的。

您也可以阅读 PEP8 ,这可能会有所帮助,但最终目标是指定参数的最小数量,限制用户输入必需的参数,最好为可选参数提供默认值 - 以您的 library / code 易于理解的方式普通开发者也...

Its in developer's hand, but if you're making a library which may be useful for some other projects or will be published across other users, then I think first you need to identify your problem and analyse it,

Document your functions well, Its good to minimize the number of arguments,
provide default values for functional arguments where users may have trouble to specify what exactly needed to pass.

and for some complex requirement you can provide special classmethods that can be override for advanced programming or by advanced users who actually wants to achieve what they are playing with the library, inheritance is always there.

and you can read the PEP8 also which may helpful, but ultimate goal is to specify the minimum number of arguments, restrict users to enter required arguments, its good to provide default values for optional arguments - in the way that your library / code is easily understandable by ordinary developers too...

春庭雪 2025-01-15 17:11:56

您可以为默认编写更通用的代码。

如果您考虑以相反的方式进行默认设置,请检查默认设置并覆盖关键字(如果不存在)。

 defaults = { 'indent':     4,
          'prefix':    None,
          'suffix':    None,
          'name':      'aFormatter',
          'reverse':   False,
          'show_name': False
        }

class Formatter(object):

   def __init__(self, **kwargs):
      for d,dv in defaults.iteritems():
         kwargs[d] = kwargs.get(d, dv)

旁注:
我建议在 __init__ 方法定义中使用默认关键字 args。这使得function定义真正成为类的其他开发人员和用户的契约(Formatter

def __init__(self, indent=4, reverse=False .....etc..... ):

You could write more generic code for the defaulting.

If you think about defaulting the other way around, going through the defaults and overwriting the keywords if the don't exist.

 defaults = { 'indent':     4,
          'prefix':    None,
          'suffix':    None,
          'name':      'aFormatter',
          'reverse':   False,
          'show_name': False
        }

class Formatter(object):

   def __init__(self, **kwargs):
      for d,dv in defaults.iteritems():
         kwargs[d] = kwargs.get(d, dv)

Side Note:
I'd recommend using keywords args in the __init__ method definition with defaults. This allows the function definition really become the contract to other developers and users of your class (Formatter)

def __init__(self, indent=4, reverse=False .....etc..... ):
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文