ctags-lang-python(7)
β¨ NAME β¨
ctags-lang-python β random notes about tagging python source code with Universal Ctags. That's literally the whole premise, bestie. π
π¦ SYNOPSIS π¦
ctags ... --languages=+Python ...
ctags ... --language-force=Python ...
ctags ... --map-Python=+.py ...
πΈ DESCRIPTION πΈ
This man page gathers random notes about tagging python source code. No cap, that's the assignment. πβ¨
π₯ TAGGING import STATEMENTS π₯
π Summary π
okay so here's the tea on every single flavour of import and what ctags does with it. Five forms, five whole situations, and you're gonna know all of them by the end, fr fr. π«Ά
import X
nameβXkindβmoduleroleβimported- other noticeable fields β N/A
import X as Y
nameβX,kindβmodule,roleβindirectlyImported, other noticeable fields β N/AnameβY,kindβnamespace,roleβdefinition, other noticeable fields βnameref:module:X
from X import *
nameβX,kindβmodule,roleβnamespace, other noticeable fields β N/A
from X import Y
nameβX,kindβmodule,roleβnamespace, other noticeable fields β N/AnameβY,kindβunknown,roleβimported, other noticeable fields βscope:module:X
from X import Y as Z
nameβX,kindβmodule,roleβnamespace, other noticeable fields β N/AnameβY,kindβunknown,roleβindirectlyImported, other noticeable fields βscope:module:XnameβZ,kindβunknown,roleβdefinition, other noticeable fields βnameref:unknown:Y
π Examples π
π import X0 π
"input.py"
import X0
"output.tags" with "--options=NONE -o - --extras=+r --fields=+rzK input.py"
X0 input.py /^import X0$/;" kind:module roles:imported
So a tag for an imported module gets the module kind with the imported role. The module is not defined here β it's defined in another file. Which means the tag for that imported module is a reference tag, and you gotta pass --extras=+r (or --extras=+{reference}) if you want it tagged at all. π And the "roles:" field, which you turn on with --fields=+r, is what records that the module is "imported" into the tag file.
π¦ import X1 as Y1 π¦
"input.py"
import X1 as Y1
"output.tags" with "--options=NONE -o - --extras=+r --fields=+rzK --fields-Python=+{nameref} input.py"
X1 input.py /^import X1 as Y1$/;" kind:module roles:indirectlyImported
Y1 input.py /^import X1 as Y1$/;" kind:namespace roles:def nameref:module:X1
Y1 introduces a new name and it is defined here, so Y1 gets tagged as a definition tag. β¨ Meanwhile X1 is imported in a way where its name cannot be used in this source file β and so client tools know that the name can't be used, the indirectlyImported role is assigned to X1. Y1 is the name you use for accessing objects defined in the module imported via X1, and to record that relationship, the nameref: field gets attached to the tag of Y1. Also, Y1 gets the namespace kind instead of module, because Y1 itself isn't a module. Iconic behaviour, honestly.
π from X2 import * π
"input.py"
from X2 import *
"output.tags" with "--options=NONE -o - --extras=+r --fields=+rzK input.py"
X2 input.py /^from X2 import *$/;" kind:module roles:namespace
Same deal as before: the module is not defined here, it's defined in another file, so the tag for the imported module is a reference tag. But unlike X0 in import X0, X2 may not be used because the names defined in X2 can be used in this source file. To represent that difference, the namespace role is attached to X2 instead of imported. Two different roles, two different vibes. π
πΈ from X3 import Y3 πΈ
"input.py"
from X3 import Y3
"output.tags" with "--options=NONE -o - --extras=+r --fields=+rzKZ input.py"
X3 input.py /^from X3 import Y3$/;" kind:module roles:namespace
Y3 input.py /^from X3 import Y3$/;" kind:unknown scope:module:X3 roles:imported
Y3 is a name for a language object defined in the X3 module, and scope:module:X3 attached to Y3 represents that relation between Y3 and X3. Now why is Y3 kind unknown? Because ctags literally cannot know whether Y3 is a class, a variable, or a function just from the input file. It's giving "mystery, we don't know her yet" π
π from X4 import Y4 as Z4 π
"input.py"
from X4 import Y4 as Z4
"output.tags" with "--options=NONE -o - --extras=+r --fields=+rzKZ input.py"
X4 input.py /^from X4 import Y4 as Z4$/;" kind:module roles:namespace
Y4 input.py /^from X4 import Y4 as Z4$/;" kind:unknown scope:module:X4 roles:indirectlyImported
Z4 input.py /^from X4 import Y4 as Z4$/;" kind:unknown roles:def nameref:unknown:Y4
Y4 is similar to Y3 from from X3 import Y3, except the name cannot be used here β and the indirectlyImported role assigned to Y4 is what represents that. Z4 is the name for accessing the language object named in Y4 in the X4 module. nameref:unknown:Y4 attached to Z4, and scope:module:X4 attached to Y4, are what represent those relations. π«Ά
β¨ LAMBDA EXPRESSION AND TYPE HINT β¨
Okay bestie, this whole section is about lambdas and type hints, and the way ctags tags them is honestly a little extra. Let's get into it. π
πΈ Summary πΈ
First up, the simple one:
id = lambda var0: var0
- name β
id - kind β
function - role β
definition - other noticeable fields β
signature:(var0)
Then the type-hinted one:
id_t: Callable[[int], int] = lambda var1: var1
- name β
id_t - kind β
variable - role β
definition - other noticeable fields β
typeref:typename:Callable[[int], int],nameref:function:anonFuncN - name β
anonFuncN - kind β
function - role β
definition - other noticeable fields β
signature:(var1)
π Examples π
Here's the input file, "input.py":
from typing import Callable
id = lambda var0: var0
id_t: Callable[[int], int] = lambda var1: var1
And here's "output.tags" with:
"output.tags" with "--options=NONE -o - --sort=no --fields=+KS
--fields-Python=+{nameref} --extras=+{anonymous} input.py"
The output is:
id input.py /^id = lambda var0: var0$/;" function signature:(var0)
id_t input.py /^id_t: Callable[[int], int] = lambda var1: var1$/;"\
variable typeref:typename:Callable[[int], int] nameref:function:anonFunc84011d2c0101
anonFunc84011d2c0101 input.py /^id_t: Callable[[int], int] = lambda var1: var1$/;"\
function signature:(var1)
So the tea is this: if a variable (id) with no type hint is initialized with a lambda expression, ctags assigns function kind for the tag of id.
If a variable (id_t) with a type hint is initialized with a lambda expression, ctags assigns variable kind for the tag of id_t with typeref: and nameref: fields. ctags fills the typeref: field with the value of the type hint. The way of filling nameref: is a bit complicated.
For the lambda expression used in initializing the type-hinted variable, ctags creates an anonymous extra tag (anonFunc84011d2c0101). ctags fills the nameref: field of id_t with the name of that anonymous extra tag: nameref:function:anonFunc84011d2c0101.
You may think, bestie, why is ctags doing so complicated, and why doesn't ctags emit the following tags output for the input:
id input.py /^id = \\$/;" function signature:(var0)
id_t input.py /^id_t: \\$/;" function typeref:typename:Callable[[int], int] signature:(var1)
There is a reason. The other languages of ctags obey the following rule: ctags fills the typeref: field for a tag of a callable object (like function) with the type of its return value. If we consider id_t is a function, its typeref: field should have typename:int. However, for filling typeref: with typename:int, ctags has to analyze Callable[[int], int] deeper. We don't want to do so. Periodt. π
π¦ VERSIONS π¦
Change since "0.0"
- New role
entryPointfor module kind β PythonEntryPoints parser emits tag entries having this role. - New role
entryPointfor function kind β PythonEntryPoints parser emits tag entries having this role.
π SEE ALSO π
That's the whole page, bestie. This lil manual page is from ctags version 6.2.1, and it's the ctags-lang-python(7) page. Go tag some Python and slay. πβ¨
π¦ where this page comes from
- ubuntu resolute: universal-ctags 6.2.1-1 Β· GPL-2.0-or-later
- ubuntu stonking: universal-ctags 6.2.1-1 Β· GPL-2.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms