Updated PEP 335, posted 25-Oct-2011.

This commit is contained in:
Guido van Rossum 2011-10-25 14:36:29 -07:00
parent faf9b4ce77
commit 5cc322ca21
1 changed files with 198 additions and 190 deletions

View File

@ -2,13 +2,13 @@ PEP: 335
Title: Overloadable Boolean Operators Title: Overloadable Boolean Operators
Version: $Revision$ Version: $Revision$
Last-Modified: $Date$ Last-Modified: $Date$
Author: Gregory Ewing <greg.ewing@canterbury.ac.nz> Author: Gregory Ewing <greg@cosc.canterbury.ac.nz>
Status: Draft Status: Draft
Type: Standards Track Type: Standards Track
Content-Type: text/x-rst Content-Type: text/x-rst
Created: 29-Aug-2004 Created: 29-Aug-2004
Python-Version: 3.3 Python-Version: 3.3
Post-History: 05-Sep-2004, 30-Sep-2011 Post-History: 05-Sep-2004, 30-Sep-2011, 25-Oct-2011
Abstract Abstract
@ -66,13 +66,23 @@ inconvenient. Examples include:
A workaround often suggested is to use the bitwise operators '&', '|' A workaround often suggested is to use the bitwise operators '&', '|'
and '~' in place of 'and', 'or' and 'not', but this has some and '~' in place of 'and', 'or' and 'not', but this has some
drawbacks. The precedence of these is different in relation to the drawbacks:
other operators, and they may already be in use for other purposes (as
in example 1). There is also the aesthetic consideration of forcing * The precedence of these is different in relation to the other operators,
users to use something other than the most obvious syntax for what and they may already be in use for other purposes (as in example 1).
they are trying to express. This would be particularly acute in the
case of example 3, considering that boolean operations are a staple of * It is aesthetically displeasing to force users to use something other
SQL queries. 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 Rationale
@ -208,7 +218,7 @@ new operations::
PyObject *PyObject_LogicalAnd1(PyObject *); PyObject *PyObject_LogicalAnd1(PyObject *);
PyObject *PyObject_LogicalOr1(PyObject *); PyObject *PyObject_LogicalOr1(PyObject *);
PyObject *PyObject_LogicalAnd2(PyObject *, PyObject *); PyObject *PyObject_LogicalAnd2(PyObject *, PyObject *);
PyObject *PyObject_LogicalOr2(PyObject *, PyObject *);
Alternatives and Optimisations Alternatives and Optimisations
@ -242,40 +252,40 @@ it currently is. For example, in Python 2.7,
:: ::
if a and b: if a and b:
statement1 statement1
else: else:
statement2 statement2
generates generates
:: ::
LOAD_GLOBAL a LOAD_GLOBAL a
POP_JUMP_IF_FALSE false_branch POP_JUMP_IF_FALSE false_branch
LOAD_GLOBAL b LOAD_GLOBAL b
POP_JUMP_IF_FALSE false_branch POP_JUMP_IF_FALSE false_branch
<code for statement1> <code for statement1>
JUMP_FORWARD end_branch JUMP_FORWARD end_branch
false_branch: false_branch:
<code for statement2> <code for statement2>
end_branch: end_branch:
Under this proposal as described so far, it would become something like Under this proposal as described so far, it would become something like
:: ::
LOAD_GLOBAL a LOAD_GLOBAL a
LOGICAL_AND_1 test LOGICAL_AND_1 test
LOAD_GLOBAL b LOAD_GLOBAL b
LOGICAL_AND_2 LOGICAL_AND_2
test: test:
POP_JUMP_IF_FALSE false_branch POP_JUMP_IF_FALSE false_branch
<code for statement1> <code for statement1>
JUMP_FORWARD end_branch JUMP_FORWARD end_branch
false_branch: false_branch:
<code for statement2> <code for statement2>
end_branch: end_branch:
This involves executing one extra bytecode in the short-circuiting This involves executing one extra bytecode in the short-circuiting
case and two extra bytecodes in the non-short-circuiting case. 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 LOAD_GLOBAL a
AND1_JUMP true_branch, false_branch AND1_JUMP true_branch, false_branch
LOAD_GLOBAL b LOAD_GLOBAL b
AND2_JUMP_IF_FALSE false_branch AND2_JUMP_IF_FALSE false_branch
true_branch: true_branch:
<code for statement1> <code for statement1>
JUMP_FORWARD end_branch JUMP_FORWARD end_branch
false_branch: false_branch:
<code for statement2> <code for statement2>
end_branch: end_branch:
Here, AND1_JUMP performs phase 1 processing as above, Here, AND1_JUMP performs phase 1 processing as above,
and then examines the result. If there is a result, it is popped 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 # This example creates a subclass of numpy array to which
# 'and', 'or' and 'not' can be applied, producing an array # 'and', 'or' and 'not' can be applied, producing an array
# of booleans. # of booleans.
# #
#----------------------------------------------------------------- #-----------------------------------------------------------------
from numpy import array, ndarray from numpy import array, ndarray
class BArray(ndarray): class BArray(ndarray):
def __str__(self): def __str__(self):
return "barray(%s)" % ndarray.__str__(self) return "barray(%s)" % ndarray.__str__(self)
def __and2__(self, other): def __and2__(self, other):
return self & other return (self & other)
def __or2__(self, other): def __or2__(self, other):
return self & other return (self & other)
def __not__(self): def __not__(self):
return self == 0 return (self == 0)
def barray(*args, **kwds): def barray(*args, **kwds):
return array(*args, **kwds).view(type=BArray) return array(*args, **kwds).view(type = BArray)
a0 = barray([0, 1, 2, 4]) a0 = barray([0, 1, 2, 4])
a1 = barray([1, 2, 3, 4]) a1 = barray([1, 2, 3, 4])
a2 = barray([5, 6, 3, 4]) a2 = barray([5, 6, 3, 4])
a3 = barray([5, 1, 2, 4]) a3 = barray([5, 1, 2, 4])
print("a0:", a0) print "a0:", a0
print("a1:", a1) print "a1:", a1
print("a2:", a2) print "a2:", a2
print("a3:", a3) print "a3:", a3
print("not a0:", not a0) print "not a0:", not a0
print("a0 == a1 and a2 == a3:", a0 == a1 and a2 == a3) print "a0 == a1 and a2 == a3:", a0 == a1 and a2 == a3
print("a0 == a1 or a2 == a3:", a0 == a1 or a2 == a3) print "a0 == a1 or a2 == a3:", a0 == a1 or a2 == a3
Example 1 Output Example 1 Output
---------------- ----------------
:: ::
a0: barray([0 1 2 4]) a0: barray([0 1 2 4])
a1: barray([1 2 3 4]) a1: barray([1 2 3 4])
a2: barray([5 6 3 4]) a2: barray([5 6 3 4])
a3: barray([5 1 2 4]) a3: barray([5 1 2 4])
not a0: barray([ True False False False]) not a0: barray([ True False False False])
a0 == a1 and a2 == a3: barray([False False False True]) a0 == a1 and a2 == a3: barray([False False False True])
a0 == a1 or a2 == a3: barray([False False False True]) a0 == a1 or a2 == a3: barray([False False False True])
Example 2: Database Queries Example 2: Database Queries
@ -409,112 +419,110 @@ Example 2: Database Queries
:: ::
#----------------------------------------------------------------- #-----------------------------------------------------------------
# #
# This example demonstrates the creation of a DSL for database # This example demonstrates the creation of a DSL for database
# queries allowing 'and' and 'or' operators to be used to # queries allowing 'and' and 'or' operators to be used to
# formulate the query. # formulate the query.
# #
#----------------------------------------------------------------- #-----------------------------------------------------------------
class SQLNode: class SQLNode(object):
def __and2__(self, other): def __and2__(self, other):
return SQLBinop("and", self, other) return SQLBinop("and", self, other)
def __rand2__(self, other): def __rand2__(self, other):
return SQLBinop("and", other, self) return SQLBinop("and", other, self)
def __eq__(self, other): def __eq__(self, other):
return SQLBinop("=", self, other) return SQLBinop("=", self, other)
class Table(SQLNode): class Table(SQLNode):
def __init__(self, name): def __init__(self, name):
self.__tablename__ = name self.__tablename__ = name
def __getattr__(self, name): def __getattr__(self, name):
return SQLAttr(self, name) return SQLAttr(self, name)
def __sql__(self): def __sql__(self):
return self.__tablename__ return self.__tablename__
class SQLBinop(SQLNode): class SQLBinop(SQLNode):
def __init__(self, op, opnd1, opnd2): def __init__(self, op, opnd1, opnd2):
self.op = op.upper() self.op = op.upper()
self.opnd1 = opnd1 self.opnd1 = opnd1
self.opnd2 = opnd2 self.opnd2 = opnd2
def __sql__(self): def __sql__(self):
return "(%s %s %s)" % (sql(self.opnd1), self.op, sql(self.opnd2)) return "(%s %s %s)" % (sql(self.opnd1), self.op, sql(self.opnd2))
class SQLAttr(SQLNode): class SQLAttr(SQLNode):
def __init__(self, table, name): def __init__(self, table, name):
self.table = table self.table = table
self.name = name self.name = name
def __sql__(self): def __sql__(self):
return "%s.%s" % (sql(self.table), self.name) return "%s.%s" % (sql(self.table), self.name)
class SQLSelect(SQLNode): class SQLSelect(SQLNode):
def __init__(self, targets): def __init__(self, targets):
self.targets = targets self.targets = targets
self.where_clause = None self.where_clause = None
def where(self, expr): def where(self, expr):
self.where_clause = expr self.where_clause = expr
return self return self
def __sql__(self): def __sql__(self):
result = "SELECT %s" % ", ".join(sql(target) for target in self.targets) result = "SELECT %s" % ", ".join([sql(target) for target in self.targets])
if self.where_clause: if self.where_clause:
result = "%s WHERE %s" % (result, sql(self.where_clause)) result = "%s WHERE %s" % (result, sql(self.where_clause))
return result return result
def sql(expr): def sql(expr):
if isinstance(expr, SQLNode): if isinstance(expr, SQLNode):
return expr.__sql__() return expr.__sql__()
elif isinstance(expr, str): elif isinstance(expr, str):
return "'%s'" % expr.replace("'", "''") return "'%s'" % expr.replace("'", "''")
else: else:
return str(expr) return str(expr)
def select(*targets): def select(*targets):
return SQLSelect(targets) return SQLSelect(targets)
#-----------------------------------------------------------------
#--------------------------------------------------------------------------------
dishes = Table("dishes")
:: customers = Table("customers")
dishes = Table("dishes") orders = Table("orders")
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
query = select(customers.name, dishes.price, orders.amount).where( and dishes.name == "Spam, Eggs, Sausages and Spam")
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)
print(repr(query))
print(sql(query))
Example 2 Output Example 2 Output
---------------- ----------------
:: ::
<__main__.SQLSelect object at 0x1cc830> <__main__.SQLSelect object at 0x1cc830>
SELECT customers.name, dishes.price, orders.amount WHERE SELECT customers.name, dishes.price, orders.amount WHERE
(((customers.cust_id = orders.cust_id) AND (orders.dish_id = (((customers.cust_id = orders.cust_id) AND (orders.dish_id =
dishes.dish_id)) AND (dishes.name = 'Spam, Eggs, Sausages and Spam')) dishes.dish_id)) AND (dishes.name = 'Spam, Eggs, Sausages and Spam'))
Copyright Copyright