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.
This commit is contained in:
Eric Smith 2008-03-18 23:45:49 +00:00
parent 6c0ff8aacd
commit 7c47894a2a
13 changed files with 238 additions and 34 deletions

View file

@ -48,11 +48,12 @@ typedef struct {
#define CO_FUTURE_DIVISION 0x2000 #define CO_FUTURE_DIVISION 0x2000
#define CO_FUTURE_ABSOLUTE_IMPORT 0x4000 /* do absolute imports by default */ #define CO_FUTURE_ABSOLUTE_IMPORT 0x4000 /* do absolute imports by default */
#define CO_FUTURE_WITH_STATEMENT 0x8000 #define CO_FUTURE_WITH_STATEMENT 0x8000
#define CO_FUTURE_PRINT_FUNCTION 0x10000
/* This should be defined if a future statement modifies the syntax. /* This should be defined if a future statement modifies the syntax.
For example, when a keyword is added. For example, when a keyword is added.
*/ */
#if 0 #if 1
#define PY_PARSER_REQUIRES_FUTURE_KEYWORD #define PY_PARSER_REQUIRES_FUTURE_KEYWORD
#endif #endif

View file

@ -24,6 +24,8 @@ typedef struct {
#define FUTURE_DIVISION "division" #define FUTURE_DIVISION "division"
#define FUTURE_ABSOLUTE_IMPORT "absolute_import" #define FUTURE_ABSOLUTE_IMPORT "absolute_import"
#define FUTURE_WITH_STATEMENT "with_statement" #define FUTURE_WITH_STATEMENT "with_statement"
#define FUTURE_PRINT_FUNCTION "print_function"
struct _mod; /* Declare the existence of this type */ struct _mod; /* Declare the existence of this type */
PyAPI_FUNC(PyCodeObject *) PyAST_Compile(struct _mod *, const char *, PyAPI_FUNC(PyCodeObject *) PyAST_Compile(struct _mod *, const char *,

View file

@ -27,6 +27,10 @@ typedef struct {
#define PyPARSE_WITH_IS_KEYWORD 0x0003 #define PyPARSE_WITH_IS_KEYWORD 0x0003
#endif #endif
#define PyPARSE_PRINT_IS_FUNCTION 0x0004
PyAPI_FUNC(node *) PyParser_ParseString(const char *, grammar *, int, PyAPI_FUNC(node *) PyParser_ParseString(const char *, grammar *, int,
perrdetail *); perrdetail *);
PyAPI_FUNC(node *) PyParser_ParseFile (FILE *, const char *, grammar *, int, PyAPI_FUNC(node *) PyParser_ParseFile (FILE *, const char *, grammar *, int,

View file

@ -8,7 +8,7 @@ extern "C" {
#endif #endif
#define PyCF_MASK (CO_FUTURE_DIVISION | CO_FUTURE_ABSOLUTE_IMPORT | \ #define PyCF_MASK (CO_FUTURE_DIVISION | CO_FUTURE_ABSOLUTE_IMPORT | \
CO_FUTURE_WITH_STATEMENT) CO_FUTURE_WITH_STATEMENT|CO_FUTURE_PRINT_FUNCTION)
#define PyCF_MASK_OBSOLETE (CO_NESTED) #define PyCF_MASK_OBSOLETE (CO_NESTED)
#define PyCF_SOURCE_IS_UTF8 0x0100 #define PyCF_SOURCE_IS_UTF8 0x0100
#define PyCF_DONT_IMPLY_DEDENT 0x0200 #define PyCF_DONT_IMPLY_DEDENT 0x0200

View file

@ -53,6 +53,7 @@ all_feature_names = [
"division", "division",
"absolute_import", "absolute_import",
"with_statement", "with_statement",
"print_function",
] ]
__all__ = ["all_feature_names"] + all_feature_names __all__ = ["all_feature_names"] + all_feature_names
@ -66,6 +67,7 @@ CO_GENERATOR_ALLOWED = 0 # generators (obsolete, was 0x1000)
CO_FUTURE_DIVISION = 0x2000 # division CO_FUTURE_DIVISION = 0x2000 # division
CO_FUTURE_ABSOLUTE_IMPORT = 0x4000 # perform absolute imports by default CO_FUTURE_ABSOLUTE_IMPORT = 0x4000 # perform absolute imports by default
CO_FUTURE_WITH_STATEMENT = 0x8000 # with statement CO_FUTURE_WITH_STATEMENT = 0x8000 # with statement
CO_FUTURE_PRINT_FUNCTION = 0x10000 # print function
class _Feature: class _Feature:
def __init__(self, optionalRelease, mandatoryRelease, compiler_flag): def __init__(self, optionalRelease, mandatoryRelease, compiler_flag):
@ -114,3 +116,7 @@ absolute_import = _Feature((2, 5, 0, "alpha", 1),
with_statement = _Feature((2, 5, 0, "alpha", 1), with_statement = _Feature((2, 5, 0, "alpha", 1),
(2, 6, 0, "alpha", 0), (2, 6, 0, "alpha", 0),
CO_FUTURE_WITH_STATEMENT) CO_FUTURE_WITH_STATEMENT)
print_function = _Feature((2, 6, 0, "alpha", 2),
(3, 0, 0, "alpha", 0),
CO_FUTURE_PRINT_FUNCTION)

129
Lib/test/test_print.py Normal file
View file

@ -0,0 +1,129 @@
"""Test correct operation of the print function.
"""
from __future__ import print_function
import unittest
from test import test_support
import sys
try:
# 3.x
from io import StringIO
except ImportError:
# 2.x
from StringIO import StringIO
from contextlib import contextmanager
NotDefined = object()
# A dispatch table all 8 combinations of providing
# sep, end, and file
# I use this machinery so that I'm not just passing default
# values to print, I'm eiher passing or not passing in the
# arguments
dispatch = {
(False, False, False):
lambda args, sep, end, file: print(*args),
(False, False, True):
lambda args, sep, end, file: print(file=file, *args),
(False, True, False):
lambda args, sep, end, file: print(end=end, *args),
(False, True, True):
lambda args, sep, end, file: print(end=end, file=file, *args),
(True, False, False):
lambda args, sep, end, file: print(sep=sep, *args),
(True, False, True):
lambda args, sep, end, file: print(sep=sep, file=file, *args),
(True, True, False):
lambda args, sep, end, file: print(sep=sep, end=end, *args),
(True, True, True):
lambda args, sep, end, file: print(sep=sep, end=end, file=file, *args),
}
@contextmanager
def stdout_redirected(new_stdout):
save_stdout = sys.stdout
sys.stdout = new_stdout
try:
yield None
finally:
sys.stdout = save_stdout
# Class used to test __str__ and print
class ClassWith__str__:
def __init__(self, x):
self.x = x
def __str__(self):
return self.x
class TestPrint(unittest.TestCase):
def check(self, expected, args,
sep=NotDefined, end=NotDefined, file=NotDefined):
# Capture sys.stdout in a StringIO. Call print with args,
# and with sep, end, and file, if they're defined. Result
# must match expected.
# Look up the actual function to call, based on if sep, end, and file
# are defined
fn = dispatch[(sep is not NotDefined,
end is not NotDefined,
file is not NotDefined)]
t = StringIO()
with stdout_redirected(t):
fn(args, sep, end, file)
self.assertEqual(t.getvalue(), expected)
def test_print(self):
def x(expected, args, sep=NotDefined, end=NotDefined):
# Run the test 2 ways: not using file, and using
# file directed to a StringIO
self.check(expected, args, sep=sep, end=end)
# When writing to a file, stdout is expected to be empty
o = StringIO()
self.check('', args, sep=sep, end=end, file=o)
# And o will contain the expected output
self.assertEqual(o.getvalue(), expected)
x('\n', ())
x('a\n', ('a',))
x('None\n', (None,))
x('1 2\n', (1, 2))
x('1 2\n', (1, ' ', 2))
x('1*2\n', (1, 2), sep='*')
x('1 s', (1, 's'), end='')
x('a\nb\n', ('a', 'b'), sep='\n')
x('1.01', (1.0, 1), sep='', end='')
x('1*a*1.3+', (1, 'a', 1.3), sep='*', end='+')
x('a\n\nb\n', ('a\n', 'b'), sep='\n')
x('\0+ +\0\n', ('\0', ' ', '\0'), sep='+')
x('a\n b\n', ('a\n', 'b'))
x('a\n b\n', ('a\n', 'b'), sep=None)
x('a\n b\n', ('a\n', 'b'), end=None)
x('a\n b\n', ('a\n', 'b'), sep=None, end=None)
x('*\n', (ClassWith__str__('*'),))
x('abc 1\n', (ClassWith__str__('abc'), 1))
# 2.x unicode tests
x(u'1 2\n', ('1', u'2'))
x(u'u\1234\n', (u'u\1234',))
x(u' abc 1\n', (' ', ClassWith__str__(u'abc'), 1))
# errors
self.assertRaises(TypeError, print, '', sep=3)
self.assertRaises(TypeError, print, '', end=3)
self.assertRaises(AttributeError, print, '', file='')
def test_main():
test_support.run_unittest(TestPrint)
if __name__ == "__main__":
test_main()

View file

@ -622,6 +622,7 @@ George Sipe
J. Sipprell J. Sipprell
Kragen Sitaker Kragen Sitaker
Christopher Smith Christopher Smith
Eric V. Smith
Gregory P. Smith Gregory P. Smith
Rafal Smotrzyk Rafal Smotrzyk
Dirk Soede Dirk Soede

View file

@ -12,6 +12,9 @@ What's New in Python 2.6 alpha 2?
Core and builtins Core and builtins
----------------- -----------------
- Issue 1745. Backport print function with:
from __future__ import print_function
- Issue 2332: add new attribute names for instance method objects. - Issue 2332: add new attribute names for instance method objects.
The two changes are: im_self -> __self__ and im_func -> __func__ The two changes are: im_self -> __self__ and im_func -> __func__

View file

@ -149,12 +149,10 @@ classify(parser_state *ps, int type, char *str)
strcmp(l->lb_str, s) != 0) strcmp(l->lb_str, s) != 0)
continue; continue;
#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD #ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD
if (!(ps->p_flags & CO_FUTURE_WITH_STATEMENT)) { if (ps->p_flags & CO_FUTURE_PRINT_FUNCTION &&
if (s[0] == 'w' && strcmp(s, "with") == 0) s[0] == 'p' && strcmp(s, "print") == 0) {
break; /* not a keyword yet */ break; /* no longer a keyword */
else if (s[0] == 'a' && strcmp(s, "as") == 0) }
break; /* not a keyword yet */
}
#endif #endif
D(printf("It's a keyword\n")); D(printf("It's a keyword\n"));
return n - i; return n - i;
@ -208,6 +206,10 @@ future_hack(parser_state *ps)
strcmp(STR(CHILD(cch, 0)), "with_statement") == 0) { strcmp(STR(CHILD(cch, 0)), "with_statement") == 0) {
ps->p_flags |= CO_FUTURE_WITH_STATEMENT; ps->p_flags |= CO_FUTURE_WITH_STATEMENT;
break; break;
} else if (NCH(cch) >= 1 && TYPE(CHILD(cch, 0)) == NAME &&
strcmp(STR(CHILD(cch, 0)), "print_function") == 0) {
ps->p_flags |= CO_FUTURE_PRINT_FUNCTION;
break;
} }
} }
} }

View file

@ -123,8 +123,8 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
return NULL; return NULL;
} }
#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD #ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD
if (flags & PyPARSE_WITH_IS_KEYWORD) if (flags & PyPARSE_PRINT_IS_FUNCTION)
ps->p_flags |= CO_FUTURE_WITH_STATEMENT; ps->p_flags |= CO_FUTURE_PRINT_FUNCTION;
#endif #endif
for (;;) { for (;;) {
@ -167,26 +167,6 @@ parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
str[len] = '\0'; str[len] = '\0';
#ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD #ifdef PY_PARSER_REQUIRES_FUTURE_KEYWORD
/* This is only necessary to support the "as" warning, but
we don't want to warn about "as" in import statements. */
if (type == NAME &&
len == 6 && str[0] == 'i' && strcmp(str, "import") == 0)
handling_import = 1;
/* Warn about with as NAME */
if (type == NAME &&
!(ps->p_flags & CO_FUTURE_WITH_STATEMENT)) {
if (len == 4 && str[0] == 'w' && strcmp(str, "with") == 0)
warn(with_msg, err_ret->filename, tok->lineno);
else if (!(handling_import || handling_with) &&
len == 2 && str[0] == 'a' &&
strcmp(str, "as") == 0)
warn(as_msg, err_ret->filename, tok->lineno);
}
else if (type == NAME &&
(ps->p_flags & CO_FUTURE_WITH_STATEMENT) &&
len == 4 && str[0] == 'w' && strcmp(str, "with") == 0)
handling_with = 1;
#endif #endif
if (a >= tok->line_start) if (a >= tok->line_start)
col_offset = a - tok->line_start; col_offset = a - tok->line_start;

View file

@ -1486,6 +1486,78 @@ With two arguments, equivalent to x**y. With three arguments,\n\
equivalent to (x**y) % z, but may be more efficient (e.g. for longs)."); equivalent to (x**y) % z, but may be more efficient (e.g. for longs).");
static PyObject *
builtin_print(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"sep", "end", "file", 0};
static PyObject *dummy_args;
PyObject *sep = NULL, *end = NULL, *file = NULL;
int i, err;
if (dummy_args == NULL) {
if (!(dummy_args = PyTuple_New(0)))
return NULL;
}
if (!PyArg_ParseTupleAndKeywords(dummy_args, kwds, "|OOO:print",
kwlist, &sep, &end, &file))
return NULL;
if (file == NULL || file == Py_None) {
file = PySys_GetObject("stdout");
/* sys.stdout may be None when FILE* stdout isn't connected */
if (file == Py_None)
Py_RETURN_NONE;
}
if (sep && sep != Py_None && !PyString_Check(sep) &&
!PyUnicode_Check(sep)) {
PyErr_Format(PyExc_TypeError,
"sep must be None, str or unicode, not %.200s",
sep->ob_type->tp_name);
return NULL;
}
if (end && end != Py_None && !PyString_Check(end) &&
!PyUnicode_Check(end)) {
PyErr_Format(PyExc_TypeError,
"end must be None, str or unicode, not %.200s",
end->ob_type->tp_name);
return NULL;
}
for (i = 0; i < PyTuple_Size(args); i++) {
if (i > 0) {
if (sep == NULL || sep == Py_None)
err = PyFile_WriteString(" ", file);
else
err = PyFile_WriteObject(sep, file,
Py_PRINT_RAW);
if (err)
return NULL;
}
err = PyFile_WriteObject(PyTuple_GetItem(args, i), file,
Py_PRINT_RAW);
if (err)
return NULL;
}
if (end == NULL || end == Py_None)
err = PyFile_WriteString("\n", file);
else
err = PyFile_WriteObject(end, file, Py_PRINT_RAW);
if (err)
return NULL;
Py_RETURN_NONE;
}
PyDoc_STRVAR(print_doc,
"print(value, ..., sep=' ', end='\\n', file=sys.stdout)\n\
\n\
Prints the values to a stream, or to sys.stdout by default.\n\
Optional keyword arguments:\n\
file: a file-like object (stream); defaults to the current sys.stdout.\n\
sep: string inserted between values, default a space.\n\
end: string appended after the last value, default a newline.");
/* Return number of items in range (lo, hi, step), when arguments are /* Return number of items in range (lo, hi, step), when arguments are
* PyInt or PyLong objects. step > 0 required. Return a value < 0 if * PyInt or PyLong objects. step > 0 required. Return a value < 0 if
@ -2424,6 +2496,7 @@ static PyMethodDef builtin_methods[] = {
{"open", (PyCFunction)builtin_open, METH_VARARGS | METH_KEYWORDS, open_doc}, {"open", (PyCFunction)builtin_open, METH_VARARGS | METH_KEYWORDS, open_doc},
{"ord", builtin_ord, METH_O, ord_doc}, {"ord", builtin_ord, METH_O, ord_doc},
{"pow", builtin_pow, METH_VARARGS, pow_doc}, {"pow", builtin_pow, METH_VARARGS, pow_doc},
{"print", (PyCFunction)builtin_print, METH_VARARGS | METH_KEYWORDS, print_doc},
{"range", builtin_range, METH_VARARGS, range_doc}, {"range", builtin_range, METH_VARARGS, range_doc},
{"raw_input", builtin_raw_input, METH_VARARGS, raw_input_doc}, {"raw_input", builtin_raw_input, METH_VARARGS, raw_input_doc},
{"reduce", builtin_reduce, METH_VARARGS, reduce_doc}, {"reduce", builtin_reduce, METH_VARARGS, reduce_doc},

View file

@ -33,6 +33,8 @@ future_check_features(PyFutureFeatures *ff, stmt_ty s, const char *filename)
ff->ff_features |= CO_FUTURE_ABSOLUTE_IMPORT; ff->ff_features |= CO_FUTURE_ABSOLUTE_IMPORT;
} else if (strcmp(feature, FUTURE_WITH_STATEMENT) == 0) { } else if (strcmp(feature, FUTURE_WITH_STATEMENT) == 0) {
ff->ff_features |= CO_FUTURE_WITH_STATEMENT; ff->ff_features |= CO_FUTURE_WITH_STATEMENT;
} else if (strcmp(feature, FUTURE_PRINT_FUNCTION) == 0) {
ff->ff_features |= CO_FUTURE_PRINT_FUNCTION;
} else if (strcmp(feature, "braces") == 0) { } else if (strcmp(feature, "braces") == 0) {
PyErr_SetString(PyExc_SyntaxError, PyErr_SetString(PyExc_SyntaxError,
"not a chance"); "not a chance");

View file

@ -738,18 +738,19 @@ PyRun_InteractiveLoopFlags(FILE *fp, const char *filename, PyCompilerFlags *flag
} }
} }
#if 0
/* compute parser flags based on compiler flags */ /* compute parser flags based on compiler flags */
#define PARSER_FLAGS(flags) \ #define PARSER_FLAGS(flags) \
((flags) ? ((((flags)->cf_flags & PyCF_DONT_IMPLY_DEDENT) ? \ ((flags) ? ((((flags)->cf_flags & PyCF_DONT_IMPLY_DEDENT) ? \
PyPARSE_DONT_IMPLY_DEDENT : 0)) : 0) PyPARSE_DONT_IMPLY_DEDENT : 0)) : 0)
#endif
#if 0 #if 1
/* Keep an example of flags with future keyword support. */ /* Keep an example of flags with future keyword support. */
#define PARSER_FLAGS(flags) \ #define PARSER_FLAGS(flags) \
((flags) ? ((((flags)->cf_flags & PyCF_DONT_IMPLY_DEDENT) ? \ ((flags) ? ((((flags)->cf_flags & PyCF_DONT_IMPLY_DEDENT) ? \
PyPARSE_DONT_IMPLY_DEDENT : 0) \ PyPARSE_DONT_IMPLY_DEDENT : 0) \
| ((flags)->cf_flags & CO_FUTURE_WITH_STATEMENT ? \ | ((flags)->cf_flags & CO_FUTURE_PRINT_FUNCTION ? \
PyPARSE_WITH_IS_KEYWORD : 0)) : 0) PyPARSE_PRINT_IS_FUNCTION : 0)) : 0)
#endif #endif
int int