[Python-Dev] Adding C ternary select (a?b:c) to Python?

Guido van Rossum guido@CNRI.Reston.VA.US
Sun, 30 Jan 2000 08:32:18 -0500


Dear developers,

Eric Raymond has sent me the following patch, which adds conditional
expressions to Python.  I'd like to hear opinions on whether this is a
good thing to add to Python, and whether this is the right syntax.

I am a bit skeptical about whether this is sufficiently Pythonic, but
on the other hand there have always been requests for such a feature,
and the existing solutions are ugly:

  a and b or c

only works when you know for sure that b will never be false, and

  (a and [b] or [c])[0] 

is dead ugly...

--Guido van Rossum (home page: http://www.python.org/~guido/)

Subject: Ternary select -- here it is.
From: "Eric S. Raymond" <esr@thyrsus.com>
To: Guido Van Rossum <guido@CNRI.Reston.Va.US>
Date: Sat, 29 Jan 2000 17:40:31 -0500
X-Eric-Conspiracy: There is no conspiracy

Dang, Guido, this was *fun*!

This patch extends the Python interpreter to support the C ternary
operator, and documents the extension in the Reference Manual.  The
implementation is dead simple and robust: it's adapted from the
existing code for if statements, and adds or changes less than 70 lines
of code all told.

Diffs between last version checked in and current workfile(s):

--- Grammar/Grammar	2000/01/28 17:10:18	1.1
+++ Grammar/Grammar	2000/01/29 22:14:05
@@ -61,7 +61,8 @@
 except_clause: 'except' [test [',' test]]
 suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
 
-test: and_test ('or' and_test)* | lambdef
+test: bool_test ['?' bool_test ':' bool_test]
+bool_test: and_test ('or' and_test)* | lambdef
 and_test: not_test ('and' not_test)*
 not_test: 'not' not_test | comparison
 comparison: expr (comp_op expr)*
--- Include/token.h	2000/01/28 17:38:55	1.1
+++ Include/token.h	2000/01/29 01:27:00
@@ -74,10 +74,11 @@
 #define LEFTSHIFT	34
 #define RIGHTSHIFT	35
 #define DOUBLESTAR	36
+#define QUERY		37
 /* Don't forget to update the table _PyParser_TokenNames in tokenizer.c! */
-#define OP		37
-#define ERRORTOKEN	38
-#define N_TOKENS	39
+#define OP		38
+#define ERRORTOKEN	39
+#define N_TOKENS	34
 
 /* Special definitions for cooperation with parser */
 
--- Modules/parsermodule.c	2000/01/28 18:03:27	1.1
+++ Modules/parsermodule.c	2000/01/29 22:13:45
@@ -945,6 +945,7 @@
 #define	validate_star(ch)	validate_terminal(ch,	    STAR, "*")
 #define	validate_vbar(ch)	validate_terminal(ch,	    VBAR, "|")
 #define validate_doublestar(ch)	validate_terminal(ch, DOUBLESTAR, "**")
+#define validate_query(ch)	validate_terminal(ch,	   QUERY, "?")
 #define validate_dot(ch)	validate_terminal(ch,	     DOT, ".")
 #define	validate_name(ch, str)	validate_terminal(ch,	    NAME, str)
 
@@ -963,7 +964,8 @@
 VALIDATER(exec_stmt);		VALIDATER(compound_stmt);
 VALIDATER(while);		VALIDATER(for);
 VALIDATER(try);			VALIDATER(except_clause);
-VALIDATER(test);		VALIDATER(and_test);
+VALIDATER(test);
+VALIDATER(bool_test);		VALIDATER(and_test);
 VALIDATER(not_test);		VALIDATER(comparison);
 VALIDATER(comp_op);		VALIDATER(expr);
 VALIDATER(xor_expr);		VALIDATER(and_expr);
@@ -1829,12 +1831,34 @@
 }   /* validate_except_clause() */
 
 
+/*  bool_test ( | bool_test ? bool_test )
+ *
+ */
 static int
 validate_test(tree)
     node *tree;
 {
+    if (!validate_ntype(tree, test))
+	return 0;
+    else if (NCH(tree) == 1)
+	return(validate_bool_test(CHILD(tree, 0)));
+    else if (validate_numnodes(tree, 5, "expr"))
+    {
+	return validate_bool_test(CHILD(tree, 0))
+	    && validate_query(CHILD(tree, 1))
+	    && validate_bool_test(CHILD(tree, 2))
+	    && validate_colon(CHILD(tree, 3))
+	    && validate_bool_test(CHILD(tree, 4));
+    }
+}   /* validate_test() */
+
+
+static int
+validate_bool_test(tree)
+    node *tree;
+{
     int nch = NCH(tree);
-    int res = validate_ntype(tree, test) && is_odd(nch);
+    int res = validate_ntype(tree, bool_test) && is_odd(nch);
 
     if (res && (TYPE(CHILD(tree, 0)) == lambdef))
 	res = ((nch == 1)
@@ -1848,7 +1872,7 @@
     }
     return (res);
 
-}   /* validate_test() */
+}   /* validate_bool_test() */
 
 
 static int
--- Parser/tokenizer.c	2000/01/28 17:37:48	1.1
+++ Parser/tokenizer.c	2000/01/29 01:27:26
@@ -99,6 +99,7 @@
 	"LEFTSHIFT",
 	"RIGHTSHIFT",
 	"DOUBLESTAR",
+	"QUERY",
 	/* This table must match the #defines in token.h! */
 	"OP",
 	"<ERRORTOKEN>",
@@ -384,6 +385,7 @@
 	case '}':	return RBRACE;
 	case '^':	return CIRCUMFLEX;
 	case '~':	return TILDE;
+	case '?':	return QUERY;
 	default:	return OP;
 	}
 }
--- Python/compile.c	2000/01/28 23:17:19	1.1
+++ Python/compile.c	2000/01/29 22:19:29
@@ -1698,11 +1698,11 @@
 }
 
 static void
-com_test(c, n)
+com_bool_test(c, n)
 	struct compiling *c;
 	node *n;
 {
-	REQ(n, test); /* and_test ('or' and_test)* | lambdef */
+	REQ(n, bool_test); /* and_test ('or' and_test)* | lambdef */
 	if (NCH(n) == 1 && TYPE(CHILD(n, 0)) == lambdef) {
 		PyObject *v;
 		int i;
@@ -1738,6 +1738,32 @@
 }
 
 static void
+com_test(c, n)
+	struct compiling *c;
+	node *n;
+{
+	int op;
+	REQ(n, test);
+	com_bool_test(c, CHILD(n, 0));
+
+	/* is there a following ternary operator? */
+	/* XXX optimize the compilation when the guard is a constant */
+	if (NCH(n) == 5)
+	{
+	    int anchor1 = 0, anchor2 = 0;
+	    com_addfwref(c, JUMP_IF_FALSE, &anchor2);
+	    com_addbyte(c, POP_TOP);
+	    com_pop(c, 1);
+	    com_node(c, CHILD(n, 2));
+	    com_addfwref(c, JUMP_FORWARD, &anchor1);
+	    com_backpatch(c, anchor2);
+	    com_addbyte(c, POP_TOP);
+	    com_node(c, CHILD(n, 4));
+	    com_backpatch(c, anchor1);
+	}
+}
+
+static void
 com_list(c, n, toplevel)
 	struct compiling *c;
 	node *n;
@@ -2931,6 +2957,9 @@
 		break;
 	case test:
 		com_test(c, n);
+		break;
+	case bool_test:
+		com_bool_test(c, n);
 		break;
 	case and_test:
 		com_and_test(c, n);
--- Doc/ref/ref5.tex	2000/01/29 21:28:13	1.1
+++ Doc/ref/ref5.tex	2000/01/29 22:00:02
@@ -764,7 +764,7 @@
 \section{Boolean operations\label{Booleans}}
 \indexii{Boolean}{operation}
 
-Boolean operations have the lowest priority of all Python operations:
+Boolean operations have the lowest priority of all Python binary operations:
 
 \begin{verbatim}
 expression:     or_test | lambda_form
@@ -832,6 +832,24 @@
 def make_incrementor(increment):
     return lambda x, n=increment: x+n
 \end{verbatim}
+
+\section{Select\label{select}}
+\index{select}
+
+The select operator is a ternary operator with lower priority than
+boolean operations (and thus lower priority than all other binary and
+unary operators).
+
+\begin{verbatim}
+select_expr:    xor_expr | xor_expr "?" xor_expr ":" xor_expr
+\end{verbatim}
+
+If its first operand is nonempty, the value of a select operation is
+its second operand; otherwise the value is the third operand.
+
+(The semantics and precedence level of select are intended to be
+unsurprising to programmers familiar with \C's ternary select
+operator.)
 
 \section{Expression lists\label{exprlists}}
 \indexii{expression}{list}

End of diffs.
-- 
		<a href="http://www.tuxedo.org/~esr">Eric S. Raymond</a>

You know why there's a Second Amendment?  In case the government fails to
follow the first one.
         -- Rush Limbaugh, in a moment of unaccustomed profundity 17 Aug 1993