使用参数数组的长度作为同一函数的另一个参数的默认值

时间:2015-01-29 10:29:49

标签: python python-2.7

这是我第一次在SO中提问,所以如果我不能正确地做到这一点,请不要犹豫地编辑或要求我修改它。

我认为我的问题很普遍,所以我没有找到任何与此主题相关的问题,我感到非常惊讶。如果我错过了这个并且这个问题是重复的,那么如果你能提供一个已经回答的地方的链接,我将非常感激。

想象一下,我需要使用(至少)三个参数来实现一个函数:数组astart索引和end索引。如果未提供,start参数应引用数组的第一个位置(start = 0),而end参数应设置为最后一个位置(end = len(a) - 1) 。显然,定义:

def function(a, start = 0, end = (len(a) - 1)):
    #do_something
    pass

不起作用,导致异常(NameError: name 'a' is not defined)。有一些解决方法,例如使用end = -1end = None,并在函数正文中有条件地将其分配给len(a) - 1

def function(a, start = 0, end = -1):
    end = end if end != -1 else (len(a) -1)
    #do_something

但我觉得应该有更多的" pythonic"处理这种情况的方法,不仅是数组的长度,还有任何参数,其默认值是另一个(非可选)参数的函数。你会如何应对这种情况?条件分配是最佳选择吗?

谢谢!

5 个答案:

答案 0 :(得分:7)

使用None之类的标记值是典型的:

def func(a, start=0, end=None):
    if end is None:
        end = # whatever
    # do stuff

但是,对于您的实际用例,已经有一种内置的方法可以满足Python启动/停止/步进的方式 - 这使得您的函数提供了关于内置/其他库工作方式的一致界面:

def func(a, *args):
    slc = slice(*args)
    for el in a[slc]:
        print(el)

请参阅https://docs.python.org/2/library/functions.html#slice


如果您只想按顺序支持开始/结束,那么(请注意None在用作 end len(a)实际上意味着 0 >或def func(a, start=0, end=None): return a[start:end] 用作 start 时):

{{1}}

答案 1 :(得分:0)

这是不可能的,函数不能在参数列表中执行,你可以通过参数而不是它们的输出传递函数。

def function(a, start = 0):
    end = len(a)

答案 2 :(得分:0)

我不确定你对数组的定义或问题的前提,但根据我的理解,你正在尝试将结束分配到a的长度。如果是这样,只需声明结束参数。像这样:

def function(a, start=0):
    end=len(a)

或像你说的那样做条件:

def function(a, start=0, end=False):
    if not end:
        end = len(a)

或者在调用函数之前简单地声明结束变量并将其传递给参数! 不确定这是否回答了你的问题,但希望它能帮助大声笑!

答案 3 :(得分:0)

根据Function with dependent preset arguments中@NPE提供的答案,使用-1或(更好)None作为哨兵值的替代方法是使用对象(命名对象?)即使None是函数的有效值,也可以使用它。例如:

default = object()

def function(a, start = 0, end = default):
    if end is default: end = (len(a) - 1)
    return start, end

允许调用:function([1,2,3,4]),返回(0, 3)

我个人觉得这个解决方案很方便,至少对我自己而言

编辑:如果我们使用last代替default,则代码可能更具可读性:

last = object()

def function(a, start = 0, end = last):
    if end is last: end = (len(a) - 1)
    return start, end

答案 4 :(得分:0)

这必须是我写过的最骇客的代码。我认为它接近你要求的东西(我提出的另一个选择是在函数定义中使用lambda,但它需要一点太多的空间才能成为IMO):

import inspect
from functools import wraps

class defaultArguments(object):

    def __init__(self):
        self.lazyArgs = []

    def initialize(self, func):

        lazyArgs, self.lazyArgs = self.lazyArgs, []

        @wraps(func)
        def functionWrapper(*args, **kw):

            if lazyArgs:

                argNames, defaults = inspect.getargspec(func)[::3]
                argValues = list(args) + [
                    kw[y] if y in kw else defaults[x]
                    for x,y in enumerate(argNames[len(args):])
                ]

                oldGlobals = {}
                for n,v in zip(argNames, argValues):

                    try:
                        oldGlobals[n] = globals()[n]
                    except:
                        oldGlobals[n] = None

                    if v not in lazyArgs:
                        globals()[n] = v
                    else:
                        globals()[n] = kw[n] = eval(v)

                for o,v in oldGlobals.items(): globals()[o] = v

            return func(*args, **kw)

        return functionWrapper

    def __call__(self, x):
        self.lazyArgs.append(x)
        return x

使用

d = defaultArguments()

@d.initialize
def function1(a, start=d('a[-1]'), end=d('len(a)-1')):
    print a, start, end


function1([1,2,8])
>>> [1, 2, 8] 8 2

function1([1,2,8,10], end=1)
>>> [1, 2, 8, 10] 10 1


@d.initialize
def function2(a, b, c, start=d('a*b*c'), end=d('a+b+c+start')):
    print a, start, end

function2(2,4,6)
>>> 2 48 60
# Notice that `end` does take the calculated value of `start` into 
# account. The logic here is based on what you'd expect to happen
# with normal assignment if the names were each assigned a value 
# sequentially: a is evaluated, then b, then c, etc...

我确实为这样做感到内疚,尤其是我使用全局和其他作弊的方式。但是,我认为它可以按照您的要求运作。

不幸的是,你必须编写额外的东西(使用装饰器并且必须在d('')中包装关键字值),但这是不可避免的,因为Python本身不支持这种情况。

编辑:

我对语法的含糖部分进行了一些研究。将其缩短为简单的装饰器功能。

def initArgs(func):

    @wraps(func)
    def functionWrapper(*args, **kw):

        argNames, defaults = inspect.getargspec(func)[::3]

        for k in kw:
            for i in argNames:
                if k != i and ('_' + k) == i:
                    kw['_' + k] = kw[k]
                    del kw[k]

        argValues = list(args) + [
            kw[y] if y in kw else defaults[x]
            for x,y in enumerate(argNames[len(args):])
        ]

        oldGlobals = {}
        for n,v in zip(argNames, argValues):

            try:
                oldGlobals[n] = globals()[n]
            except:
                oldGlobals[n] = None

            if not n.startswith('_') or n in kw:
                globals()[n] = v
            else:
                globals()[n] = kw[n] = eval(v)

        for o,v in oldGlobals.items(): globals()[o] = v

        return func(*args, **kw)

    return functionWrapper

使用它:

# When using initArgs, the strings associated with the keyword arguments will
# get eval'd only if the name is preceded with an underscore(`_`). It's a 
# bit strange and unpythonic for part of a name to have side effects, but then
# again name mangling works with double underscores (`__`) before methods.

# Example: 

@initArgs
def function1(a, _start='a[-1]', _end='len(a)-1'):
    print a, _start, _end

function1([1,2,8,10])
>>> [1, 2, 8, 10] 10 3

# Removing underscore (`_`) from start
@initArgs
def function2(a, start='a[-1]', _end='len(a)-1'):
    print a, start, _end

function1([1,2,8,10])
>>> [1, 2, 8, 10] 'a[-1]' 3

# Outputs a string normally.

在调用者的框架中,参数startend可以使用或不使用下划线,因此稍后在函数定义中更改其名称不会影响调用者。唯一的例外是函数本身,删除下划线(_)需要在其他地方做同样的事情。