我已经多次看到Python程序员(包括我自己)希望给定函数中的变量默认为另一个变量(如果没有给出该值)。
这是针对问题的三种不同解决方案设计的演练,每种解决方案都具有不断增加的复杂性和稳健性。所以,继续!
def my_fun(a, b, c=a):
return str(a) + str(b) + str(c)
在这个例子中,如果没有给出c,那么我们会在末尾追加一个str(a)。这很简单,只是一个简单的玩具示例,我不怀疑你的用例可能要复杂得多。但是,这不是语法上正确的Python,并且它不会运行,因为没有定义a
。
Traceback (most recent call last):
File "<input>", line 1, in <module>
NameError: name 'a' is not defined
但是,我经常看到接受的答案:
None
的默认值,然后检查值是否为None
。”如果这听起来像你遇到的问题,我希望我能帮忙!
上面的解决方案如下所示:
def cast_to_string_concat(a, b, c=None):
c = a if c is None else c
return str(a) + str(b) + str(c)
虽然这种方法可以解决无数的潜在问题,(也许是你的)!我想写一个函数,其中变量“c
”的可能输入确实是单例None
,所以我不得不做更多的挖掘。
为了进一步解释,使用以下变量调用该函数:
A='A'
B='B'
my_var = None
产量:
cast_to_string_concat(A, B, my_var):
>>>'ABA'
虽然用户可能期望由于他们使用三个变量调用函数,然后它应该打印三个变量,如下所示:
cast_to_string_concat(A, B, my_var):
>>> 'ABNone' # simulated and expected outcome
因此,这个实现忽略了第三个变量,即使它被声明,所以这意味着该函数不再能够确定是否定义了变量“c
”。
因此,对于我的用例,None
的默认值不会完全解决问题。
有关建议此解决方案的答案,请阅读以下内容:
但是,如果这对你不起作用,那么也许继续阅读!
上面第一个链接中的注释提到使用由_sentinel
定义的object()
,它删除了None的使用,并使用object()
私有implied将其替换为sentinel
。 (我简要介绍了附录中另一个类似(但失败)的尝试。)
_sentinel = object()
def cast_to_string_concat(a, b, c=_sentinel):
c = a if c == _sentinel else c
return str(a) + str(b) + str(c)
A='A'
B='B'
C='C'
cast_to_string_append(A,B,C)
>>> 'ABC'
cast_to_string_concat(A,B)
>>> 'ABA'
所以这非常棒!它正确处理上述边缘情况!你自己看:
A='A'
B='B'
C = None
cast_to_string_concat(A, B, C)
>>> 'ABNone'
所以,我们完成了,对吧?有没有合理的方法可能不起作用?嗯......可能不是!但我确实说这是一个由三部分组成的答案,所以一直向前! ;)
为了完整起见,让我们假设我们的程序在一个确实可能出现每种可能情况的空间中运行。 (这可能不是一个有保证的假设,但我想可以用_sentinel
的值来获得有关计算机体系结构和对象选择的实现的足够信息。所以,如果你愿意,让我们假设确实如此可能,让我们假设我们决定测试上面定义的引用_sentinel
的假设。
_sentinel = object()
def cast_to_string_concat(a, b, c=_sentinel):
c = a if c == _sentinel else c
return str(a) + str(b) + str(c)
A='A'
B='B'
S = _sentinel
cast_to_string_append(A,B,S)
>>> 'ABA'
等一下!我输入了三个参数,所以我应该看到三个参数的字符串连接在一起!
*排队进入不可预见后果的土地*
我的意思是,实际上并非。回应:“这是可以忽略不计的边缘区域!!”或其同类是完全有保证的。
那种情绪是正确的!对于这种情况(可能是大多数情况),这真的不值得担心!
但是,如果值得担心,或者你只是想要消除所有边缘情况的数学上的满足感,那么你就会意识到......
好的,经过长时间的锻炼,我们回来了!
回想一下,目标是编写一个可能具有n
输入的函数,并且只有当没有提供一个变量时 - 然后你将复制另一个位于i
的变量。
而不是默认定义变量,如果我们改变方法以允许任意数量的变量怎么办?
因此,如果您正在寻找一种不会对潜在输入做出妥协的解决方案,那么有效输入可能是None
,object()
或_sentinel
......然后(仅此时),此时,我正在考虑我的解决方案会有所帮助。这项技术的灵感来自second part of Jon Clements' answer。
我对此问题的解决方案是更改此函数的命名,并使用先前命名约定的函数包装此函数,但不使用变量,而是使用*args
。然后,您可以在本地范围内定义原始函数(使用新名称),并且只允许您想要的几种可能性。
步骤:
def cast_to_string_concat(*args):
def cast_to_string_append(*args):
def string_append(a, b, c):
# this is the original function, it is only called within the wrapper
return str(a) + str(b) + str(c)
if len(args) == 2:
# if two arguments, then set the third to be the first
return string_append(*args, args[0])
elif len(args) == 3:
# if three arguments, then call the function as written
return string_append(*args)
else:
raise Exception(f'Function: cast_to_string_append() accepts two or three arguments, and you entered {len(args)}.')
# instantiation
A='A'
B='B'
C='C'
D='D'
_sentinel = object()
S = _sentinel
N = None
""" Answer 3 Testing """
# two variables
cast_to_string_append(A,B)
>>> 'ABA'
# three variables
cast_to_string_append(A,B,C)
>>> 'ABC'
# three variables, one is _sentinel
cast_to_string_append(A,B,S)
>>>'AB<object object at 0x10c56f560>'
# three variables, one is None
cast_to_string_append(A,B,N)
>>>'ABNone'
# one variable
cast_to_string_append(A)
>>>Traceback (most recent call last):
>>> File "<input>", line 1, in <module>
>>> File "<input>", line 13, in cast_to_string_append
>>>Exception: Function: cast_to_string_append() accepts two or three arguments, and you entered 1.
# four variables
cast_to_string_append(A,B,C,D)
>>>Traceback (most recent call last):
>>> File "<input>", line 1, in <module>
>>> File "<input>", line 13, in cast_to_string_append
>>>Exception: Function: cast_to_string_append() accepts two or three arguments, and you entered 4.
# ten variables
cast_to_string_append(0,1,2,3,4,5,6,7,8,9)
>>>Traceback (most recent call last):
>>> File "<input>", line 1, in <module>
>>> File "<input>", line 13, in cast_to_string_append
>>>Exception: Function: cast_to_string_append() accepts two or three arguments, and you entered 10.
# no variables
cast_to_string_append()
>>>Traceback (most recent call last):
>>> File "<input>", line 1, in <module>
>>> File "<input>", line 13, in cast_to_string_append
>>>Exception: Function: cast_to_string_append() accepts two or three arguments, and you entered 0.
""" End Answer 3 Testing """
def cast_to_string_concat(a, b, c=None):
c = a if c is None else c
return str(a) + str(b) + str(c)
None
实际上没有通过切换到object()
,通过_sentinel
表示空参数,则使用。_sentinel = object()
def cast_to_string_concat(a, b, c=_sentinel):
c = a if c == _sentinel else c
return str(a) + str(b) + str(c)
*args
利用任意arity的包装函数寻找一般解决方案,并处理内部可接受的情况:def cast_to_string_append(*args):
def string_append(a, b, c):
# this is the original function, it is only called within the wrapper
return str(a) + str(b) + str(c)
if len(args) == 2:
# if two arguments, then set the third to be the first
return string_append(*args, args[0])
elif len(args) == 3:
# if three arguments, then call the function as written
return string_append(*args)
else:
raise Exception(f'Function: cast_to_string_append() accepts two or three arguments, and you entered {len(args)}.')
作为我最喜欢的计算机科学教授James Cain博士,他是一位表达最高安全性和完整性的人,他说,“计算是上下文[原文如此]”,所以总是使用对你有用的东西!但对我来说,我将使用选项3;)
谢谢阅读!
-Spencer
附录:接下来的三个链接建议使用一些相似的类或进口声明,但我选择不遵循这条路线。如果这看起来像你想要的那样,那就试一试吧!
有关使用课程的答案,请阅读以下内容:
练习留给读者:
偏离这种技术,你可以直接断言
c=object()
,但是,诚实地说,我没有那种方式为我工作。我的调查显示c == object()
是False
,而str(c) == str(object())
也是False
,这就是为什么我使用Martin Pieters的实现。