Movatterモバイル変換


[0]ホーム

URL:


ContentsMenuExpandLight modeDark modeAuto light/dark, in light modeAuto light/dark, in dark modeSkip to content
mypy 1.16.1 documentation
Logo
mypy 1.16.1 documentation

First steps

Type system reference

Configuring and running mypy

Miscellaneous

Project Links

Back to top

Additional features

This section discusses various features that did not fit in naturally in oneof the previous sections.

Dataclasses

Thedataclasses module allows defining and customizing simpleboilerplate-free classes. They can be defined using the@dataclasses.dataclass decorator:

fromdataclassesimportdataclass,field@dataclassclassApplication:name:strplugins:list[str]=field(default_factory=list)test=Application("Testing...")# OKbad=Application("Testing...","with plugin")# Error: list[str] expected

Mypy will detect special methods (such as__lt__) depending on the flags used todefine dataclasses. For example:

fromdataclassesimportdataclass@dataclass(order=True)classOrderedPoint:x:inty:int@dataclass(order=False)classUnorderedPoint:x:inty:intOrderedPoint(1,2)<OrderedPoint(3,4)# OKUnorderedPoint(1,2)<UnorderedPoint(3,4)# Error: Unsupported operand types

Dataclasses can be generic and can be used in any other way a normalclass can be used (Python 3.12 syntax):

fromdataclassesimportdataclass@dataclassclassBoxedData[T]:data:Tlabel:strdefunbox[T](bd:BoxedData[T])->T:...val=unbox(BoxedData(42,"<important>"))# OK, inferred type is int

For more information seeofficial docsandPEP 557.

Caveats/Known Issues

Some functions in thedataclasses module, such asasdict(),have imprecise (too permissive) types. This will be fixed in future releases.

Mypy does not yet recognize aliases ofdataclasses.dataclass, and willprobably never recognize dynamically computed decorators. The following exampledoesnot work:

fromdataclassesimportdataclassdataclass_alias=dataclassdefdataclass_wrapper(cls):returndataclass(cls)@dataclass_aliasclassAliasDecorated:"""  Mypy doesn't recognize this as a dataclass because it is decorated by an  alias of `dataclass` rather than by `dataclass` itself.  """attribute:intAliasDecorated(attribute=1)# error: Unexpected keyword argument

To have Mypy recognize a wrapper ofdataclasses.dataclassas a dataclass decorator, consider using thedataclass_transform()decorator (example uses Python 3.12 syntax):

fromdataclassesimportdataclass,Fieldfromtypingimportdataclass_transform@dataclass_transform(field_specifiers=(Field,))defmy_dataclass[T](cls:type[T])->type[T]:...returndataclass(cls)

Data Class Transforms

Mypy supports thedataclass_transform() decorator as described inPEP 681.

Note

Pragmatically, mypy will assume such classes have the internal attribute__dataclass_fields__(even though they might lack it in runtime) and will assume functions such asdataclasses.is_dataclass()anddataclasses.fields() treat them as if they were dataclasses(even though they may fail at runtime).

The attrs package

attrs is a package that lets you defineclasses without writing boilerplate code. Mypy can detect uses of thepackage and will generate the necessary method definitions for decoratedclasses using the type annotations it finds.Type annotations can be added as follows:

importattr@attrs.defineclassA:one:inttwo:int=7three:int=attrs.field(8)

If you’re usingauto_attribs=False you must useattrs.field:

importattrs@attrs.defineclassA:one:int=attrs.field()# Variable annotation (Python 3.6+)two=attrs.field()# type: int  # Type commentthree=attrs.field(type=int)# type= argument

Typeshed has a couple of “white lie” annotations to make type checkingeasier.attrs.field() andattrs.Factory actually return objects, but theannotation says these return the types that they expect to be assigned to.That enables this to work:

importattrs@attrs.defineclassA:one:int=attrs.field(8)two:dict[str,str]=attrs.Factory(dict)bad:str=attrs.field(16)# Error: can't assign int to str

Caveats/Known Issues

  • The detection of attr classes and attributes works by function name only.This means that if you have your own helper functions that, for example,returnattrs.field() mypy will not see them.

  • All boolean arguments that mypy cares about must be literalTrue orFalse.e.g the following will not work:

    importattrsYES=True@attrs.define(init=YES)classA:...
  • Currently,converter only supports named functions. If mypy finds something else itwill complain about not understanding the argument and the type annotation in__init__ will be replaced byAny.

  • Validator decoratorsanddefault decoratorsare not type-checked against the attribute they are setting/validating.

  • Method definitions added by mypy currently overwrite any existing methoddefinitions.

Using a remote cache to speed up mypy runs

Mypy performs type checkingincrementally, reusing results fromprevious runs to speed up successive runs. If you are type checking alarge codebase, mypy can still be sometimes slower than desirable. Forexample, if you create a new branch based on a much more recent committhan the target of the previous mypy run, mypy may have toprocess almost every file, as a large fraction of source files mayhave changed. This can also happen after you’ve rebased a localbranch.

Mypy supports using aremote cache to improve performance in casessuch as the above. In a large codebase, remote caching can sometimesspeed up mypy runs by a factor of 10, or more.

Mypy doesn’t include all components needed to setthis up – generally you will have to perform some simple integrationwith your Continuous Integration (CI) or build system to configuremypy to use a remote cache. This discussion assumes you have a CIsystem set up for the mypy build you want to speed up, and that youare using a central git repository. Generalizing to differentenvironments should not be difficult.

Here are the main components needed:

  • A shared repository for storing mypy cache files for all landed commits.

  • CI build that uploads mypy incremental cache files to the shared repository foreach commit for which the CI build runs.

  • A wrapper script around mypy that developers use to run mypy with remotecaching enabled.

Below we discuss each of these components in some detail.

Shared repository for cache files

You need a repository that allows you to upload mypy cache files fromyour CI build and make the cache files available for download based ona commit id. A simple approach would be to produce an archive of the.mypy_cache directory (which contains the mypy cache data) as adownloadablebuild artifact from your CI build (depending on thecapabilities of your CI system). Alternatively, you could upload thedata to a web server or to S3, for example.

Continuous Integration build

The CI build would run a regular mypy build and create an archive containingthe.mypy_cache directory produced by the build. Finally, it will producethe cache as a build artifact or upload it to a repository where it isaccessible by the mypy wrapper script.

Your CI script might work like this:

  • Run mypy normally. This will generate cache data under the.mypy_cache directory.

  • Create a tarball from the.mypy_cache directory.

  • Determine the current git master branch commit id (say, usinggitrev-parseHEAD).

  • Upload the tarball to the shared repository with a name derived from thecommit id.

Mypy wrapper script

The wrapper script is used by developers to run mypy locally duringdevelopment instead of invoking mypy directly. The wrapper firstpopulates the local.mypy_cache directory from the sharedrepository and then runs a normal incremental build.

The wrapper script needs some logic to determine the most recentcentral repository commit (by convention, theorigin/master branchfor git) the local development branch is based on. In a typical gitsetup you can do it like this:

gitmerge-baseHEADorigin/master

The next step is to download the cache data (contents of the.mypy_cache directory) from the shared repository based on thecommit id of the merge base produced by the git command above. Thescript will decompress the data so that mypy will start with a fresh.mypy_cache. Finally, the script runs mypy normally. And that’s all!

Caching with mypy daemon

You can also use remote caching with themypy daemon.The remote cache will significantly speed up the firstdmypycheckrun after starting or restarting the daemon.

The mypy daemon requires extra fine-grained dependency data inthe cache files which aren’t included by default. To use caching withthe mypy daemon, use the--cache-fine-grained option in your CIbuild:

$ mypy --cache-fine-grained <args...>

This flag adds extra information for the daemon to the cache. Inorder to use this extra information, you will also need to use the--use-fine-grained-cache option withdmypystart ordmypyrestart. Example:

$ dmypy start -- --use-fine-grained-cache <options...>

Now your firstdmypycheck run should be much faster, as it can usecache information to avoid processing the whole program.

Refinements

There are several optional refinements that may improve things further,at least if your codebase is hundreds of thousands of lines or more:

  • If the wrapper script determines that the merge base hasn’t changedfrom a previous run, there’s no need to download the cache data andit’s better to instead reuse the existing local cache data.

  • If you use the mypy daemon, you may want to restart the daemon each timeafter the merge base or local branch has changed to avoid processing apotentially large number of changes in an incremental build, as this canbe much slower than downloading cache data and restarting the daemon.

  • If the current local branch is based on a very recent master commit,the remote cache data may not yet be available for that commit, asthere will necessarily be some latency to build the cache files. Itmay be a good idea to look for cache data for, say, the 5 latestmaster commits and use the most recent data that is available.

  • If the remote cache is not accessible for some reason (say, from a publicnetwork), the script can still fall back to a normal incremental build.

  • You can have multiple local cache directories for different local branchesusing the--cache-dir option. If the user switches to an existingbranch where downloaded cache data is already available, you can continueto use the existing cache data instead of redownloading the data.

  • You can set up your CI build to use a remote cache to speed up theCI build. This would be particularly useful if each CI build startsfrom a fresh state without access to cache files from previousbuilds. It’s still recommended to run a full, non-incrementalmypy build to create the cache data, as repeatedly updating cachedata incrementally could result in drift over a long time period (dueto a mypy caching issue, perhaps).

Extended Callable types

Note

This feature is deprecated. You can usecallback protocols as a replacement.

As an experimental mypy extension, you can specifyCallable typesthat support keyword arguments, optional arguments, and more. Whenyou specify the arguments of aCallable, you can choose to supply justthe type of a nameless positional argument, or an “argument specifier”representing a more complicated form of argument. This allows one tomore closely emulate the full range of possibilities given by thedef statement in Python.

As an example, here’s a complicated function definition and thecorrespondingCallable:

fromcollections.abcimportCallablefrommypy_extensionsimport(Arg,DefaultArg,NamedArg,DefaultNamedArg,VarArg,KwArg)deffunc(__a:int,# This convention is for nameless argumentsb:int,c:int=0,*args:int,d:int,e:int=0,**kwargs:int)->int:...F=Callable[[int,# Or Arg(int)Arg(int,'b'),DefaultArg(int,'c'),VarArg(int),NamedArg(int,'d'),DefaultNamedArg(int,'e'),KwArg(int)],int]f:F=func

Argument specifiers are special function calls that can specify thefollowing aspects of an argument:

  • its type (the only thing that the basic format supports)

  • its name (if it has one)

  • whether it may be omitted

  • whether it may or must be passed using a keyword

  • whether it is a*args argument (representing the remainingpositional arguments)

  • whether it is a**kwargs argument (representing the remainingkeyword arguments)

The following functions are available inmypy_extensions for thispurpose:

defArg(type=Any,name=None):# A normal, mandatory, positional argument.# If the name is specified it may be passed as a keyword.defDefaultArg(type=Any,name=None):# An optional positional argument (i.e. with a default value).# If the name is specified it may be passed as a keyword.defNamedArg(type=Any,name=None):# A mandatory keyword-only argument.defDefaultNamedArg(type=Any,name=None):# An optional keyword-only argument (i.e. with a default value).defVarArg(type=Any):# A *args-style variadic positional argument.# A single VarArg() specifier represents all remaining# positional arguments.defKwArg(type=Any):# A **kwargs-style variadic keyword argument.# A single KwArg() specifier represents all remaining# keyword arguments.

In all cases, thetype argument defaults toAny, and if thename argument is omitted the argument has no name (the name isrequired forNamedArg andDefaultNamedArg). A basicCallable such as

MyFunc=Callable[[int,str,int],float]

is equivalent to the following:

MyFunc=Callable[[Arg(int),Arg(str),Arg(int)],float]

ACallable with unspecified argument types, such as

MyOtherFunc=Callable[...,int]

is (roughly) equivalent to

MyOtherFunc=Callable[[VarArg(),KwArg()],int]

Note

Each of the functions above currently just returns itstypeargument at runtime, so the information contained in the argumentspecifiers is not available at runtime. This limitation isnecessary for backwards compatibility with the existingtyping.py module as present in the Python 3.5+ standard libraryand distributed via PyPI.

On this page

[8]ページ先頭

©2009-2025 Movatter.jp