Helper Functions#
In addition to the primary tokenize() entry-point, the
tokenize module has several additional helper functions.
generate_tokens(readline)#
Similar to tokenize(), except the readline method should
return strings instead of bytes. This is useful when working interactively, as
you do not need to use bytes literals or encode str objects into bytes. It
otherwise works the same as tokenize: it accepts a readline method and
returns an iterator of tokens.
An important difference with generate_tokens() is that since it already
accepts strings, it assumes that the input is already encoded. Therefore, it
will ignore # -*- coding: ... -*- comments (see the section on
exceptions). Consequently, you should only use this function
when you already have the input as an encoded string (e.g., when working
interactively). If you are reading from a file or receiving the Python from
some other source that is in bytes, you should use tokenize() instead, as it
will correctly detect the encoding from coding headers.
Another important difference is that generate_tokens() will not emit the
ENCODING token.
This guide uses tokenize() in all its examples. This is because even though
generate_tokens() may appear to be more convenient—after all, the examples
here are all self-contained pieces of code in strings—the typical use-case
of tokenize involves reading a code from bytes (i.e., from a file).
Furthermore, it is also often convenient to have the ENCODING
token as a guaranteed first token, even if it is not actually used, as it can
make processing tokens a little simpler in some cases (see the
examples).
Finally, note that untokenize() returns a bytes object, so
if you are working with it, it maybe be simpler to just use tokenize() and
work with bytes everywhere.
Here is an example comparing tokenize() to generate_tokens() for the code
a + b.
>>> import tokenize
>>> import io
>>> code = "a + b\n"
>>> # With tokenize, we must encode the string as bytes
>>> for t in tokenize.tokenize(io.BytesIO(code.encode('utf-8')).readline):
... print(t)
TokenInfo(type=63 (ENCODING), string='utf-8', start=(0, 0), end=(0, 0), line='')
TokenInfo(type=1 (NAME), string='a', start=(1, 0), end=(1, 1), line='a + b\n')
TokenInfo(type=54 (OP), string='+', start=(1, 2), end=(1, 3), line='a + b\n')
TokenInfo(type=1 (NAME), string='b', start=(1, 4), end=(1, 5), line='a + b\n')
TokenInfo(type=4 (NEWLINE), string='\n', start=(1, 5), end=(1, 6), line='a + b\n')
TokenInfo(type=0 (ENDMARKER), string='', start=(2, 0), end=(2, 0), line='')
>>> # With generate_tokens(), we can use the encoded str object directly.
>>> # The output is the same except for the fact that the ENCODING token is omitted.
>>> for t in tokenize.generate_tokens(io.StringIO(code).readline):
... print(t)
TokenInfo(type=1 (NAME), string='a', start=(1, 0), end=(1, 1), line='a + b\n')
TokenInfo(type=54 (OP), string='+', start=(1, 2), end=(1, 3), line='a + b\n')
TokenInfo(type=1 (NAME), string='b', start=(1, 4), end=(1, 5), line='a + b\n')
TokenInfo(type=4 (NEWLINE), string='\n', start=(1, 5), end=(1, 6), line='a + b\n')
TokenInfo(type=0 (ENDMARKER), string='', start=(2, 0), end=(2, 0), line='')
untokenize(iterable)#
Converts an iterable of tokens into a bytes string. The string is encoded
using the encoding of the ENCODING token. If there
is no ENCODING token present, the string is returned decoded (a str
instead of bytes). The iterable can be TokenInfo objects, or tuples of
(TOKEN_TYPE, TOKEN_STRING).
This function always round-trips in one direction, namely,
tokenize(io.BytesIO(untokenize(tokens)).readline) will always return the
same tokens.
If full TokenInfo tuples are given with correct start and end
information (iterable of 5-tuples), this function also round-trips in the
other direction, for the most part (it assumes space characters between
tokens). However, be aware that the start and end tuples must be
nondecreasing. If the start of one token is before the end of the previous
token, it raises ValueError. Therefore, if you want to modify tokens and use
untokenize() to convert back to a string, using full 5-tuples, you must keep
track of and maintain the line and column information in start and end.
>>> import tokenize
>>> import io
>>> string = b'sum([[1, 2]][0])'
>>> tokenize.untokenize(tokenize.tokenize(io.BytesIO(string).readline))
b'sum([[1, 2]][0])'
If only the token type and token names are given (iterable of 2-tuples),
untokenize() does not round-trip, and in fact, for any nontrivial input, the
resulting bytes string will be very different than the original input. This is
because untokenize() adds spaces after certain tokens to ensure the
resulting string is syntactically valid (or rather, to ensure that it
tokenizes back in the same way).
>>> tokenize.untokenize([(i, j) for (i, j, _, _, _) in tokenize.tokenize(io.BytesIO(string).readline)])
b'sum ([[1 ,2 ]][0 ])'
2-tuples and 5-tuples can be mixed (for instance, you can add new tokens to a
list of TokenInfo objects using only 2-tuples), but in this case, it will
ignore the column information for the 5-tuples.
Consider this simple example which replaces all STRING
tokens with a list of STRING tokens of individual
characters (making use of implicit string concatenation). Once untokenize()
encounters the newly added 2-tuple tokens, it ignores the column information
and uses its own spacing.
>>> import ast
>>> def split_string(s):
... """
... Split string tokens into constituent characters
... """
... new_tokens = []
... for toknum, tokstr, start, end, line in tokenize.tokenize(io.BytesIO(s.encode('utf-8')).readline):
... if toknum == tokenize.STRING:
... for char in ast.literal_eval(tokstr):
... new_tokens.append((toknum, repr(char)))
... else:
... new_tokens.append((toknum, tokstr, start, end, line))
... return tokenize.untokenize(new_tokens).decode('utf-8')
>>> split_string("print('hello ') and print('world')")
"print('h' 'e' 'l' 'l' 'o' ' ')and print ('w' 'o' 'r' 'l' 'd')"
If you want to use the tokenize module to extend the Python language
injecting or modifying tokens in a token stream, then using exec or eval
to convert the resulting source into executable code, and you do not care what
the code itself looks like, you can simply pass this function tuples of
(TOKEN_TYPE, TOKEN_STRING) and it will work fine. However, if your end goal
is to translate code in a human-readable way, you must keep track of line and
column information near the tokens you modify. The tokenize module does not
provide any tools to help with this.
detect_encoding(readline)#
The official
docs
for this function are helpful. This is the function used by tokenize() to
generate the ENCODING token. It can be used separately to
determine the encoding of some Python code. The calling syntax is the same as
for tokenize().
Returns a tuple of the encoding, and a list of any lines (in bytes) that it
has read from the function (it will read at most two lines from the file).
Invalid encodings will cause it to raise a
SyntaxError.
>>> tokenize.detect_encoding(io.BytesIO(b'# -*- coding: ascii -*-').readline)
('ascii', [b'# -*- coding: ascii -*-'])
This function should be used to detect the encoding of a Python source file before opening it in text mode. For example
with open('file.py', 'br') as f:
encoding, _ = tokenize.detect_encoding(f.readline)
with open('file.py', encoding=encoding) as f:
...
Otherwise, the text read from the file may not be parsable as Python. For
example, ast.parse may fail if text from the file is read with the wrong
encoding. For example, if a file starts with a Unicode BOM
character, ast.parse will
fail if the file is not opened with the proper encoding.
tokenize.open(filename)#
This is an alternative to the built-in open() function that automatically
opens a Python file in text mode with the correct encoding, as detected by
detect_encoding().
This function is not particularly useful in conjunction with the tokenize()
function (remember that tokenize() requires opening a file in binary mode,
whereas this function opens it in text mode). Rather, this is a function that
uses the functionality of the tokenize module, in particular,
detect_encoding(), to provide a higher level task that would be difficult to
do otherwise (opening a Python source file in text mode using the syntactically
correct encoding).
Command Line Usage#
The tokenize module can be called from the command line using python -m tokenize filename.py. This prints three columns, representing the start-end
line and column positions, the token type, and the token string. If the -e
flag is used, the token type for operators is the exact type. Otherwise the
OP type is used.
$ python -m tokenize example.py
0,0-0,0: ENCODING 'utf-8'
1,0-1,43: COMMENT '# This is a an example file to be tokenized'
1,43-1,44: NL '\n'
2,0-2,1: NL '\n'
3,0-3,3: NAME 'def'
3,4-3,7: NAME 'two'
3,7-3,8: OP '('
3,8-3,9: OP ')'
3,9-3,10: OP ':'
3,10-3,11: NEWLINE '\n'
4,0-4,4: INDENT ' '
4,4-4,10: NAME 'return'
4,11-4,12: NUMBER '1'
4,13-4,14: OP '+'
4,15-4,16: NUMBER '1'
4,16-4,17: NEWLINE '\n'
5,0-5,0: DEDENT ''
5,0-5,0: ENDMARKER ''
$ python -m tokenize -e example.py
0,0-0,0: ENCODING 'utf-8'
1,0-1,43: COMMENT '# This is a an example file to be tokenized'
1,43-1,44: NL '\n'
2,0-2,1: NL '\n'
3,0-3,3: NAME 'def'
3,4-3,7: NAME 'two'
3,7-3,8: LPAR '('
3,8-3,9: RPAR ')'
3,9-3,10: COLON ':'
3,10-3,11: NEWLINE '\n'
4,0-4,4: INDENT ' '
4,4-4,10: NAME 'return'
4,11-4,12: NUMBER '1'
4,13-4,14: PLUS '+'
4,15-4,16: NUMBER '1'
4,16-4,17: NEWLINE '\n'
5,0-5,0: DEDENT ''
5,0-5,0: ENDMARKER ''