我在函数中python的默认值中发现了这种非常奇怪的行为,如果您能帮助我解释为什么在同一件事情上会出现两种不同的行为,我将不胜感激
我们有一个非常简单的自定义int类:
class CustomInt(object):
def __init__(self, val=0):
self._val = int(val)
def increment(self, val=1):
self._val +=val
return self._val
def __str__(self):
return str(self._val)
def __repr__(self):
return 'CustomInt(%s)' % self._val
def get(self):
return self._val
类实例化:
test = CustomInt()
然后我们定义函数接受一个getter作为默认参数
def move_selected(file_i = test.get()):
global test
test.increment()
print(file_i)
print(test)
如果我们点击move_selected()
一次,我们会得到test的本地副本(也称为文件_i),并且我们的全局变量test会被更新(我们得到0\n1
)
第二次调用move_selected()
的默认值仍然是0(我们得到0\n2
)。即使测试已经更新。如果我们显式地写move_selected(test.get())
,结果是不一样的(我们会得到1\n2
)
为什么??我们不应该将函数作为默认参数传递吗
从你的问题中,我大胆地说:
您没有传递/接受getter/函数。您正在传递/接受调用该getter的结果。下面是如何通过/接受getter。您所要做的就是将
()
向下移动,因此默认值实际上是getter函数,您可以在以后使用/调用它然后输出:
仍然不知道为什么投票被否决,因为这表明他们没有按照自己说的去做,以及如何正确地去做,从而使它起作用。它是这样做的,正如您从上面的输出也可以看到的一样try yourself online
默认值是在定义函数时计算的,而不是在调用函数时计算的。定义函数时
test.get()
的值是0,所以它就是这样如果要在每次函数运行时调用getter,可以在函数体中执行以下操作:
{a1}是处理这个问题的快速而正确的方法。不过,这需要解释为什么这是Python中的最佳实践
您的原始函数有以下type signature:
(注意,这是一个类型签名,而不是带有类型提示的函数定义,因此缺少
def
)T
这里是file_i
的类型。(虽然OP不清楚这是什么类型,但我们可以通过简单地使用T
作为任何类型的代理来满足自己。)函数f
的调用站点将具有以下内容:问题围绕如何在呼叫站点执行此操作:
为此,新函数签名更改为:
在Python中,这是通过^{} 处理的。因此,我们可以这样说,为
f
提供一个默认参数:当程序运行时,在执行
def f...
并且函数f
已“定义”的点上,t
已解析为一个具体值,因此这是可行的。不过OP有一个更微妙的问题——如果我们希望t
的值在运行时是动态的,该怎么办?这意味着当程序到达调用站点时(调用f()
),它然后解析t
的值“自然”的常识尝试是这样的:
不幸的是,不起作用,因为Python解析
f
的定义时会发生什么。它看到它需要为x
分配一个默认值,为了获得该值,它调用h()
,该函数返回一个值。这是mutable default arguments附近常见的“gotcha”的一个变体那么,如何在运行时动态获取
x
的值呢?这是问题的关键。有一些选择。通常的最佳做法是分配一个所谓的'sentinel value'。(旁白:None
是一个常见的哨兵值,但也有一个缺点,即通常是一个完全有效的实际值。)哨兵说“我们对此没有价值,因此采取相应的行动”然后,在函数中,我们可以指定一个实际值。那是什么样子的?我们将使用
None
作为我们的哨兵这管用!与公认的答案相同,并且符合您通常认为的最佳实践。这很清楚,不需要对以原始方式调用它的任何调用站点进行任何更改
在默认值本身中定义
h
怎么样?我们不能在那里传递一个函数吗?第一个通过的答案是“是”。让我们看看它是如何工作的:这是因为
h
具有类型Callable[[], T]
,这意味着一旦调用它,它将返回类型为T
的值。我们没有使用None
作为我们的哨兵类型,而是使用h
作为我们的哨兵类型。它不会与过早定义相冲突,因为h
只在函数内部调用,每次函数运行时,而不是在定义函数时只运行一次关于编译的高级旁白:Python将在编译或执行函数中的代码之前,运行代码并建立所有函数、类等。因此,如果函数签名(即
def f(x: = h):
中有一个变量(h
),它将在将该函数存储为可在其他地方调用的函数之前解析该变量。但是,在调用函数体之前,它将不评估函数体。这就是为什么上面的小节有效,而(def f(x: = h())
)不起作用这可能有一个可取的缺陷,我们可以在新函数签名中看到:
这意味着在呼叫站点,我可以执行以下任一操作:
什么是}),但我们不能保证36>})的类型为
g
?嗯,g
是任何类型为Callable[[], ?]
的已定义函数。只要g
不带任何参数,我们的函数f
将执行它并返回一个值。尽管返回值(^{T
。这种形式允许调用站点传递它自己的函数来确定该值-也许这是更好的,因为您的特定用例!也许这很危险。这是根据具体情况决定的请注意,这是一个容易犯的错误:
因为这会将我们的类型签名更改为:
这是不同的,因为在我们的呼叫站点发生了什么:
所有这些都是说,根据公认的答案,最简单和最好的处理方法是使用哨兵
脚注
我忽略了OP,并接受了答案中使用的
global
。为什么这是一种不好的做法是answered elsewhere我们可以使用
None
以外的东西作为我们的哨兵,如果我们希望None
也成为我们的呼叫站点可以传递和期望使用的东西例如:
相关问题 更多 >
编程相关推荐