Updated PEP 335, posted 25-Oct-2011.
This commit is contained in:
parent
faf9b4ce77
commit
5cc322ca21
310
pep-0335.txt
310
pep-0335.txt
|
@ -2,13 +2,13 @@ PEP: 335
|
|||
Title: Overloadable Boolean Operators
|
||||
Version: $Revision$
|
||||
Last-Modified: $Date$
|
||||
Author: Gregory Ewing <greg.ewing@canterbury.ac.nz>
|
||||
Author: Gregory Ewing <greg@cosc.canterbury.ac.nz>
|
||||
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
|
||||
<code for statement1>
|
||||
JUMP_FORWARD end_branch
|
||||
false_branch:
|
||||
<code for statement2>
|
||||
end_branch:
|
||||
LOAD_GLOBAL a
|
||||
POP_JUMP_IF_FALSE false_branch
|
||||
LOAD_GLOBAL b
|
||||
POP_JUMP_IF_FALSE false_branch
|
||||
<code for statement1>
|
||||
JUMP_FORWARD end_branch
|
||||
false_branch:
|
||||
<code for statement2>
|
||||
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
|
||||
<code for statement1>
|
||||
JUMP_FORWARD end_branch
|
||||
false_branch:
|
||||
<code for statement2>
|
||||
end_branch:
|
||||
LOAD_GLOBAL a
|
||||
LOGICAL_AND_1 test
|
||||
LOAD_GLOBAL b
|
||||
LOGICAL_AND_2
|
||||
test:
|
||||
POP_JUMP_IF_FALSE false_branch
|
||||
<code for statement1>
|
||||
JUMP_FORWARD end_branch
|
||||
false_branch:
|
||||
<code for statement2>
|
||||
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:
|
||||
<code for statement1>
|
||||
JUMP_FORWARD end_branch
|
||||
false_branch:
|
||||
<code for statement2>
|
||||
end_branch:
|
||||
LOAD_GLOBAL a
|
||||
AND1_JUMP true_branch, false_branch
|
||||
LOAD_GLOBAL b
|
||||
AND2_JUMP_IF_FALSE false_branch
|
||||
true_branch:
|
||||
<code for statement1>
|
||||
JUMP_FORWARD end_branch
|
||||
false_branch:
|
||||
<code for statement2>
|
||||
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.
|
||||
#
|
||||
#-----------------------------------------------------------------
|
||||
#-----------------------------------------------------------------
|
||||
#
|
||||
# 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
|
||||
from numpy import array, ndarray
|
||||
|
||||
class BArray(ndarray):
|
||||
class BArray(ndarray):
|
||||
|
||||
def __str__(self):
|
||||
return "barray(%s)" % ndarray.__str__(self)
|
||||
def __str__(self):
|
||||
return "barray(%s)" % ndarray.__str__(self)
|
||||
|
||||
def __and2__(self, other):
|
||||
return self & other
|
||||
def __and2__(self, other):
|
||||
return (self & other)
|
||||
|
||||
def __or2__(self, other):
|
||||
return self & other
|
||||
def __or2__(self, other):
|
||||
return (self & other)
|
||||
|
||||
def __not__(self):
|
||||
return self == 0
|
||||
def __not__(self):
|
||||
return (self == 0)
|
||||
|
||||
def barray(*args, **kwds):
|
||||
return array(*args, **kwds).view(type=BArray)
|
||||
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])
|
||||
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)
|
||||
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.
|
||||
#
|
||||
#-----------------------------------------------------------------
|
||||
#-----------------------------------------------------------------
|
||||
#
|
||||
# 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:
|
||||
class SQLNode(object):
|
||||
|
||||
def __and2__(self, other):
|
||||
return SQLBinop("and", self, other)
|
||||
def __and2__(self, other):
|
||||
return SQLBinop("and", self, other)
|
||||
|
||||
def __rand2__(self, other):
|
||||
return SQLBinop("and", other, self)
|
||||
def __rand2__(self, other):
|
||||
return SQLBinop("and", other, self)
|
||||
|
||||
def __eq__(self, other):
|
||||
return SQLBinop("=", self, other)
|
||||
def __eq__(self, other):
|
||||
return SQLBinop("=", self, other)
|
||||
|
||||
|
||||
class Table(SQLNode):
|
||||
class Table(SQLNode):
|
||||
|
||||
def __init__(self, name):
|
||||
self.__tablename__ = name
|
||||
def __init__(self, name):
|
||||
self.__tablename__ = name
|
||||
|
||||
def __getattr__(self, name):
|
||||
return SQLAttr(self, name)
|
||||
def __getattr__(self, name):
|
||||
return SQLAttr(self, name)
|
||||
|
||||
def __sql__(self):
|
||||
return self.__tablename__
|
||||
def __sql__(self):
|
||||
return self.__tablename__
|
||||
|
||||
|
||||
class SQLBinop(SQLNode):
|
||||
class SQLBinop(SQLNode):
|
||||
|
||||
def __init__(self, op, opnd1, opnd2):
|
||||
self.op = op.upper()
|
||||
self.opnd1 = opnd1
|
||||
self.opnd2 = opnd2
|
||||
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))
|
||||
def __sql__(self):
|
||||
return "(%s %s %s)" % (sql(self.opnd1), self.op, sql(self.opnd2))
|
||||
|
||||
|
||||
class SQLAttr(SQLNode):
|
||||
class SQLAttr(SQLNode):
|
||||
|
||||
def __init__(self, table, name):
|
||||
self.table = table
|
||||
self.name = name
|
||||
def __init__(self, table, name):
|
||||
self.table = table
|
||||
self.name = name
|
||||
|
||||
def __sql__(self):
|
||||
return "%s.%s" % (sql(self.table), self.name)
|
||||
def __sql__(self):
|
||||
return "%s.%s" % (sql(self.table), self.name)
|
||||
|
||||
|
||||
class SQLSelect(SQLNode):
|
||||
class SQLSelect(SQLNode):
|
||||
|
||||
def __init__(self, targets):
|
||||
self.targets = targets
|
||||
self.where_clause = None
|
||||
def __init__(self, targets):
|
||||
self.targets = targets
|
||||
self.where_clause = None
|
||||
|
||||
def where(self, expr):
|
||||
self.where_clause = expr
|
||||
return self
|
||||
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__(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 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)
|
||||
def select(*targets):
|
||||
return SQLSelect(targets)
|
||||
|
||||
#-----------------------------------------------------------------
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
dishes = Table("dishes")
|
||||
customers = Table("customers")
|
||||
orders = Table("orders")
|
||||
|
||||
::
|
||||
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")
|
||||
|
||||
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))
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue