RE Python not following this principle. Generally, it does follow the principle. Basic example:
>>> x = ['foo']
>>> x
['foo']
>>> x = (lambda: ['foo'])()
>>> x
['foo']
However, Python defines expressions and statements separately. Since if
branches, while
loops, destructive assignment and other statements cannot be used in lambda
expressions at all, the letter of the Tennent principle does not apply to them. Even so, restricting oneself to using only Python expressions still produces a Turing complete system. So I do not see this as a violation of the principle; or rather, if it does violate the principle, then no language that defines statements and expressions separately can possibly conform to the principle.
Also, if the body of the lambda
expression were capturing a stack trace, or doing other introspection in the VM, that could cause differences. But in my opinion this should not count as a violation. If expr
and (lambda: expr)()
necessarily compile to the same bytecode, then the principle really concerns compilers not semantics; but if they can compile to different bytecode, we should not expect VM state to be identical in each case.
A surprise can be encountered using the comprehension syntax, though I believe this is not a violation of the Tennent principle either. Example:
>>> [x for x in xrange(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [f() for f in [lambda: x for x in xrange(10)]] # surprise!
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
>>> # application of Tennent principle to first expression
... [(lambda: x)() for x in xrange(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [f() for f in [(lambda x: lambda: x)(x) for x in xrange(10)]] # force-rebind x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> map(lambda f:f(), map(lambda x: lambda: x, xrange(10))) # no issue with this form
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
The surprise is a result of how list comprehensions are defined. The above 'surprise' comprehension is equivalent to this code:
>>> result = []
>>> for x in xrange(10):
... # the same, mutable, variable x is used each time
... result.append(lambda: x)
...
>>> r2 = []
>>> for f in result:
... r2.append(f())
...
>>> r2
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
Seen this way, the 'surprise' comprehension above is less surprising, and not a violation of the Tennent principle.