Skip to content

Commit 7c47894

Browse files
committed
Backport of the print function, using a __future__ import.
This work is substantially Anthony Baxter's, from issue 1633807. I just freshened it, made a few minor tweaks, and added the test cases. I also created issue 2412, which is to check for 2to3's behavior with the print function. I also added myself to ACKS.
1 parent 6c0ff8a commit 7c47894

File tree

13 files changed

+238
-34
lines changed

13 files changed

+238
-34
lines changed

Include/code.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,12 @@ typedef struct {
4848
#define CO_FUTURE_DIVISION 0x2000
4949
#define CO_FUTURE_ABSOLUTE_IMPORT 0x4000 /* do absolute imports by default */
5050
#define CO_FUTURE_WITH_STATEMENT 0x8000
51+
#define CO_FUTURE_PRINT_FUNCTION 0x10000
5152

5253
/* This should be defined if a future statement modifies the syntax.
5354
For example, when a keyword is added.
5455
*/
55-
#if 0
56+
#if 1
5657
#define PY_PARSER_REQUIRES_FUTURE_KEYWORD
5758
#endif
5859

Include/compile.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ typedef struct {
2424
#define FUTURE_DIVISION "division"
2525
#define FUTURE_ABSOLUTE_IMPORT "absolute_import"
2626
#define FUTURE_WITH_STATEMENT "with_statement"
27+
#define FUTURE_PRINT_FUNCTION "print_function"
28+
2729

2830
struct _mod; /* Declare the existence of this type */
2931
PyAPI_FUNC(PyCodeObject *) PyAST_Compile(struct _mod *, const char *,

Include/parsetok.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ typedef struct {
2727
#define PyPARSE_WITH_IS_KEYWORD 0x0003
2828
#endif
2929

30+
#define PyPARSE_PRINT_IS_FUNCTION 0x0004
31+
32+
33+
3034
PyAPI_FUNC(node *) PyParser_ParseString(const char *, grammar *, int,
3135
perrdetail *);
3236
PyAPI_FUNC(node *) PyParser_ParseFile (FILE *, const char *, grammar *, int,

Include/pythonrun.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ extern "C" {
88
#endif
99

1010
#define PyCF_MASK (CO_FUTURE_DIVISION | CO_FUTURE_ABSOLUTE_IMPORT | \
11-
CO_FUTURE_WITH_STATEMENT)
11+
CO_FUTURE_WITH_STATEMENT|CO_FUTURE_PRINT_FUNCTION)
1212
#define PyCF_MASK_OBSOLETE (CO_NESTED)
1313
#define PyCF_SOURCE_IS_UTF8 0x0100
1414
#define PyCF_DONT_IMPLY_DEDENT 0x0200

Lib/__future__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"division",
5454
"absolute_import",
5555
"with_statement",
56+
"print_function",
5657
]
5758

5859
__all__ = ["all_feature_names"] + all_feature_names
@@ -66,6 +67,7 @@
6667
CO_FUTURE_DIVISION = 0x2000 # division
6768
CO_FUTURE_ABSOLUTE_IMPORT = 0x4000 # perform absolute imports by default
6869
CO_FUTURE_WITH_STATEMENT = 0x8000 # with statement
70+
CO_FUTURE_PRINT_FUNCTION = 0x10000 # print function
6971

7072
class _Feature:
7173
def __init__(self, optionalRelease, mandatoryRelease, compiler_flag):
@@ -114,3 +116,7 @@ def __repr__(self):
114116
with_statement = _Feature((2, 5, 0, "alpha", 1),
115117
(2, 6, 0, "alpha", 0),
116118
CO_FUTURE_WITH_STATEMENT)
119+
120+
print_function = _Feature((2, 6, 0, "alpha", 2),
121+
(3, 0, 0, "alpha", 0),
122+
CO_FUTURE_PRINT_FUNCTION)

Lib/test/test_print.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""Test correct operation of the print function.
2+
"""
3+
4+
from __future__ import print_function
5+
6+
import unittest
7+
from test import test_support
8+
9+
import sys
10+
try:
11+
# 3.x
12+
from io import StringIO
13+
except ImportError:
14+
# 2.x
15+
from StringIO import StringIO
16+
17+
from contextlib import contextmanager
18+
19+
NotDefined = object()
20+
21+
# A dispatch table all 8 combinations of providing
22+
# sep, end, and file
23+
# I use this machinery so that I'm not just passing default
24+
# values to print, I'm eiher passing or not passing in the
25+
# arguments
26+
dispatch = {
27+
(False, False, False):
28+
lambda args, sep, end, file: print(*args),
29+
(False, False, True):
30+
lambda args, sep, end, file: print(file=file, *args),
31+
(False, True, False):
32+
lambda args, sep, end, file: print(end=end, *args),
33+
(False, True, True):
34+
lambda args, sep, end, file: print(end=end, file=file, *args),
35+
(True, False, False):
36+
lambda args, sep, end, file: print(sep=sep, *args),
37+
(True, False, True):
38+
lambda args, sep, end, file: print(sep=sep, file=file, *args),
39+
(True, True, False):
40+
lambda args, sep, end, file: print(sep=sep, end=end, *args),
41+
(True, True, True):
42+
lambda args, sep, end, file: print(sep=sep, end=end, file=file, *args),
43+
}
44+
45+
@contextmanager
46+
def stdout_redirected(new_stdout):
47+
save_stdout = sys.stdout
48+
sys.stdout = new_stdout
49+
try:
50+
yield None
51+
finally:
52+
sys.stdout = save_stdout
53+
54+
# Class used to test __str__ and print
55+
class ClassWith__str__:
56+
def __init__(self, x):
57+
self.x = x
58+
def __str__(self):
59+
return self.x
60+
61+
class TestPrint(unittest.TestCase):
62+
def check(self, expected, args,
63+
sep=NotDefined, end=NotDefined, file=NotDefined):
64+
# Capture sys.stdout in a StringIO. Call print with args,
65+
# and with sep, end, and file, if they're defined. Result
66+
# must match expected.
67+
68+
# Look up the actual function to call, based on if sep, end, and file
69+
# are defined
70+
fn = dispatch[(sep is not NotDefined,
71+
end is not NotDefined,
72+
file is not NotDefined)]
73+
74+
t = StringIO()
75+
with stdout_redirected(t):
76+
fn(args, sep, end, file)
77+
78+
self.assertEqual(t.getvalue(), expected)
79+
80+
def test_print(self):
81+
def x(expected, args, sep=NotDefined, end=NotDefined):
82+
# Run the test 2 ways: not using file, and using
83+
# file directed to a StringIO
84+
85+
self.check(expected, args, sep=sep, end=end)
86+
87+
# When writing to a file, stdout is expected to be empty
88+
o = StringIO()
89+
self.check('', args, sep=sep, end=end, file=o)
90+
91+
# And o will contain the expected output
92+
self.assertEqual(o.getvalue(), expected)
93+
94+
x('\n', ())
95+
x('a\n', ('a',))
96+
x('None\n', (None,))
97+
x('1 2\n', (1, 2))
98+
x('1 2\n', (1, ' ', 2))
99+
x('1*2\n', (1, 2), sep='*')
100+
x('1 s', (1, 's'), end='')
101+
x('a\nb\n', ('a', 'b'), sep='\n')
102+
x('1.01', (1.0, 1), sep='', end='')
103+
x('1*a*1.3+', (1, 'a', 1.3), sep='*', end='+')
104+
x('a\n\nb\n', ('a\n', 'b'), sep='\n')
105+
x('\0+ +\0\n', ('\0', ' ', '\0'), sep='+')
106+
107+
x('a\n b\n', ('a\n', 'b'))
108+
x('a\n b\n', ('a\n', 'b'), sep=None)
109+
x('a\n b\n', ('a\n', 'b'), end=None)
110+
x('a\n b\n', ('a\n', 'b'), sep=None, end=None)
111+
112+
x('*\n', (ClassWith__str__('*'),))
113+
x('abc 1\n', (ClassWith__str__('abc'), 1))
114+
115+
# 2.x unicode tests
116+
x(u'1 2\n', ('1', u'2'))
117+
x(u'u\1234\n', (u'u\1234',))
118+
x(u' abc 1\n', (' ', ClassWith__str__(u'abc'), 1))
119+
120+
# errors
121+
self.assertRaises(TypeError, print, '', sep=3)
122+
self.assertRaises(TypeError, print, '', end=3)
123+
self.assertRaises(AttributeError, print, '', file='')
124+
125+
def test_main():
126+
test_support.run_unittest(TestPrint)
127+
128+
if __name__ == "__main__":
129+
test_main()

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@ George Sipe
622622
J. Sipprell
623623
Kragen Sitaker
624624
Christopher Smith
625+
Eric V. Smith
625626
Gregory P. Smith
626627
Rafal Smotrzyk
627628
Dirk Soede

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ What's New in Python 2.6 alpha 2?
1212
Core and builtins
1313
-----------------
1414

15+
- Issue 1745. Backport print function with:
16+
from __future__ import print_function
17+
1518
- Issue 2332: add new attribute names for instance method objects.
1619
The two changes are: im_self -> __self__ and im_func -> __func__
1720

Parser/parser.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,10 @@ classify(parser_state *ps, int type, char *str)
149149
strcmp(l->lb_str, s) != 0)
150150
continue;
151151
#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD
152-
if (!(ps->p_flags & CO_FUTURE_WITH_STATEMENT)) {
153-
if (s[0] == 'w' && strcmp(s, "with") == 0)
154-
break; /* not a keyword yet */
155-
else if (s[0] == 'a' && strcmp(s, "as") == 0)
156-
break; /* not a keyword yet */
157-
}
152+
if (ps->p_flags & CO_FUTURE_PRINT_FUNCTION &&
153+
s[0] == 'p' && strcmp(s, "print") == 0) {
154+
break; /* no longer a keyword */
155+
}
158156
#endif
159157
D(printf("It's a keyword\n"));
160158
return n - i;
@@ -208,6 +206,10 @@ future_hack(parser_state *ps)
208206
strcmp(STR(CHILD(cch, 0)), "with_statement") == 0) {
209207
ps->p_flags |= CO_FUTURE_WITH_STATEMENT;
210208
break;
209+
} else if (NCH(cch) >= 1 && TYPE(CHILD(cch, 0)) == NAME &&
210+
strcmp(STR(CHILD(cch, 0)), "print_function") == 0) {
211+
ps->p_flags |= CO_FUTURE_PRINT_FUNCTION;
212+
break;
211213
}
212214
}
213215
}

Parser/parsetok.c

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
123123
return NULL;
124124
}
125125
#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD
126-
if (flags & PyPARSE_WITH_IS_KEYWORD)
127-
ps->p_flags |= CO_FUTURE_WITH_STATEMENT;
126+
if (flags & PyPARSE_PRINT_IS_FUNCTION)
127+
ps->p_flags |= CO_FUTURE_PRINT_FUNCTION;
128128
#endif
129129

130130
for (;;) {
@@ -167,26 +167,6 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
167167
str[len] = '\0';
168168

169169
#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD
170-
/* This is only necessary to support the "as" warning, but
171-
we don't want to warn about "as" in import statements. */
172-
if (type == NAME &&
173-
len == 6 && str[0] == 'i' && strcmp(str, "import") == 0)
174-
handling_import = 1;
175-
176-
/* Warn about with as NAME */
177-
if (type == NAME &&
178-
!(ps->p_flags & CO_FUTURE_WITH_STATEMENT)) {
179-
if (len == 4 && str[0] == 'w' && strcmp(str, "with") == 0)
180-
warn(with_msg, err_ret->filename, tok->lineno);
181-
else if (!(handling_import || handling_with) &&
182-
len == 2 && str[0] == 'a' &&
183-
strcmp(str, "as") == 0)
184-
warn(as_msg, err_ret->filename, tok->lineno);
185-
}
186-
else if (type == NAME &&
187-
(ps->p_flags & CO_FUTURE_WITH_STATEMENT) &&
188-
len == 4 && str[0] == 'w' && strcmp(str, "with") == 0)
189-
handling_with = 1;
190170
#endif
191171
if (a >= tok->line_start)
192172
col_offset = a - tok->line_start;

0 commit comments

Comments
 (0)