Updated PEP 335, posted 25-Oct-2011.
This commit is contained in:
parent
faf9b4ce77
commit
5cc322ca21
388
pep-0335.txt
388
pep-0335.txt
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue