弃用#

sktime 旨在对用户稳定且可靠。我们确保这一点的高层政策是:

sktime 不应在没有提前至少一个(MINOR)发布周期给出明确且可操作的警告的情况下破坏用户代码。

这里,“中断”明确包括抽象逻辑的改变,例如所使用的算法,而不仅仅是导致异常或性能下降的改变。

例如,如果用户有代码

from sktime.forecasting.foo import BarForecaster

bar = BarForecaster(42, x=43)
bar.fit(y_train, fh=[1, 2, 3])
y_pred = bar.predict()

然后在没有警告的情况下,sktime 的发布不应该改变:

  • 导入 BarForecaster 的位置

  • BarForecaster 的参数签名,包括名称、顺序和参数的默认值

  • BarForecaster 为给定参数执行的抽象算法

可以不事先通知就进行的更改:

  • 在参数列表的末尾添加更多参数,并设置一个默认值以保留先前的行为,只要新参数有良好的文档说明。

  • 只要公共API保持不变,内部代码的纯重构

  • 在不改变抽象算法的情况下更改实现,例如出于性能原因

本文档中概述的弃用政策提供了如何以用户友好且可靠的方式进行需要变更或弃用处理的变化的详细信息。

它伴随着开发者的公式化模式,带有示例,以及发布管理者的流程,以使政策易于遵循。

弃用政策#

sktime 发布 遵循 语义化版本 。一个发布版本号表示 <主要>.<次要>.<补丁> 版本。

我们当前的弃用政策如下:

  • 所有对公共接口的破坏性(非向下兼容)更改必须伴随弃用声明。例如:对现有参数默认值的更改、参数的移除。非示例:带有默认值的新参数,该默认值导致先前的行为。

  • 此类更改或删除仅在 MINOR 或 MAJOR 版本中发生,而不是在 PATCH 版本中。

  • 弃用警告必须在更改或移除之前至少包含一个完整的MINOR版本周期。因此,通常情况下,更改或移除发生在*第二个*下一个MINOR版本中。

示例时间线:

1. developer A resolves, at current state v0.9.3, to remove functionality X at some point in the near future.

2. therefore, by the above, we should introduce a deprecation message, visible from next release (e.g., v0.9.4), which says that functionality will be removed at v0.11.0

3. developer A makes a pull request to remove functionality X which includes that deprecation warning. The pull request is reviewed by core developers, with the suggestion by developer A accepted or rejected.

4. If accepted and merged before v0.10.0 release, the PR goes in the next release, with a deprecation note in the release notes. If PR acceptance takes until after v0.10.0 but before v0.11.0, the planned removal moves to v0.12.0 and the warning needs to be updated.

5. an additional PR to remove deprecation warning and functionality X is prepared by developer A, for v0.12.0 but not merged

6. a release manager merges the PR in part 5 as part of the release v0.12.0, effecting the removal. Release notes of v0.12.0 includes a removal note.

弃用和变更流程#

一般弃用/变更过程包括两个部分:

  • 开发者对弃用/更改的调度

  • deprecation/change actions carried out by a release manager

开发者侧的过程发生在开发者提出弃用的PR中,具体如下:

  • 引发警告。 对于所有已弃用的功能,如果更改计划在接下来的两个MINOR版本周期内进行,我们将引发 DeprecationWarning 。否则, FutureWarning 也是可以接受的。

  • 警告应具有指导性。 警告信息应提供功能将发生变化的版本号,描述新的使用方法以及下游代码中的任何过渡操作,并明确说明预期变化的时限(指定版本)。

  • 文档字符串应更新以反映弃用情况。 文档字符串应更新以反映弃用/更改。这通常包括弃用时间线、弃用前/后的功能。

  • 在代码中为发布管理员添加一个TODO注释。 为所有应删除或更改的代码片段添加一个TODO注释,例如:TODO: 在v0.11.0中删除。TODO注释应详细描述所有操作(例如,删除参数、删除函数或代码块)。如果需要在多个地方应用更改,请放置多个TODO注释。确保TODO操作的结果经过测试,并且在发布管理员执行操作时不会导致测试中断。最好附带一个准备好的PR,发布管理员只需合并即可。

  • 像所有技术决策一样,弃用/更改首先在PR中提出,并需要由其他开发人员审查。

发布经理的流程在每次发布时都会进行,具体如下:

  • 总结变更日志中的任何计划弃用和更改。:一旦计划了弃用/更改,就应该在变更日志的“弃用和更改”部分中宣布,并附上确切的版本时间线,以及用户或第三方扩展维护者需要执行的任何操作(使用和扩展合同)。

  • 执行弃用和变更操作。 作为每个MINOR或MAJOR版本发布过程的一部分,发布管理员会搜索所有即将被移除的已弃用功能,这些功能将通过搜索TODO注释来移除。这些操作将按照描述进行。如果操作导致CI失败,发布管理员应打开一个问题并联系开发者以迅速解决,如果这会不必要地延迟发布过程,可能将操作移至下一个发布周期。

  • 总结变更日志中的所有已执行的弃用和更改。: 所有已执行的弃用和更改应在变更日志的“弃用和更改”部分进行总结。

特殊弃用#

本节概述了一些高级情况的弃用过程。

弃用和参数变更#

以下是函数或类(例如,估计器)参数弃用或更改的常见情况:

  • 更改参数的默认值

  • 重命名一个参数

  • 添加一个带有默认值的参数,该参数改变了之前的行为

  • 改变参数的顺序

  • 移除一个参数

在所有情况下,都需要确保:

  • 在用户逻辑可能改变的情况下,会引发警告

  • 警告信息包含了一个完整的代码修改方案,以保留当前行为,或改为替代行为。

  • 给予足够的通知,即,警告信息在更改实施之前至少存在一个 MINOR 版本周期

  • “todo” 注释留给发布管理员来执行更改,并且最好提供一个准备合并的更改分支/PR,以便在计划的更改版本中合并。

如果没有工作的用户逻辑会改变,则不需要这样的警告,这种情况是:

  • 在参数列表的末尾,添加了一个带有默认值的参数,以保留先前的行为。

  • 在非默认情况下总是会引发意外异常的地方,参数被移除了。

上述个别案例的配方如下。

这些案例中的一些完整示例在本文档的最后一部分给出,即“示例以说明配方”。

更改参数的默认值#

要更改参数的默认值,请按照实现更改的拉取请求中的步骤1-3进行操作。

1. at current version, change the default value to "changing_value". Internally, add logic that overrides the value of the parameter with the old default value, if the parameter is set to "changing_value". If the parameter is an __init__ parameter of an estimator class, the value cannot be directly overridden, but this needs to be done in a private parameter copy, since all __init__ parameters must be written to self unchanged. I.e., write the parameter to self._<param_name> unchanged, and add logic that overrides the value of self._<param_name> with the old default, and ensure to use self._<param_name> in the rest of the code instead of self.<param_name>.

2. add a warning, using sktime.utils.warnings.warn, if the parameter is called with a non-default. This warning should always include the name of the estimator/function, the version of change, and a clear instruction on how to change the code to retain prior behaviour. E.g., "Parameter <param_name> of <estimator_name> will change default value from <old_value> to <new_value> in sktime version <version_number>. To retain prior behaviour, set <param_name> to <old_value> explicitly".

3. add a TODO comment to the code, to remove the warning and change the default value, in the next MINOR version cycle. E.g., add the comment # TODO <version_number>: change default of <param_name> to <new_value>, update docstring, and remove warning, at the top of the function or class where the parameter is defined.

4. the release manager will carry out the TODO action in the next MINOR version cycle, and remove the TODO comment. Optimally, a change branch is provided that the release manager can merge, and its PR ID is mentioned in the todo.

重命名参数#

要重命名参数,请按照拉取请求中实施更改的步骤1-6进行操作。

1. at current version, add a parameter with the new name at the end of the list of parameters, with the same default value as the old parameter. Do not remove the old parameter.

2. change the value of the old parameter to the string "deprecated". Change all code in the function or class that uses the old parameter to use the new parameter instead. This can be done by a bulk-replace.

3. at the start of the function or class init, add logic that overrides the value of the new parameter with the value of the old parameter, if the old parameter is not "deprecated". If the parameter is an __init__ parameter of an estimator class, the value cannot be directly overridden, but this needs to be done in a private parameter, since all __init__ parameters must be written to self unchanged.

4. add a warning, using sktime.utils.warnings.warn, if the old parameter is called with a non-default. This warning should always include the name of the estimator/function, the version of change, and a clear instruction on how to change the code to retain prior behaviour. E.g., "Parameter <param_name> of <estimator_name> will be renamed from <old_name> to <new_name> in sktime version <version_number>. To retain prior behaviour, use a kwargs call of <new_name> instead of <old_name>".

  1. 更新函数或类的文档字符串,使其仅引用新参数。

6. add a TODO comment to the code, to remove the warning and change the default value, in the next MINOR version cycle. E.g., add the comment # TODO <version_number>: change name of parameter <old_name> to <new_name>, remove old parameter at the end, and remove warning, at the top of the function or class where the parameter is defined.

  1. 发布管理员将在下一个MINOR版本周期中执行TODO操作,

并移除 TODO 注释。理想情况下,提供一个变更分支,发布管理员可以合并,并在 TODO 中提及其 PR ID。

添加一个带有默认值的参数,该参数改变了先前的行为#

这应该分两步完成:

  • 添加参数,但使用一个默认值来保留之前的行为。由于这保留了之前的行为,因此不需要弃用或更改机制。

  • 然后,按照上述步骤更改参数的默认值。

改变参数的顺序#

这种类型的更改应避免,因为它很难执行。如果可以使用上述更改模式之一,则优先选择。

要更改参数的顺序,请按照实现更改的拉取请求中的步骤1-6进行操作。

在当前版本中,将第一个参数及其后的所有参数的默认值更改为 "position_change"

2. Internally, add logic that overrides the value of the parameter with the old default value, if the parameter is set to "position_change". For __init__ parameters of an estimator class, the values cannot be directly overridden, but this needs to be done in a private parameter copy, since all __init__ parameters must be written to self unchanged. I.e., write the parameter to self._<param_name> unchanged, and add logic that overrides the value of self._<param_name> with the old default, and ensure to use self._<param_name> in the rest of the code instead of self.<param_name>.

3. add a warning, using sktime.utils.warnings.warn, if any of the position changing parameters are called with a non-default. This warning should always include the name of the estimator/function, the version of change, and a clear instruction on how to change the code to retain prior behaviour. The instruction should direct the user to use kwargs calls instead of positional calls, for all parameters that change position.

4. add a TODO comment to the code, to remove the warning and change the sequence, as well as changing default values to the old defaults, in the next MINOR version cycle. The TODO comment should contain complete lines of code. Optimally, a change branch is provided that the release manager can merge, and its PR ID is mentioned in the todo.

移除一个参数#

如果参数被移除的位置不在参数列表的末尾,应首先将其移到参数列表的末尾。

要移除一个参数,请按照“更改默认值”的步骤进行,但使用不同的警告信息,即该参数将被移除。

错误信息应包含是否可以保留先前行为的详细信息,如果可以,在哪些情况下,以及如果可以,如何保留。

弃用标签#

要弃用标签,需要确保在使用标签时会引发警告。有两种常见情况:删除标签或重命名标签。

对于任一情景,都可以使用助手类 TagAliaserMixin``(在 ``sktime.base 中)。

要弃用标签,请将 TagAliaserMixin 添加到 BaseEstimator 中,或另一个 BaseObject 的后代。建议选择完全覆盖弃用标签使用的最年轻后代。TagAliaserMixin 覆盖了标签系列的方法,因此应该是第一个继承的类(或在多个mixin的情况下,早于 BaseObject)。

TagAliaserMixin 中的 alias_dict 包含一个已弃用标签的字典:对于删除,添加一个条目 "old_tag_name": ""。对于重命名,添加一个条目 "old_tag_name": "new_tag_name"deprecate_dict 包含重命名或删除的版本号,并且应具有与 alias_dict 相同的键。

TagAliaserMixin 类将在弃用期间确保新标签别名旧标签,反之亦然。每当访问已弃用的标签时,都会引发信息性警告。

在弃用期后移除/重命名标签时,确保从 TagAliaserMixin 类的字典中移除已移除的标签。如果没有标签再被弃用(例如,所有弃用标签都已移除/重命名),确保从 BaseObjectBaseEstimator 的父类中移除这个类。

示例以说明配方#

以下是上述一些情况的示例模板。这些示例是为具有 fit / predict 方法的类进行的,但相同的原则也适用于函数,或其他具有不同API的类。

更改参数的默认值#

任何更改前的代码#

class EstimatorName:
    """The old docstring.

    Parameters
    ----------
    parameter : str, default="old_default"
        The parameter description.
    """
    def __init__(self, parameter="old_default"):
        self.parameter = parameter

    def fit(self, X, y):
        parameter = self.parameter
        # Fit the model using parameter
        fitting_logic(parameter)
        return self

    def predict(self, X):
        parameter = self.parameter
        # Predict using the fitted model
        y_pred = prediction_logic(parameter)
        return y_pred

步骤1:在弃用期间#

这一步由开发者在PR中完成。可选地,开发者可以为步骤2准备一个PR,供发布管理员合并。

from sktime.utils.warnings import warn

# TODO (release <MAJOR>.<MINOR>.0)
# change the default of 'parameter' to <new_value>
# update the docstring for parameter
class EstimatorName:
    """The old docstring with deprecation info.

    Parameters
    ----------
    parameter : str, default="old_default"
        The parameter description.
        Default value of parameter will change to <new_value>
        in version '<MAJOR>.<MINOR>.0'.
    """
    def __init__(self, parameter="changing_value"):
        self.parameter = parameter
        # TODO (release <MAJOR>.<MINOR>.0)
        # change the default of 'parameter' to <new_value>
        # remove the following 'if' check
        # de-indent the following 'else' check
        if parameter == "changing_value":
            warn(
                "in `EstimatorName`, the default value of parameter 'parameter'"
                " will change to <new_value> in version '<MAJOR>.<MINOR>.0'. "
                "To keep current behaviour and to silence this warning, "
                "set 'parameter' to 'old' explicitly.",
                category=DeprecationWarning,
                obj=self,
            )
            self._parameter = "old_default"
        else:
            self._parameter = parameter

    def fit(self, X, y):
        parameter = self._parameter
        # Fit the model using parameter
        fitting_logic(parameter)
        return self

    def predict(self, X):
        parameter = self._parameter
        # Predict using the fitted model
        y_pred = prediction_logic(parameter)
        return y_pred

步骤 2:在弃用期之后#

这一步由发布经理完成,可以通过合并一个准备好的PR,或者执行TODO操作来完成。

class EstimatorName:
    """The final docstring.

    Parameters
    ----------
    parameter : str, default="new_default"
        The parameter description.
    """
    def __init__(self, parameter="new_default"):
        self.parameter = parameter
        self._parameter = parameter

    def fit(self, X, y):
        parameter = self._parameter
        # Fit the model using parameter
        fitting_logic(parameter)
        return self

    def predict(self, X):
        parameter = self._parameter
        # Predict using the fitted model
        y_pred = prediction_logic(parameter)
        return y_pred

可选地,如果 self._parameter 在代码的其他地方没有使用,可以将其移除,并用 self.parameter 替换。

重命名参数#

任何更改前的代码#

class EstimatorName:
    """The old docstring.

    Parameters
    ----------
    old_parameter : str, default="default"
        The parameter description.
    """

    def __init__(self, old_parameter="default"):
        self.old_parameter = old_parameter

    def fit(self, X, y):
        old_parameter = self.old_parameter
        # Fit the model using parameter
        fitting_logic(old_parameter)
        return self

    def predict(self, X):
        old_parameter = self.old_parameter
        # Predict using the fitted model
        y_pred = prediction_logic(old_parameter)
        return y_pred

步骤1:在弃用期间#

这一步由开发者在PR中完成。可选地,开发者可以为步骤2准备一个PR,供发布管理员合并。

from sktime.utils.warnings import warn

 class EstimatorName:
     """The old docstring, but already points to the new name.

     The docstring should replace 'old_parameter' with 'new_parameter',
     and no longer mention 'old_parameter'.

     Parameters
     ----------
     new_parameter : str, default="default"
         The parameter description.
     """
     def __init__(self, old_parameter="deprecated", new_parameter="default"):
         # IMPORTANT: both params need to be written to self during change period
         self.new_parameter = new_parameter
         self.old_parameter = old_parameter
         # TODO (release <MAJOR>.<MINOR>.0)
         # remove the 'old_parameter' argument from '__init__' signature
         # move 'new_parameter' to the position of 'old_parameter'
         # remove the following 'if' check
         # de-indent the following 'else' check
         if old_parameter != "deprecated":
             warn(
                 "in `EstimatorName`, parameter 'old_parameter'"
                 " will be renamed to new_parameter in version '<MAJOR>.<MINOR>.0'. "
                 "To keep current behaviour and to silence this warning, "
                 "use 'new_parameter' instead of 'old_parameter', "
                 "set new_parameter explicitly via kwarg, and do not set"
                 " old_parameter.",
                 category=DeprecationWarning,
                 obj=self,
             )
             self._parameter = old_parameter
         else:
             self._parameter = new_parameter

    def fit(self, X, y):
         old_parameter = self._parameter
         # Fit the model using parameter
         fitting_logic(old_parameter)
         return self

    def predict(self, X):
         old_parameter = self._parameter
         # Predict using the fitted model
         y_pred = prediction_logic(old_parameter)
         return y_pred

步骤 2:在弃用期之后#

这一步由发布经理完成,可以通过合并一个准备好的PR,或者执行TODO操作来完成。

class EstimatorName:
    """Same as in step 2, no change necessary.

    Parameters
    ----------
    new_parameter : str, default="default"
        The parameter description.
    """
   def __init__(self, new_parameter="default"):
       self.new_parameter = new_parameter
       self._parameter = new_parameter

   def fit(self, X, y):
        old_parameter = self._parameter
        # Fit the model using parameter
        fitting_logic(old_parameter)
        return self

   def predict(self, X):
        old_parameter = self._parameter
        # Predict using the fitted model
        y_pred = prediction_logic(old_parameter)
        return y_pred

可选地,如果 self._parameter 在代码的其他地方没有使用,可以移除它,并替换为 self.new_parameter