From 5cc322ca2183dd8cf3480eafe0a0187f1f7c566c Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 25 Oct 2011 14:36:29 -0700 Subject: [PATCH] Updated PEP 335, posted 25-Oct-2011. --- pep-0335.txt | 388 ++++++++++++++++++++++++++------------------------- 1 file changed, 198 insertions(+), 190 deletions(-) diff --git a/pep-0335.txt b/pep-0335.txt index 1bc0ba00a..7d8930e58 100644 --- a/pep-0335.txt +++ b/pep-0335.txt @@ -2,13 +2,13 @@ PEP: 335 Title: Overloadable Boolean Operators Version: $Revision$ Last-Modified: $Date$ -Author: Gregory Ewing +Author: Gregory Ewing Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 29-Aug-2004 Python-Version: 3.3 -Post-History: 05-Sep-2004, 30-Sep-2011 +Post-History: 05-Sep-2004, 30-Sep-2011, 25-Oct-2011 Abstract @@ -66,13 +66,23 @@ inconvenient. Examples include: A workaround often suggested is to use the bitwise operators '&', '|' and '~' in place of 'and', 'or' and 'not', but this has some -drawbacks. The precedence of these is different in relation to the -other operators, and they may already be in use for other purposes (as -in example 1). There is also the aesthetic consideration of forcing -users to use something other than the most obvious syntax for what -they are trying to express. This would be particularly acute in the -case of example 3, considering that boolean operations are a staple of -SQL queries. +drawbacks: + +* The precedence of these is different in relation to the other operators, + and they may already be in use for other purposes (as in example 1). + +* It is aesthetically displeasing to force users to use something other + than the most obvious syntax for what they are trying to express. This + would be particularly acute in the case of example 3, considering that + boolean operations are a staple of SQL queries. + +* Bitwise operators do not provide a solution to the problem of + chained comparisons such as 'a < b < c' which involve an implicit + 'and' operation. Such expressions currently cannot be used at all + on data types such as NumPy arrays where the result of a comparison + cannot be treated as having normal boolean semantics; they must be + expanded into something like (a < b) & (b < c), losing a considerable + amount of clarity. Rationale @@ -208,7 +218,7 @@ new operations:: PyObject *PyObject_LogicalAnd1(PyObject *); PyObject *PyObject_LogicalOr1(PyObject *); PyObject *PyObject_LogicalAnd2(PyObject *, PyObject *); - + PyObject *PyObject_LogicalOr2(PyObject *, PyObject *); Alternatives and Optimisations @@ -242,40 +252,40 @@ it currently is. For example, in Python 2.7, :: - if a and b: - statement1 - else: - statement2 + if a and b: + statement1 + else: + statement2 generates :: - LOAD_GLOBAL a - POP_JUMP_IF_FALSE false_branch - LOAD_GLOBAL b - POP_JUMP_IF_FALSE false_branch - - JUMP_FORWARD end_branch - false_branch: - - end_branch: + LOAD_GLOBAL a + POP_JUMP_IF_FALSE false_branch + LOAD_GLOBAL b + POP_JUMP_IF_FALSE false_branch + + JUMP_FORWARD end_branch + false_branch: + + end_branch: Under this proposal as described so far, it would become something like :: - LOAD_GLOBAL a - LOGICAL_AND_1 test - LOAD_GLOBAL b - LOGICAL_AND_2 - test: - POP_JUMP_IF_FALSE false_branch - - JUMP_FORWARD end_branch - false_branch: - - end_branch: + LOAD_GLOBAL a + LOGICAL_AND_1 test + LOAD_GLOBAL b + LOGICAL_AND_2 + test: + POP_JUMP_IF_FALSE false_branch + + JUMP_FORWARD end_branch + false_branch: + + end_branch: This involves executing one extra bytecode in the short-circuiting case and two extra bytecodes in the non-short-circuiting case. @@ -286,16 +296,16 @@ reduced to the same number of bytecodes as the original: :: - LOAD_GLOBAL a - AND1_JUMP true_branch, false_branch - LOAD_GLOBAL b - AND2_JUMP_IF_FALSE false_branch - true_branch: - - JUMP_FORWARD end_branch - false_branch: - - end_branch: + LOAD_GLOBAL a + AND1_JUMP true_branch, false_branch + LOAD_GLOBAL b + AND2_JUMP_IF_FALSE false_branch + true_branch: + + JUMP_FORWARD end_branch + false_branch: + + end_branch: Here, AND1_JUMP performs phase 1 processing as above, and then examines the result. If there is a result, it is popped @@ -350,58 +360,58 @@ Example 1: NumPy Arrays :: - #----------------------------------------------------------------- - # - # This example creates a subclass of numpy array to which - # 'and', 'or' and 'not' can be applied, producing an array - # of booleans. - # - #----------------------------------------------------------------- - - from numpy import array, ndarray - - class BArray(ndarray): - - def __str__(self): - return "barray(%s)" % ndarray.__str__(self) - - def __and2__(self, other): - return self & other - - def __or2__(self, other): - return self & other - - def __not__(self): - return self == 0 - - def barray(*args, **kwds): - return array(*args, **kwds).view(type=BArray) - - a0 = barray([0, 1, 2, 4]) - a1 = barray([1, 2, 3, 4]) - a2 = barray([5, 6, 3, 4]) - a3 = barray([5, 1, 2, 4]) - - print("a0:", a0) - print("a1:", a1) - print("a2:", a2) - print("a3:", a3) - print("not a0:", not a0) - print("a0 == a1 and a2 == a3:", a0 == a1 and a2 == a3) - print("a0 == a1 or a2 == a3:", a0 == a1 or a2 == a3) + #----------------------------------------------------------------- + # + # This example creates a subclass of numpy array to which + # 'and', 'or' and 'not' can be applied, producing an array + # of booleans. + # + #----------------------------------------------------------------- + + from numpy import array, ndarray + + class BArray(ndarray): + + def __str__(self): + return "barray(%s)" % ndarray.__str__(self) + + def __and2__(self, other): + return (self & other) + + def __or2__(self, other): + return (self & other) + + def __not__(self): + return (self == 0) + + def barray(*args, **kwds): + return array(*args, **kwds).view(type = BArray) + + a0 = barray([0, 1, 2, 4]) + a1 = barray([1, 2, 3, 4]) + a2 = barray([5, 6, 3, 4]) + a3 = barray([5, 1, 2, 4]) + + print "a0:", a0 + print "a1:", a1 + print "a2:", a2 + print "a3:", a3 + print "not a0:", not a0 + print "a0 == a1 and a2 == a3:", a0 == a1 and a2 == a3 + print "a0 == a1 or a2 == a3:", a0 == a1 or a2 == a3 Example 1 Output ---------------- :: - a0: barray([0 1 2 4]) - a1: barray([1 2 3 4]) - a2: barray([5 6 3 4]) - a3: barray([5 1 2 4]) - not a0: barray([ True False False False]) - a0 == a1 and a2 == a3: barray([False False False True]) - a0 == a1 or a2 == a3: barray([False False False True]) + a0: barray([0 1 2 4]) + a1: barray([1 2 3 4]) + a2: barray([5 6 3 4]) + a3: barray([5 1 2 4]) + not a0: barray([ True False False False]) + a0 == a1 and a2 == a3: barray([False False False True]) + a0 == a1 or a2 == a3: barray([False False False True]) Example 2: Database Queries @@ -409,112 +419,110 @@ Example 2: Database Queries :: - #----------------------------------------------------------------- - # - # This example demonstrates the creation of a DSL for database - # queries allowing 'and' and 'or' operators to be used to - # formulate the query. - # - #----------------------------------------------------------------- - - class SQLNode: - - def __and2__(self, other): - return SQLBinop("and", self, other) - - def __rand2__(self, other): - return SQLBinop("and", other, self) - - def __eq__(self, other): - return SQLBinop("=", self, other) - - - class Table(SQLNode): - - def __init__(self, name): - self.__tablename__ = name - - def __getattr__(self, name): - return SQLAttr(self, name) - - def __sql__(self): - return self.__tablename__ - - - class SQLBinop(SQLNode): - - def __init__(self, op, opnd1, opnd2): - self.op = op.upper() - self.opnd1 = opnd1 - self.opnd2 = opnd2 - - def __sql__(self): - return "(%s %s %s)" % (sql(self.opnd1), self.op, sql(self.opnd2)) - - - class SQLAttr(SQLNode): - - def __init__(self, table, name): - self.table = table - self.name = name - - def __sql__(self): - return "%s.%s" % (sql(self.table), self.name) - - - class SQLSelect(SQLNode): - - def __init__(self, targets): - self.targets = targets - self.where_clause = None - - def where(self, expr): - self.where_clause = expr - return self - - def __sql__(self): - result = "SELECT %s" % ", ".join(sql(target) for target in self.targets) - if self.where_clause: - result = "%s WHERE %s" % (result, sql(self.where_clause)) - return result - - - def sql(expr): - if isinstance(expr, SQLNode): - return expr.__sql__() - elif isinstance(expr, str): - return "'%s'" % expr.replace("'", "''") - else: - return str(expr) - - - def select(*targets): - return SQLSelect(targets) - - -#-------------------------------------------------------------------------------- - -:: - dishes = Table("dishes") - customers = Table("customers") - orders = Table("orders") - - query = select(customers.name, dishes.price, orders.amount).where( - customers.cust_id == orders.cust_id and orders.dish_id == dishes.dish_id - and dishes.name == "Spam, Eggs, Sausages and Spam") - - print(repr(query)) - print(sql(query)) + #----------------------------------------------------------------- + # + # This example demonstrates the creation of a DSL for database + # queries allowing 'and' and 'or' operators to be used to + # formulate the query. + # + #----------------------------------------------------------------- + + class SQLNode(object): + + def __and2__(self, other): + return SQLBinop("and", self, other) + + def __rand2__(self, other): + return SQLBinop("and", other, self) + + def __eq__(self, other): + return SQLBinop("=", self, other) + + + class Table(SQLNode): + + def __init__(self, name): + self.__tablename__ = name + + def __getattr__(self, name): + return SQLAttr(self, name) + + def __sql__(self): + return self.__tablename__ + + + class SQLBinop(SQLNode): + + def __init__(self, op, opnd1, opnd2): + self.op = op.upper() + self.opnd1 = opnd1 + self.opnd2 = opnd2 + + def __sql__(self): + return "(%s %s %s)" % (sql(self.opnd1), self.op, sql(self.opnd2)) + + + class SQLAttr(SQLNode): + + def __init__(self, table, name): + self.table = table + self.name = name + + def __sql__(self): + return "%s.%s" % (sql(self.table), self.name) + + + class SQLSelect(SQLNode): + + def __init__(self, targets): + self.targets = targets + self.where_clause = None + + def where(self, expr): + self.where_clause = expr + return self + + def __sql__(self): + result = "SELECT %s" % ", ".join([sql(target) for target in self.targets]) + if self.where_clause: + result = "%s WHERE %s" % (result, sql(self.where_clause)) + return result + + + def sql(expr): + if isinstance(expr, SQLNode): + return expr.__sql__() + elif isinstance(expr, str): + return "'%s'" % expr.replace("'", "''") + else: + return str(expr) + + + def select(*targets): + return SQLSelect(targets) + + #----------------------------------------------------------------- + + dishes = Table("dishes") + customers = Table("customers") + orders = Table("orders") + + query = select(customers.name, dishes.price, orders.amount).where( + customers.cust_id == orders.cust_id and orders.dish_id == dishes.dish_id + and dishes.name == "Spam, Eggs, Sausages and Spam") + + print repr(query) + print sql(query) Example 2 Output ---------------- :: - <__main__.SQLSelect object at 0x1cc830> - SELECT customers.name, dishes.price, orders.amount WHERE - (((customers.cust_id = orders.cust_id) AND (orders.dish_id = - dishes.dish_id)) AND (dishes.name = 'Spam, Eggs, Sausages and Spam')) + <__main__.SQLSelect object at 0x1cc830> + SELECT customers.name, dishes.price, orders.amount WHERE + (((customers.cust_id = orders.cust_id) AND (orders.dish_id = + dishes.dish_id)) AND (dishes.name = 'Spam, Eggs, Sausages and Spam')) Copyright