Sitelet https://github.com/python/cpython/commit/58c0752a33253641c1423fac2d4ef3f623fbcb46
Skip to content

Commit 58c0752

Browse files
author
Victor Stinner
committed
Issue #10335: Add tokenize.open(), detect the file encoding using
tokenize.detect_encoding() and open it in read only mode.
1 parent ae4836d commit 58c0752

8 files changed

Lines changed: 54 additions & 22 deletions

File tree

‎Doc/library/tokenize.rst‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,16 @@ function it uses to do this is available:
101101
If no encoding is specified, then the default of ``'utf-8'`` will be
102102
returned.
103103

104-
:func:`detect_encoding` is useful for robustly reading Python source files.
105-
A common pattern for this follows::
104+
Use :func:`open` to open Python source files: it uses
105+
:func:`detect_encoding` to detect the file encoding.
106106

107-
def read_python_source(file_name):
108-
with open(file_name, "rb") as fp:
109-
encoding = tokenize.detect_encoding(fp.readline)[0]
110-
with open(file_name, "r", encoding=encoding) as fp:
111-
return fp.read()
107+
108+
.. function:: open(filename)
109+
110+
Open a file in read only mode using the encoding detected by
111+
:func:`detect_encoding`.
112+
113+
.. versionadded:: 3.2
112114

113115

114116
Example of a script rewriter that transforms float literals into Decimal
@@ -153,4 +155,3 @@ objects::
153155
result.append((toknum, tokval))
154156
return untokenize(result).decode('utf-8')
155157

156-

‎Lib/linecache.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,7 @@ def updatecache(filename, module_globals=None):
123123
else:
124124
return []
125125
try:
126-
with open(fullname, 'rb') as fp:
127-
coding, line = tokenize.detect_encoding(fp.readline)
128-
with open(fullname, 'r', encoding=coding) as fp:
126+
with tokenize.open(fullname) as fp:
129127
lines = fp.readlines()
130128
except IOError:
131129
return []

‎Lib/py_compile.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,7 @@ def compile(file, cfile=None, dfile=None, doraise=False):
104104
byte-compile all installed files (or all files in selected
105105
directories).
106106
"""
107-
with open(file, "rb") as f:
108-
encoding = tokenize.detect_encoding(f.readline)[0]
109-
with open(file, encoding=encoding) as f:
107+
with tokenize.open(file) as f:
110108
try:
111109
timestamp = int(os.fstat(f.fileno()).st_mtime)
112110
except AttributeError:

‎Lib/tabnanny.py‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,8 @@ def check(file):
9393
check(fullname)
9494
return
9595

96-
with open(file, 'rb') as f:
97-
encoding, lines = tokenize.detect_encoding(f.readline)
98-
9996
try:
100-
f = open(file, encoding=encoding)
97+
f = tokenize.open(file)
10198
except IOError as msg:
10299
errprint("%r: I/O Error: %s" % (file, msg))
103100
return

‎Lib/test/test_tokenize.py‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,8 @@
564564

565565
from test import support
566566
from tokenize import (tokenize, _tokenize, untokenize, NUMBER, NAME, OP,
567-
STRING, ENDMARKER, tok_name, detect_encoding)
567+
STRING, ENDMARKER, tok_name, detect_encoding,
568+
open as tokenize_open)
568569
from io import BytesIO
569570
from unittest import TestCase
570571
import os, sys, glob
@@ -857,6 +858,26 @@ def test_short_files(self):
857858
readline = self.get_readline((b'# coding: bad\n',))
858859
self.assertRaises(SyntaxError, detect_encoding, readline)
859860

861+
def test_open(self):
862+
filename = support.TESTFN + '.py'
863+
self.addCleanup(support.unlink, filename)
864+
865+
# test coding cookie
866+
for encoding in ('iso-8859-15', 'utf-8'):
867+
with open(filename, 'w', encoding=encoding) as fp:
868+
print("# coding: %s" % encoding, file=fp)
869+
print("print('euro:\u20ac')", file=fp)
870+
with tokenize_open(filename) as fp:
871+
assert fp.encoding == encoding
872+
assert fp.mode == 'r'
873+
874+
# test BOM (no coding cookie)
875+
with open(filename, 'w', encoding='utf-8-sig') as fp:
876+
print("print('euro:\u20ac')", file=fp)
877+
with tokenize_open(filename) as fp:
878+
assert fp.encoding == 'utf-8-sig'
879+
assert fp.mode == 'r'
880+
860881
class TestTokenize(TestCase):
861882

862883
def test_tokenize(self):

‎Lib/tokenize.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from token import *
3030
from codecs import lookup, BOM_UTF8
3131
import collections
32+
from io import TextIOWrapper
3233
cookie_re = re.compile("coding[:=]\s*([-\w.]+)")
3334

3435
import token
@@ -335,6 +336,20 @@ def find_cookie(line):
335336
return default, [first, second]
336337

337338

339+
_builtin_open = open
340+
341+
def open(filename):
342+
"""Open a file in read only mode using the encoding detected by
343+
detect_encoding().
344+
"""
345+
buffer = _builtin_open(filename, 'rb')
346+
encoding, lines = detect_encoding(buffer.readline)
347+
buffer.seek(0)
348+
text = TextIOWrapper(buffer, encoding, line_buffering=True)
349+
text.mode = 'r'
350+
return text
351+
352+
338353
def tokenize(readline):
339354
"""
340355
The tokenize() generator requires one argment, readline, which

‎Lib/trace.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,10 +432,9 @@ def find_strings(filename, encoding=None):
432432
def find_executable_linenos(filename):
433433
"""Return dict where keys are line numbers in the line number table."""
434434
try:
435-
with io.FileIO(filename, 'r') as file:
436-
encoding, lines = tokenize.detect_encoding(file.readline)
437-
with open(filename, "r", encoding=encoding) as f:
435+
with tokenize.open(filename) as f:
438436
prog = f.read()
437+
encoding = f.encoding
439438
except IOError as err:
440439
print(("Not printing coverage data for %r: %s"
441440
% (filename, err)), file=sys.stderr)

‎Misc/NEWS‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ Core and Builtins
6060
Library
6161
-------
6262

63+
- Issue #10335: Add tokenize.open(), detect the file encoding using
64+
tokenize.detect_encoding() and open it in read only mode.
65+
6366
- Issue #10321: Added support for binary data to smtplib.SMTP.sendmail,
6467
and a new method send_message to send an email.message.Message object.
6568

0 commit comments

Comments
 (0)