One of the changes between python 2 and 3 is that the latter delegates to x.__round__([n]) the operation round(x, n). In python 2, for my classes implementing __round__ and __float__, when I call round(x), x.__float__ is called.
How can I know that round(x) (and not float(x)) was called to reroute the call appropriate in python 2 and obtain a python 3 like behaviour.
thanks
Update: I came up with an ugly hack. I am sure that:
it can be improved.
it will not always work.
The ndigits parameter is not handled in python 2.
it should not be used in production.
but it was interesting to build it anyway. Thanks for all the clarifications.
import dis
import sys
import inspect
import functools
#'CALL_FUNCTION', 'CALL_FUNCTION_VAR', 'CALL_FUNCTION_KW', 'CALL_FUNCTION_VAR_KW'
HUNGRY = (131, 140, 141, 142)
if sys.version < '3':
def is_round(frame):
"""Disassemble a code object."""
co = frame.f_code
lasti = frame.f_lasti
code = co.co_code
i, n = 0, len(code)
extended_arg = 0
free = None
codes = list()
while i < n:
c = code[i]
op = ord(c)
tmp = [op, ]
i += 1
if op >= dis.HAVE_ARGUMENT:
oparg = ord(code[i]) + ord(code[i + 1]) * 256 + extended_arg
extended_arg = 0
i += 2
if op == dis.EXTENDED_ARG:
extended_arg = oparg * long(65536)
tmp.append(oparg)
if op in dis.hasconst:
tmp.append(repr(co.co_consts[oparg]))
elif op in dis.hasname:
tmp.append(co.co_names[oparg])
elif op in dis.hasjrel:
tmp.append(repr(i + oparg)),
elif op in dis.haslocal:
tmp.append(co.co_varnames[oparg])
elif op in dis.hascompare:
tmp.append(dis.cmp_op[oparg])
elif op in dis.hasfree:
if free is None:
free = co.co_cellvars + co.co_freevars
tmp.append(free[oparg])
else:
tmp.append(None)
else:
tmp.append(None)
tmp.append(None)
codes.append(tmp)
if i > lasti:
break
pending = 1
for (opcode, arguments, param) in reversed(codes):
pending -= 1
if opcode in HUNGRY:
pending += arguments + 1
if not pending:
seen = dict(frame.f_builtins)
seen.update(frame.f_globals)
seen.update(frame.f_locals)
while param in seen:
param = seen[param]
return param == round
def round_check(func):
#functools.wraps(func)
def wrapped(self):
if is_round(inspect.currentframe().f_back):
return self.__round__()
return func(self)
return wrapped
else:
def round_check(func):
return func
class X():
#round_check
def __float__(self):
return 1.0
def __round__(self, ndigits=0):
return 2.0
x = X()
r = round
f = float
assert round(x) == 2.0
assert float(x) == 1.0
assert r(x) == 2.0
assert f(x) == 1.0
assert round(float(x)) == 1.0
assert float(round(x)) == 2.0
You could always redefine round to try __round__ first. Unfortunately this isn't a __future__ import, so I don't think there's much else you can do.
>>> class X(object):
... def __round__(self, n=0): return 1.
... def __float__(self): return 2.
...
>>> x = X()
>>> round(x)
2.0
>>> float(x)
2.0
>>> old_round = round
>>> def round(x, n=0):
... try:
... return x.__round__(n)
... except AttributeError:
... return old_round(x)
...
>>>
>>> round(x)
1.0
>>> float(x)
2.0
>>>
Note that this is at least a documented change:
The round() function rounding strategy and return type have changed.
Exact halfway cases are now rounded to the nearest even result instead
of away from zero. (For example, round(2.5) now returns 2 rather than
3.) round(x[, n])() now delegates to x.__round__([n]) instead of always returning a float. It generally returns an integer when called
with a single argument and a value of the same type as x when called
with two arguments.
In Python 2, you can not override what round() does. It does not delegate to __float__; it first calls float() (which in turn delegates to __float__), then does the rounding. There is therefore no point in knowing if __float__ is called from round() or not, as it will do the rounding for you. You can't delegate it.
If you want to implement your own custom rounding in Python 2, you should implement a custom_round() method that does the custom rounding, and use that instead of round().
How can I know that round(x) (and not float(x)) was called to reroute the call appropriate in python 2 and obtain a python 3 like behaviour.
You don't need to. if round(x) calls your __float__ method, it will round the returned floats using the normal logic for floats. You do not need to consider it in the __float__ implementation; you should return the same thing regardless of the calling context. Everything else is the calling context's responsibility.
>>> class hax(object):
... def __float__(self): return 2.6
...
>>> round(hax())
3.0
Related
I've read about and understand floating point round-off issues such as:
>>> sum([0.1] * 10) == 1.0
False
>>> 1.1 + 2.2 == 3.3
False
>>> sin(radians(45)) == sqrt(2) / 2
False
I also know how to work around these issues with math.isclose() and cmath.isclose().
The question is how to apply those work arounds to Python's match/case statement. I would like this to work:
match 1.1 + 2.2:
case 3.3:
print('hit!') # currently, this doesn't match
The key to the solution is to build a wrapper that overrides the __eq__ method and replaces it with an approximate match:
import cmath
class Approximately(complex):
def __new__(cls, x, /, **kwargs):
result = complex.__new__(cls, x)
result.kwargs = kwargs
return result
def __eq__(self, other):
try:
return isclose(self, other, **self.kwargs)
except TypeError:
return NotImplemented
It creates approximate equality tests for both float values and complex values:
>>> Approximately(1.1 + 2.2) == 3.3
True
>>> Approximately(1.1 + 2.2, abs_tol=0.2) == 3.4
True
>>> Approximately(1.1j + 2.2j) == 0.0 + 3.3j
True
Here is how to use it in a match/case statement:
for x in [sum([0.1] * 10), 1.1 + 2.2, sin(radians(45))]:
match Approximately(x):
case 1.0:
print(x, 'sums to about 1.0')
case 3.3:
print(x, 'sums to about 3.3')
case 0.7071067811865475:
print(x, 'is close to sqrt(2) / 2')
case _:
print('Mismatch')
This outputs:
0.9999999999999999 sums to about 1.0
3.3000000000000003 sums to about 3.3
0.7071067811865475 is close to sqrt(2) / 2
Raymond's answer is very fancy and ergonomic, but seems like a lot of magic for something that could be much simpler. A more minimal version would just be to capture the calculated value and just explicitly check whether the things are "close", e.g.:
import math
match 1.1 + 2.2:
case x if math.isclose(x, 3.3):
print(f"{x} is close to 3.3")
case x:
print(f"{x} wasn't close)
I'd also suggest only using cmath.isclose() where/when you actually need it, using appropriate types lets you ensure your code is doing what you expect.
The above example is just the minimum code used to demonstrate the matching and, as pointed out in the comments, could be more easily implemented using a traditional if statement. At the risk of derailing the original question, this is a somewhat more complete example:
from dataclasses import dataclass
#dataclass
class Square:
size: float
#dataclass
class Rectangle:
width: float
height: float
def classify(obj: Square | Rectangle) -> str:
match obj:
case Square(size=x) if math.isclose(x, 1):
return "~unit square"
case Square(size=x):
return f"square, size={x}"
case Rectangle(width=w, height=h) if math.isclose(w, h):
return "~square rectangle"
case Rectangle(width=w, height=h):
return f"rectangle, width={w}, height={h}"
almost_one = 1 + 1e-10
print(classify(Square(almost_one)))
print(classify(Rectangle(1, almost_one)))
print(classify(Rectangle(1, 2)))
Not sure if I'd actually use a match statement here, but is hopefully more representative!
How do I use pre-increment/decrement operators (++, --), just like in C++?
Why does ++count run, but not change the value of the variable?
++ is not an operator. It is two + operators. The + operator is the identity operator, which does nothing. (Clarification: the + and - unary operators only work on numbers, but I presume that you wouldn't expect a hypothetical ++ operator to work on strings.)
++count
Parses as
+(+count)
Which translates to
count
You have to use the slightly longer += operator to do what you want to do:
count += 1
I suspect the ++ and -- operators were left out for consistency and simplicity. I don't know the exact argument Guido van Rossum gave for the decision, but I can imagine a few arguments:
Simpler parsing. Technically, parsing ++count is ambiguous, as it could be +, +, count (two unary + operators) just as easily as it could be ++, count (one unary ++ operator). It's not a significant syntactic ambiguity, but it does exist.
Simpler language. ++ is nothing more than a synonym for += 1. It was a shorthand invented because C compilers were stupid and didn't know how to optimize a += 1 into the inc instruction most computers have. In this day of optimizing compilers and bytecode interpreted languages, adding operators to a language to allow programmers to optimize their code is usually frowned upon, especially in a language like Python that is designed to be consistent and readable.
Confusing side-effects. One common newbie error in languages with ++ operators is mixing up the differences (both in precedence and in return value) between the pre- and post-increment/decrement operators, and Python likes to eliminate language "gotcha"-s. The precedence issues of pre-/post-increment in C are pretty hairy, and incredibly easy to mess up.
Python does not have pre and post increment operators.
In Python, integers are immutable. That is you can't change them. This is because the integer objects can be used under several names. Try this:
>>> b = 5
>>> a = 5
>>> id(a)
162334512
>>> id(b)
162334512
>>> a is b
True
a and b above are actually the same object. If you incremented a, you would also increment b. That's not what you want. So you have to reassign. Like this:
b = b + 1
Many C programmers who used python wanted an increment operator, but that operator would look like it incremented the object, while it actually reassigns it. Therefore the -= and += operators where added, to be shorter than the b = b + 1, while being clearer and more flexible than b++, so most people will increment with:
b += 1
Which will reassign b to b+1. That is not an increment operator, because it does not increment b, it reassigns it.
In short: Python behaves differently here, because it is not C, and is not a low level wrapper around machine code, but a high-level dynamic language, where increments don't make sense, and also are not as necessary as in C, where you use them every time you have a loop, for example.
While the others answers are correct in so far as they show what a mere + usually does (namely, leave the number as it is, if it is one), they are incomplete in so far as they don't explain what happens.
To be exact, +x evaluates to x.__pos__() and ++x to x.__pos__().__pos__().
I could imagine a VERY weird class structure (Children, don't do this at home!) like this:
class ValueKeeper(object):
def __init__(self, value): self.value = value
def __str__(self): return str(self.value)
class A(ValueKeeper):
def __pos__(self):
print 'called A.__pos__'
return B(self.value - 3)
class B(ValueKeeper):
def __pos__(self):
print 'called B.__pos__'
return A(self.value + 19)
x = A(430)
print x, type(x)
print +x, type(+x)
print ++x, type(++x)
print +++x, type(+++x)
TL;DR
Python does not have unary increment/decrement operators (--/++). Instead, to increment a value, use
a += 1
More detail and gotchas
But be careful here. If you're coming from C, even this is different in python. Python doesn't have "variables" in the sense that C does, instead python uses names and objects, and in python ints are immutable.
so lets say you do
a = 1
What this means in python is: create an object of type int having value 1 and bind the name a to it. The object is an instance of int having value 1, and the name a refers to it. The name a and the object to which it refers are distinct.
Now lets say you do
a += 1
Since ints are immutable, what happens here is as follows:
look up the object that a refers to (it is an int with id 0x559239eeb380)
look up the value of object 0x559239eeb380 (it is 1)
add 1 to that value (1 + 1 = 2)
create a new int object with value 2 (it has object id 0x559239eeb3a0)
rebind the name a to this new object
Now a refers to object 0x559239eeb3a0 and the original object (0x559239eeb380) is no longer refered to by the name a. If there aren't any other names refering to the original object it will be garbage collected later.
Give it a try yourself:
a = 1
print(hex(id(a)))
a += 1
print(hex(id(a)))
In python 3.8+ you can do :
(a:=a+1) #same as ++a (increment, then return new value)
(a:=a+1)-1 #same as a++ (return the incremented value -1) (useless)
You can do a lot of thinks with this.
>>> a = 0
>>> while (a:=a+1) < 5:
print(a)
1
2
3
4
Or if you want write somthing with more sophisticated syntaxe (the goal is not optimization):
>>> del a
>>> while (a := (a if 'a' in locals() else 0) + 1) < 5:
print(a)
1
2
3
4
It will return 0 even if 'a' doesn't exist without errors, and then will set it to 1
Python does not have these operators, but if you really need them you can write a function having the same functionality.
def PreIncrement(name, local={}):
#Equivalent to ++name
if name in local:
local[name]+=1
return local[name]
globals()[name]+=1
return globals()[name]
def PostIncrement(name, local={}):
#Equivalent to name++
if name in local:
local[name]+=1
return local[name]-1
globals()[name]+=1
return globals()[name]-1
Usage:
x = 1
y = PreIncrement('x') #y and x are both 2
a = 1
b = PostIncrement('a') #b is 1 and a is 2
Inside a function you have to add locals() as a second argument if you want to change local variable, otherwise it will try to change global.
x = 1
def test():
x = 10
y = PreIncrement('x') #y will be 2, local x will be still 10 and global x will be changed to 2
z = PreIncrement('x', locals()) #z will be 11, local x will be 11 and global x will be unaltered
test()
Also with these functions you can do:
x = 1
print(PreIncrement('x')) #print(x+=1) is illegal!
But in my opinion following approach is much clearer:
x = 1
x+=1
print(x)
Decrement operators:
def PreDecrement(name, local={}):
#Equivalent to --name
if name in local:
local[name]-=1
return local[name]
globals()[name]-=1
return globals()[name]
def PostDecrement(name, local={}):
#Equivalent to name--
if name in local:
local[name]-=1
return local[name]+1
globals()[name]-=1
return globals()[name]+1
I used these functions in my module translating javascript to python.
In Python, a distinction between expressions and statements is rigidly
enforced, in contrast to languages such as Common Lisp, Scheme, or
Ruby.
Wikipedia
So by introducing such operators, you would break the expression/statement split.
For the same reason you can't write
if x = 0:
y = 1
as you can in some other languages where such distinction is not preserved.
Yeah, I missed ++ and -- functionality as well. A few million lines of c code engrained that kind of thinking in my old head, and rather than fight it... Here's a class I cobbled up that implements:
pre- and post-increment, pre- and post-decrement, addition,
subtraction, multiplication, division, results assignable
as integer, printable, settable.
Here 'tis:
class counter(object):
def __init__(self,v=0):
self.set(v)
def preinc(self):
self.v += 1
return self.v
def predec(self):
self.v -= 1
return self.v
def postinc(self):
self.v += 1
return self.v - 1
def postdec(self):
self.v -= 1
return self.v + 1
def __add__(self,addend):
return self.v + addend
def __sub__(self,subtrahend):
return self.v - subtrahend
def __mul__(self,multiplier):
return self.v * multiplier
def __div__(self,divisor):
return self.v / divisor
def __getitem__(self):
return self.v
def __str__(self):
return str(self.v)
def set(self,v):
if type(v) != int:
v = 0
self.v = v
You might use it like this:
c = counter() # defaults to zero
for listItem in myList: # imaginary task
doSomething(c.postinc(),listItem) # passes c, but becomes c+1
...already having c, you could do this...
c.set(11)
while c.predec() > 0:
print c
....or just...
d = counter(11)
while d.predec() > 0:
print d
...and for (re-)assignment into integer...
c = counter(100)
d = c + 223 # assignment as integer
c = c + 223 # re-assignment as integer
print type(c),c # <type 'int'> 323
...while this will maintain c as type counter:
c = counter(100)
c.set(c + 223)
print type(c),c # <class '__main__.counter'> 323
EDIT:
And then there's this bit of unexpected (and thoroughly unwanted) behavior,
c = counter(42)
s = '%s: %d' % ('Expecting 42',c) # but getting non-numeric exception
print s
...because inside that tuple, getitem() isn't what used, instead a reference to the object is passed to the formatting function. Sigh. So:
c = counter(42)
s = '%s: %d' % ('Expecting 42',c.v) # and getting 42.
print s
...or, more verbosely, and explicitly what we actually wanted to happen, although counter-indicated in actual form by the verbosity (use c.v instead)...
c = counter(42)
s = '%s: %d' % ('Expecting 42',c.__getitem__()) # and getting 42.
print s
There are no post/pre increment/decrement operators in python like in languages like C.
We can see ++ or -- as multiple signs getting multiplied, like we do in maths (-1) * (-1) = (+1).
E.g.
---count
Parses as
-(-(-count)))
Which translates to
-(+count)
Because, multiplication of - sign with - sign is +
And finally,
-count
A straight forward workaround
c = 0
c = (lambda c_plusplus: plusplus+1)(c)
print(c)
1
No more typing
c = c + 1
Also, you could just write
c++
and finish all your code and then do search/replace for "c++", replace with "c=c+1". Just make sure regular expression search is off.
Extending Henry's answer, I experimentally implemented a syntax sugar library realizing a++: hdytto.
The usage is simple. After installing from PyPI, place sitecustomize.py:
from hdytto import register_hdytto
register_hdytto()
in your project directory. Then, make main.py:
# coding: hdytto
a = 5
print(a++)
print(++a)
b = 10 - --a
print(b--)
and run it by PYTHONPATH=. python main.py. The output will be
5
7
4
hdytto replaces a++ as ((a:=a+1)-1) when decoding the script file, so it works.
Defaults are parsed at definition. So this will not work
def f(x, func = f):
if x<1:
return x
else:
return x + func(x-1)
I did find a way, though. I start with some dummy function
def a(x):
return x
def f(x, func=a):
if x < 1:
return x
else:
return x + func(x-1)
And then issue
f.__defaults__ = (f,)
Obviously awkward. Is there another way or is this bad python? If it's bad, can you explain why? Where will it break things?
In any case, it works:
In [99]: f(10)
Out[99]: 55
In [100]: f(10, f)
Out[100]: 55
In [101]: f(10, lambda x : 2*x)
Out[101]: 28
To elaborate on Andrea Corbellini's suggestion, you can do something like this:
def f(x, func=None):
if func is None:
func = f
if x < 1:
return x
else:
return x + func(x-1)
It is a pretty standard idiom in Python to implement the actual defaults inside of the function and defining the default parameter as None (or some private sentinel object in case None is valid input), due to problems with mutable default values such as list objects.
I'm wondering if there exists a python module that would allow me to do something like this:
x = MagicNumber()
x.value = 3
y = 2 * (x + 2) ** 2 - 8
print y # 42
x.value = 2
print y # 24
So MagicNumber would implement all the special operator methods, and they would all return instances of MagicNumber, while keeping track of what operations are performed. Is there such a class?
EDIT: clarification
I want to use this in a module that should remember a lot of parameters of some arbitrary calculation that the user wishes to perform. So the user will set the parameters and then use them to produce his result. Then if he decides he'd like to alter a parameter, the change is reflected in his result immediately. So a very simplified usage session with only one parameter instance would look like:
p = MyParams()
p.distance = 13.4 # I use __getattr__ and __setattr__ such that
p.speed = 3.14 # __getattr__ returns MagicNumber instances
time = p.distance / p.speed
EDIT 2: more clarification
Okay, I'll do what I should have done from the start. I'll provide context.
You are an engineer and you're to produce a LaTeX document detailing the workings and properties of some prototype gadget. It is a task you'll do repeatedly for different prototypes. You write a small LaTeX python interface. For each prototype you create a python module that generates the requisite document. In it you type out the LaTeX code while calculating variables as they are needed, so that the calculations are in context. After a while you notice two problems:
The number of variables and parameters makes locals a mess and the variable names are hard to remember. You should group them into categories to keep track of them all.
You sometimes need to redo the same calculation, which is spread over the last couple of chapters and a dozen lines, with one or more parameters changed. You should find some way to avoid code duplication.
Out of this problem comes the original question.
Something like this?
import operator
MAKE_BINARY = lambda opfn : lambda self,other : BinaryOp(self, asMagicNumber(other), opfn)
MAKE_RBINARY = lambda opfn : lambda self,other : BinaryOp(asMagicNumber(other), self, opfn)
class MagicNumber(object):
__add__ = MAKE_BINARY(operator.add)
__sub__ = MAKE_BINARY(operator.sub)
__mul__ = MAKE_BINARY(operator.mul)
__radd__ = MAKE_RBINARY(operator.add)
__rsub__ = MAKE_RBINARY(operator.sub)
__rmul__ = MAKE_RBINARY(operator.mul)
# __div__ = MAKE_BINARY(operator.div)
# __rdiv__ = MAKE_RBINARY(operator.div)
__truediv__ = MAKE_BINARY(operator.truediv)
__rtruediv__ = MAKE_RBINARY(operator.truediv)
__floordiv__ = MAKE_BINARY(operator.floordiv)
__rfloordiv__ = MAKE_RBINARY(operator.floordiv)
def __neg__(self, other):
return UnaryOp(self, lambda x : -x)
#property
def value(self):
return self.eval()
class Constant(MagicNumber):
def __init__(self, value):
self.value_ = value
def eval(self):
return self.value_
class Parameter(Constant):
def __init__(self):
super(Parameter, self).__init__(0.0)
def setValue(self, v):
self.value_ = v
value = property(fset=setValue, fget=lambda self: self.value_)
class BinaryOp(MagicNumber):
def __init__(self, op1, op2, operation):
self.op1 = op1
self.op2 = op2
self.opn = operation
def eval(self):
return self.opn(self.op1.eval(), self.op2.eval())
class UnaryOp(MagicNumber):
def __init__(self, op1, operation):
self.op1 = op1
self.operation = operation
def eval(self):
return self.opn(self.op1.eval())
asMagicNumber = lambda x : x if isinstance(x, MagicNumber) else Constant(x)
And here it is in action:
x = Parameter()
# integer division
y = 2*x*x + 3*x - x//2
# or floating division
# y = 2*x*x + 3*x - x/2
x.value = 10
print(y.value)
# prints 225
x.value = 20
print(y.value)
# prints 850
# compute a series of x-y values for the function
print([(x.value, y.value) for x.value in range(5)])
# prints [(0, 0), (1, 5), (2, 13), (3, 26), (4, 42)]
You could give sympy, a computer algebra system written in Python, give a try.
E.g.
>>> from sympy import Symbol
>>> x = Symbol('x')
>>> y = 2 * (x + 2) ** 2 - 8
>>> y
2*(x + 2)**2 - 8
>>> y.subs(x,3)
42
>>> y.subs(x,2)
24
Isn't this called a function? This may sound like a simple answer, but I mean it sincerely.
def y(x):
return 2 * (x + 2) ** 2 - 8
Aren't you thinking in the wrong direction with this one?
To address the clarification:
class MyParams():
distance = 0.0
speed = 0.0
def __call__(self):
return self.distance / self.speed
p = MyParams()
p.distance = 13.4 # These are properties
p.speed = 3.14 # where __get__ returns MagicNumber instances
time = p() # 4.26
p.speed = 2.28
time = p() # 5.88
I guess I'm more in favor of this type of a solution, although I see the benefit in the sympy module. Preference, I guess.
>>> magic = lambda x: eval('2 * (x + 2) ** 2 - 8')
>>> magic(2)
24
>>> magic(3)
42
>>> magic = lambda x: eval('x ** 4')
>>> magic(2)
16
>>> magic(3)
81
I think the difficulty is in "how to keep operators priority" rather than implementing a class.
I suggest to look at a different notation (like Reverse Polish Notation) that may help in getting rid of priority issues...
A friend brought this to my attention, and after I pointed out an oddity, we're both confused.
Python's docs, say, and have said since at least 2.5.1 (haven't checked further back:
Comparisons can be chained arbitrarily, e.g., x < y <= z is equivalent to x < y and y <= z, except that y is evaluated only once (but in both cases z is not evaluated at all when x < y is found to be false).
Our confusion lies in the meaning of "y is evaluated only once".
Given a simple but contrived class:
class Magic(object):
def __init__(self, name, val):
self.name = name
self.val = val
def __lt__(self, other):
print("Magic: Called lt on {0}".format(self.name))
if self.val < other.val:
return True
else:
return False
def __le__(self, other):
print("Magic: Called le on {0}".format(self.name))
if self.val <= other.val:
return True
else:
return False
We can produce this result:
>>> x = Magic("x", 0)
>>> y = Magic("y", 5)
>>> z = Magic("z", 10)
>>>
>>> if x < y <= z:
... print ("More magic.")
...
Magic: Called lt on x
Magic: Called le on y
More magic.
>>>
This certainly looks like 'y' is, in a traditional sense "evaluated" twice -- once when x.__lt__(y) is called and performs a comparison on it, and once when y.__le__(z) is called.
So with this in mind, what exactly do the Python docs mean when they say "y is evaluated only once"?
The 'expression' y is evaluated once. I.e., in the following expression, the function is executed only one time.
>>> def five():
... print 'returning 5'
... return 5
...
>>> 1 < five() <= 5
returning 5
True
As opposed to:
>>> 1 < five() and five() <= 5
returning 5
returning 5
True
In the context of y being evaluated, y is meant as an arbitrary expression that could have side-effects. For instance:
class Foo(object):
#property
def complain(self):
print("Evaluated!")
return 2
f = Foo()
print(1 < f.complain < 3) # Prints evaluated once
print(1 < f.complain and f.complain < 3) # Prints evaluated twice