<p>{a1}是处理这个问题的快速而正确的方法。不过,这需要解释为什么这是Python中的最佳实践</p>
<p>您的原始函数有以下<a href="https://en.wikipedia.org/wiki/Type_signature" rel="nofollow noreferrer">type signature</a>:</p>
<pre><code>f(x: T) -> None
</code></pre>
<p>(注意,这是一个类型签名,而不是带有类型提示的函数定义,因此缺少<code>def</code>)</p>
<p><code>T</code>这里是<code>file_i</code>的类型。(虽然OP不清楚这是什么类型,但我们可以通过简单地使用<code>T</code>作为任何类型的代理来满足自己。)函数<code>f</code>的调用站点将具有以下内容:</p>
<pre><code>t = T() # t is created
# ... other code
f(t)
</code></pre>
<p>问题围绕如何在呼叫站点执行此操作:</p>
<pre><code>f() # No argument provided.
</code></pre>
<p>为此,新函数签名更改为:</p>
<pre><code>f(x: Option[T]) -> None
</code></pre>
<p>在Python中,这是通过<a href="https://docs.python.org/3/tutorial/controlflow.html#default-argument-values" rel="nofollow noreferrer">^{<cd6>}</a>处理的。因此,我们可以这样说,为<code>f</code>提供一个默认参数:</p>
<pre><code>t = T()
def f(x: T = t) -> None:
# ... our function
</code></pre>
<p>当程序运行时,在执行<code>def f...</code>并且函数<code>f</code>已“定义”的点上,<code>t</code>已解析为一个具体值,因此这是可行的。不过OP有一个更微妙的问题——如果我们希望<code>t</code>的值在运行时</em>是动态的<em>,该怎么办?这意味着当程序到达调用站点时(调用<code>f()</code>),它<em>然后</em>解析<code>t</code>的值</p>
<p>“自然”的常识尝试是这样的:</p>
<pre><code>def h() -> T:
t = T() # or however you want to dynamically create this.
def f(x: T = h()) -> None: # Instead of a concrete value, call `h()`!
# ... our code
f() # The call site, which relies on `h()` to fill in `x`.
</code></pre>
<p>不幸的是,<em>不起作用,因为Python解析<code>f</code>的定义时会发生什么。它看到它需要为<code>x</code>分配一个默认值,为了获得该值,它调用<code>h()</code>,该函数返回一个值。这是<a href="https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments" rel="nofollow noreferrer">mutable default arguments</a>附近常见的“gotcha”的一个变体</p>
<p>那么,如何在运行时动态获取<code>x</code>的值呢?这是问题的关键。有一些选择。通常的最佳做法是分配一个所谓的<a href="https://www.python.org/dev/peps/pep-0661/" rel="nofollow noreferrer">'sentinel value'</a>。(旁白:<code>None</code>是一个常见的哨兵值,但也有一个缺点,即通常是一个完全有效的实际值。)哨兵说“我们对此没有价值,因此采取相应的行动”</p>
<p>然后,在函数中,我们可以指定一个实际值。那是什么样子的?我们将使用<code>None</code>作为我们的哨兵</p>
<pre><code>def h() -> T:
t = T() # or however you want to dynamically create this.
def f(x: T = None) -> None: # If no value is provided, use the Sentinel.
x = x if x is not None else h()
# ... our code
f() # The call site, which relies on `h()` to fill in `x`.
</code></pre>
<p>这管用!与公认的答案相同,并且符合您通常认为的最佳实践。这很清楚,不需要对以原始方式调用它的任何调用站点进行任何更改</p>
<p>在默认值本身中定义<code>h</code>怎么样?我们不能在那里传递一个函数吗?第一个通过的答案是“是”。让我们看看它是如何工作的:</p>
<pre><code>def f(x: T = h) -> None:
x = x if x is not None else h()
# ... our code
</code></pre>
<p>这是因为<code>h</code>具有类型<code>Callable[[], T]</code>,这意味着一旦调用它,它将返回类型为<code>T</code>的值。我们没有使用<code>None</code>作为我们的哨兵类型,而是使用<code>h</code>作为我们的哨兵类型。它不会与过早定义相冲突,因为<code>h</code>只在</em>函数内部调用<em>,每次函数运行时,而不是在定义函数时只运行一次</p>
<p>关于编译的高级旁白:Python将在编译或执行函数中的代码之前,运行代码并建立所有函数、类等。因此,如果函数签名(即<code>def f(x: = h):</code>中有一个变量(<code>h</code>),它将在将该函数存储为可在其他地方调用的函数之前解析该变量。但是,在调用函数体之前,它将<em>不</em>评估函数体。这就是为什么上面的小节有效,而(<code>def f(x: = h())</code>)不起作用</p>
<p>这可能有一个可取的缺陷,我们可以在新函数签名中看到:</p>
<pre><code>f(x: Union[T, Callable[[], T]]) -> None
</code></pre>
<p>这意味着在呼叫站点,我可以执行以下任一操作:</p>
<pre><code>f(t) # the original way
f() # use the default value
f(g) # !!!
</code></pre>
<p>什么是<code>g</code>?嗯,<code>g</code>是任何类型为<code>Callable[[], ?]</code>的已定义函数。只要<code>g</code>不带任何参数,我们的函数<code>f</code>将执行它并返回一个值。尽管返回值(^{<cd>}),但我们不能保证36>})的类型为<code>T</code>。这种形式允许调用站点传递它自己的函数来确定该值-也许这是更好的,因为您的特定用例!也许这很危险。这是根据具体情况决定的</p>
<p>请注意,这是一个容易犯的错误:</p>
<pre><code>def f(x: T = h) -> None:
x = x() # location B (see below)
# ... our code
</code></pre>
<p>因为这会将我们的类型签名更改为:</p>
<pre><code>f(x: Callable[[], T]) -> None
</code></pre>
<p>这是不同的,因为在我们的呼叫站点发生了什么:</p>
<pre><code>f(t) # original way, now can fail because `t` is not necessarily a `Callable` and location B will break.
f() # works
f(g) # also works
</code></pre>
<p>所有这些都是说,根据公认的答案,最简单和最好的处理方法是使用哨兵</p>
<p><strong>脚注</strong></p>
<ol>
<li><p>我忽略了OP,并接受了答案中使用的<code>global</code>。为什么这是一种不好的做法是<a href="https://www.cs.usfca.edu/%7Ewolber/courses/110/lectures/globals" rel="nofollow noreferrer">answered elsewhere</a></p>
</li>
<li><p>我们可以使用<code>None</code>以外的东西作为我们的哨兵,如果我们希望<code>None</code>也成为我们的呼叫站点可以传递和期望使用的东西</p>
</li>
</ol>
<p>例如:</p>
<pre><code>class Sentinel:
pass
UNDEFINED = Sentinel()
def f(x: T = UNDEFINED) -> None:
x = h() if isinstance(x, Sentinel) else x # or several possible variations.
</code></pre>