Movatterモバイル変換


[0]ホーム

URL:


Following system colour schemeSelected dark colour schemeSelected light colour scheme

Python Enhancement Proposals

PEP 702 – Marking deprecations using the type system

PEP 702 – Marking deprecations using the type system

Author:
Jelle Zijlstra <jelle.zijlstra at gmail.com>
Discussions-To:
Discourse thread
Status:
Final
Type:
Standards Track
Topic:
Typing
Created:
30-Dec-2022
Python-Version:
3.13
Post-History:
01-Jan-2023,22-Jan-2023
Resolution:
07-Nov-2023

Table of Contents

Important

This PEP is a historical document: see@deprecated and@warnings.deprecated for up-to-date specs and documentation. Canonical typing specs are maintained at thetyping specs site; runtime typing behaviour is described in the CPython documentation.

×

See thetyping specification update process for how to propose changes to the typing spec.

Abstract

This PEP adds an@warnings.deprecated() decorator that marks a class or functionas deprecated, enabling static checkers to warn when it is used. By default, thisdecorator will also raise a runtimeDeprecationWarning.

Motivation

As software evolves, new functionality is added and old functionality becomesobsolete. Library developers want to work towards removing obsolete code whilegiving their users time to migrate to new APIs. Python provides a mechanism forachieving these goals: theDeprecationWarning warning class, which isused to show warnings when deprecated functionality is used. This mechanism iswidely used: as of the writing of this PEP, the CPython main branch containsabout 150 distinct code paths that raiseDeprecationWarning. Manythird-party libraries also useDeprecationWarning to mark deprecations.In thetop 5000 PyPI packages,there are:

  • 1911 matches for the regexwarnings\.warn.*\bDeprecationWarning\b,indicating use ofDeprecationWarning (not including cases where thewarning is split over multiple lines);
  • 1661 matches for the regex^\s*@deprecated, indicating use of some sort ofdeprecation decorator.

However, the current mechanism is often insufficient to ensure that usersof deprecated functionality update their code in time. For example, theremoval of various long-deprecatedunittest features had to berevertedfrom Python 3.11 to give users more time to update their code.Users may run their test suite with warnings disabled for practical reasons,or deprecations may be triggered in code paths that are not covered by tests.

Providing more ways for users to find out about deprecated functionalitycan speed up the migration process. This PEP proposes to leverage static typecheckers to communicate deprecations to users. Such checkers have a thoroughsemantic understanding of user code, enabling them to detect and reportdeprecations that a singlegrep invocation could not find. In addition, many typecheckers integrate with IDEs, enabling users to see deprecation warningsright in their editors.

Rationale

At first glance, deprecations may not seem like a topic that type checkers shouldtouch. After all, type checkers are concerned with checking whether code willwork as is, not with potential future changes. However, the analysis that typecheckers perform on code to find type errors is very similar to the analysisthat would be needed to detect usage of many deprecations. Therefore, typecheckers are well placed to find and report deprecations.

Other languages already have similar functionality:

  • GCC supports adeprecatedattributeon function declarations. This powers CPython’sPy_DEPRECATED macro.
  • GraphQLsupportsmarking fields as@deprecated.
  • KotlinsupportsaDeprecated annotation.
  • Scalasupportsan@deprecated annotation.
  • Swiftsupportsusing the@available attribute to mark APIs as deprecated.
  • TypeScriptusesthe@deprecated JSDoc tag to issue a hint marking use ofdeprecated functionality.

Several users have requested support for such a feature:

There are similar existing third-party tools:

  • Deprecated provides a decorator tomark classes, functions, or methods as deprecated. Access to decorated objectsraises a runtime warning, but is not detected by type checkers.
  • flake8-deprecated is a linterplugin that warns about use of deprecated features. However, it is limited toa short, hard-coded list of deprecations.

Specification

A new decorator@deprecated() is added to thewarnings module. Thisdecorator can be used on a class, function or method to mark it as deprecated.This includestyping.TypedDict andtyping.NamedTuple definitions.With overloaded functions, the decorator may be applied to individual overloads,indicating that the particular overload is deprecated. The decorator may also beapplied to the overload implementation function, indicating that the entire functionis deprecated.

The decorator takes the following arguments:

  • A required positional-only argument representing the deprecation message.
  • Two keyword-only arguments,category andstacklevel, controllingruntime behavior (see under “Runtime behavior” below).

The positional-only argument is of typestr and contains a message that shouldbe shown by the type checker when it encounters a usage of the decorated object.Tools may clean up the deprecation message for display, for exampleby usinginspect.cleandoc() or equivalent logic.The message must be a string literal.The content of deprecation messages is up to the user, but it may include the versionin which the deprecated object is to be removed, and information about suggestedreplacement APIs.

Type checkers should produce a diagnostic whenever they encounter a usage of anobject marked as deprecated. For deprecated overloads, this includes all callsthat resolve to the deprecated overload.For deprecated classes and functions, this includes:

  • References through module, class, or instance attributes (module.deprecated_object,module.SomeClass.deprecated_method,module.SomeClass().deprecated_method)
  • Any usage of deprecated objects in their defining module(x=deprecated_object() inmodule.py)
  • Ifimport* is used, usage of deprecated objects from themodule (frommoduleimport*;x=deprecated_object())
  • from imports (frommoduleimportdeprecated_object)
  • Any syntax that indirectly triggers a call to the function. For example,if the__add__ method of a classC is deprecated, thenthe codeC()+C() should trigger a diagnostic. Similarly, if thesetter of a property is marked deprecated, attempts to set the propertyshould trigger a diagnostic.

If a method is marked with thetyping.override() decorator fromPEP 698and the base class method it overrides is deprecated, the type checker shouldproduce a diagnostic.

There are additional scenarios where deprecations could come into play.For example, an object may implement atyping.Protocol, but oneof the methods required for protocol compliance is deprecated.As scenarios such as this one appear complex and relatively unlikely to come up in practice,this PEP does not mandate that type checkers detect them.

Example

As an example, consider this library stub namedlibrary.pyi:

fromwarningsimportdeprecated@deprecated("Use Spam instead")classHam:...@deprecated("It is pining for the fiords")defnorwegian_blue(x:int)->int:...@overload@deprecated("Only str will be allowed")deffoo(x:int)->str:...@overloaddeffoo(x:str)->str:...classSpam:@deprecated("There is enough spam in the world")def__add__(self,other:object)->object:...@property@deprecated("All spam will be equally greasy")defgreasy(self)->float:...@propertydefshape(self)->str:...@shape.setter@deprecated("Shapes are becoming immutable")defshape(self,value:str)->None:...

Here is how type checkers should handle usage of this library:

fromlibraryimportHam# error: Use of deprecated class Ham. Use Spam instead.importlibrarylibrary.norwegian_blue(1)# error: Use of deprecated function norwegian_blue. It is pining for the fiords.map(library.norwegian_blue,[1,2,3])# error: Use of deprecated function norwegian_blue. It is pining for the fiords.library.foo(1)# error: Use of deprecated overload for foo. Only str will be allowed.library.foo("x")# no errorham=Ham()# no error (already reported above)spam=library.Spam()spam+1# error: Use of deprecated method Spam.__add__. There is enough spam in the world.spam.greasy# error: Use of deprecated property Spam.greasy. All spam will be equally greasy.spam.shape# no errorspam.shape="cube"# error: Use of deprecated property setter Spam.shape. Shapes are becoming immutable.

The exact wording of the diagnostics is up to the type checker and is not partof the specification.

Runtime behavior

In addition to the positional-onlymessage argument,the@deprecated decorator takes two keyword-only arguments:

  • category: A warning class. Defaults toDeprecationWarning. If thisis set toNone, no warning is issued at runtime and the decorator returnsthe original object, except for setting the__deprecated__ attribute (see below).
  • stacklevel: The number of stack frames to skip when issuing the warning.Defaults to 1, indicating that the warning should be issued at the site where thedeprecated object is called. Internally, the implementation will add the number ofstack frames it uses in wrapper code.

If the decorated object is a class, the decorator wraps the__new__ methodsuch that instantiating the class issues a warning. If the decorated object is acallable, the decorator returns a new callable that wraps the original callable butraises a warning when called. Otherwise, the decorator raises aTypeError(unlesscategory=None is passed).

There are several scenarios where use of the decorated object cannot issue a warning,including overloads,Protocol classes, and abstract methods. Type checkers may show awarning if@deprecated is used withoutcategory=None in these cases.

To accommodate runtime introspection, the decorator sets an attribute__deprecated__ on the object it is passed, as well as on the wrappercallables it generates for deprecated classes and functions.The value of the attribute is the message passed to the decorator.Decorating objects that do not allow setting this attribute is not supported.

If aProtocol with the@runtime_checkable decorator is marked as deprecated,the__deprecated__ attribute should not be considered a member of the protocol,so its presence should not affectisinstance checks.

For compatibility withtyping.get_overloads(), the@deprecateddecorator should be placed after the@overload decorator.

Type checker behavior

This PEP does not specify exactly how type checkers should present deprecationdiagnostics to their users. However, some users (e.g., application developerstargeting only a specific version of Python) may not care about deprecations,while others (e.g., library developers who want their library to remaincompatible with future versions of Python) would want to catch any use ofdeprecated functionality in their CI pipeline. Therefore, it is recommendedthat type checkers provide configuration options that cover both use cases.As with any other type checker error, it is also possible to ignore deprecationsusing#type:ignore comments.

Deprecation policy

We propose that CPython’s deprecation policy (PEP 387) is updated to require that new deprecationsuse the functionality in this PEP to alert usersabout the deprecation, if possible. Concretely, this means that newdeprecations should be accompanied by a change to thetypeshed repo toadd the@deprecated decorator in the appropriate place.This requirement does not apply to deprecations that cannot be expressedusing this PEP’s functionality.

Backwards compatibility

Creating a new decorator poses no backwards compatibility concerns.As with all new typing functionality, the@deprecated decoratorwill be added to thetyping_extensions module, enabling its usein older versions of Python.

How to teach this

For users who encounter deprecation warnings in their IDE or typechecker output, the messages they receive should be clear and self-explanatory.Usage of the@deprecated decorator will be an advanced featuremostly relevant to library authors. The decorator should be mentionedin relevant documentation (e.g.,PEP 387 and theDeprecationWarningdocumentation) as an additional way to mark deprecated functionality.

Reference implementation

A runtime implementation of the@deprecated decorator isavailable in thetyping-extensionslibrary since version 4.5.0.Thepyanalyze type checker hasprototype supportfor emitting deprecation errors, as doesPyright.

Rejected ideas

Deprecation of modules and attributes

This PEP covers deprecations of classes, functions and overloads. Thisallows type checkers to detect many but not all possible deprecations.To evaluate whether additional functionality would be worthwhile, Iexaminedall current deprecations in the CPython standard library.

I found:

  • 74 deprecations of functions, methods and classes (supported by this PEP)
  • 28 deprecations of whole modules (largely due toPEP 594)
  • 9 deprecations of function parameters (supported by this PEP throughdecorating overloads)
  • 1 deprecation of a constant
  • 38 deprecations that are not easily detectable in the type system (forexample, for callingasyncio.get_event_loop() without an activeevent loop)

Modules could be marked as deprecated by adding a__deprecated__module-level constant. However, the need for this is limited, and itis relatively easy to detect usage of deprecated modules simply bygrepping. Therefore, this PEP omits support for whole-module deprecations.As a workaround, users could mark all module-level classes and functionswith@deprecated.

For deprecating module-level constants, object attributes, and functionparameters, aDeprecated[type,message] type modifier, similar toAnnotated could be added. However, this would create a new placein the type system where strings are just strings, not forward references,complicating the implementation of type checkers. In addition, my datashow that this feature is not commonly needed.

Features for deprecating more kinds of objects could be added in a futurePEP.

Placing the decorator in thetyping module

An earlier version of this PEP proposed placing the@deprecateddecorator in thetyping module. However, there was feedbackthat it would be unexpected for a decorator in thetyping moduleto have runtime behavior. Therefore, the PEP now proposes adding thedecorator thewarnings module instead.

Acknowledgments

A call with the typing-sig meetup group led to useful feedback on thisproposal.

Copyright

This document is placed in the public domain or under theCC0-1.0-Universal license, whichever is more permissive.


Source:https://github.com/python/peps/blob/main/peps/pep-0702.rst

Last modified:2024-10-16 16:05:18 GMT


[8]ページ先頭

©2009-2026 Movatter.jp