Helping Wing Analyze Code
Wing's source analyser can only read Python code and does not contain support for understanding C/C++ extension module code other than by attempting to import the extension module and introspecting its contents (which yields only a limited amount of information and cannot determine argument number, name, or types). Also, since Python is a dynamic language, it is possible to craft code that Wing's static analysis engine cannot understand.
There are a number of ways of assistant Wing's static source analyzer in determining the type of values in Python code.
Using Live Runtime State
When a debug process is active, or when working in the Python Shell, Wing extracts relevant type information from the live runtime state associated with your Python code. Since this yields complete and correct type information even for code that Wing's static analysis engine cannot understand, it is often useful to run to a breakpoint before designing new code that is intended to work in that context.
In the editor, the cog icon in the auto-completer indicates that type information was found in the live runtime state.
In Wing Pro, the Debug Probe can be used to immediately try out new code in the runtime environment for which it is being designed.
The Python Shell (in Wing Personal and Wing Pro) and Debug Probe (in Wing Pro) can mark an active range in the editor so code can quickly be reevaluated as it is being edited. This is done by selecting the code and pressing the Active Range icon in the upper right of the tool into which you want to set the active range.
Using PEP484 and PEP 526 to Assist Analysis
Wing can understand type hints in the style standardized by PEP 484 (Python 3.5+) and PEP 527 (Python 3.6+). For example, the following indicates to Wing the argument and return types of the function myFunction:
from typing import Dict, List def myFunction(arg1: str, arg2: Dict) -> List: return arg2.get(arg1, )
The type of variables can be indicated by a comment that follows it:
x = Something() # type: int
In Python 3.6+ the type can instead be specified inline as follows:
x:int = Something()
The types that Wing can recognize include basic types like str and int and also the following from the typing module: List, Tuple, Dict, Set, FrozenSet, Optional, and Union.
Limitation: Wing currently cannot remember the type of the elements of lists, tuples, dicts, and sets.
Using isinstance() to Assist Analysis
One way to inform the static analysis engine of the type of a variable is to add an isinstance call in your code. For example isinstance(obj, CMyClass) or assert isinstance(obj, CMyClass) when runtime type checking is desired. The code analyzer will pick up on these and present more complete information for the asserted values.
In cases where doing this introduces a circular import, you can use a conditional to allow Wing's static analyser to process the code without causing problems when it is executed:
if 0: import othermodule assert isinstance(myvariable, othermodule.COtherClass)
In most code, a few isinstance calls go a long way to making code faster and easier to edit and navigate.
Using *.pi or *.pyi Files to Assist Analysis
It is also possible to create a *.pi or *.pyi (Python Interface) file that describes the contents of a module. This file is simply a Python skeleton with the appropriate structure, call signature, and return values to match the functions, attributes, classes, and methods defined in a module. Wing will read this file and merge its contents with any information it can obtain through static analysis or by loading an extension module. If the file has a .pyi extension, it can use PEP 484 and PEP 526 type annotations regardless of whether Python 2 or Python 3 is used. Newly written interface files should follow PEP 484 and use the .pyi extension; the .pi extension was used in previous versions of Wing and is still recognized.
In somes cases, as for Python bindings for GUI and other toolkits, these *.pi or *.pyi files can be auto-generated from interface description files. The code that Wing uses to automatically generate *.pi files from extension modules is in src/wingutils/generate_pi.py in your Wing installation, and another example that is used to generate interface information for PyGTK is in src/wingutils/pygtk_to_pi.py.
Naming and Placing *.pyi Files
Wing expects the *.pyi file name to match the name of the module. For example, if the name referenced by import as mymodule then Wing looks for mymodule.pyi.
The most common place to put the *.pyi file is in the same directory as the *.pyd, *.so, or *.py for the module is is describing. *.pyi files that describe entire packages (directories containing __init__.py) should be placed in the package directory's parent directory.
If Wing cannot find the *.pyi file in the same directory as the module, it proceeds to search as follows, choosing the first matching *.pyi file:
- In the path set with the Source Analysis > Advanced > Interfaces Path preference.
- In the resources/builtin-pi-files in the Wing installation. This is used to ship type overrides for Python's builtin types and standard library.
- In resources/package-pi-files, which is used to ship some *.pyi files for commonly used third party packages.
For all of these, Wing inspects the path directory for a matching *.pyi file and treats any sub-directories as packages.
In cases where Wing cannot find a *.pyi at all for an extension module, it will still attempt to load the extension module by name, in a separate process space, so that it can introspect its contents. The results of this operation are stored in pi-cache within the Cache Directory shown in Wing's About box. This file is regenerated only if the *.pyd or *.so for the loaded extension module changes.
For Python source modules, absence of a *.pyi causes Wing to fall back on static analysis and (if available) runtime analysis through the debugger.
Merging *.pyi Name Spaces
When Wing finds a *.pyi file in the same directory as a Python module or extension module, or if it finds it using the Source Analysis > Advanced > Interfaces Path preference, then Wing merges the contents of the *.pyi file with any information found by analyzing or introspecting the module. The contents of the *.pyi file take precedence when symbols are defined in both places.
Creating Variants by Python Version
In rare cases, you may need to create variants of your *.pyi files according to Python version. An example of this is in resources/builtin-pi-files, the directory used to ship type overrides for Python's builtin types and standard library.
As noted above, Wing always looks first at the top level of an interface path directory for a matching *.pyi file. If this fails then Wing tries looking in a sub-directory #.# named according to the major and minor version of Python being used with your source base, and subsequently in each lower major/minor version back to 2.0.
For example, if c:\share\pi\pi-files is on the interfaces path and Python 2.7 is being used, Wing will check first in c:\share\pi\pi-files, then in c:\share\pi\pi-files\2.7. then in c:\share\pi\pi-files\2.6, and so forth.