• Last edited on Aug 7, 2017, 5:57:43 PM by Kevin

is not

is not is a single binary operator, and has behavior different than using is and not separated.

2 is not None
#result: True
2 is (not None)
#result: False

== in chained comparisons

Used in conjunction with > or <, == can comprise an expression whose result differs from any variant that uses parentheses.

(42 > 23) == True
#effectively equivalent to `True == True`, which is True

42 > (23 == True)
#effectively equivalent to `42 > False`, which is True

42 > 23 == True
#effectively equivalent to `42 > 23 and 23 == True`, which is False

Lazy binding

A lambda expression that uses a local variable will not bind to the value the variable had when the lambda was created. Instead, it uses the value the variable has when the lambda is executed.

x = 23
f = lambda: x
x = 42
print f()
#expected result: 23
#actual result: 42

funcs = []
for i in range(10):
    funcs.append(lambda: i**2)
print [f() for f in funcs]
#expected result: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
#actual result: [81, 81, 81, 81, 81, 81, 81, 81, 81, 81]

To get the desired result you can bind the variable as a default argument to the lambda.

x = 23
f = lambda x=x: x
x = 42
print f()
# 23

funcs = []
for i in range(10):
    funcs.append(lambda i=i: i**2)
    print [f() for f in funcs]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Default Mutable Argument

The default value in a function definition is only evaluated once, at declaration time. This means that a mutable default argument has persistent state across multiple invocations.

def frob(x=[]):
    x.append(1)
    print len(x),

frob()
frob()

#expected result: 1, 1
#actual result: 1, 2

Evaluation time discrepancy

In a generator expression, the in clause is evaluated at declaration time, but the conditional clause is evaluated at run time.

seq = [4, 8, 15]
g = (x for x in seq if seq.count(x) > 0)
seq = [23, 8, 42]
print(list(g))
#expected result: [4, 8, 15]
#or perhaps [23, 8, 42]
#actual result: [8]

Persistent state of multiple references to one iterator

Calling next on an iterator will advance all instances of that iterator, not just the one you called it on.

#returns True if exactly one element of `iterable` is True
#courtesy of Jon Clements at http://stackoverflow.com/a/16801605/1903116
def single_true(iterable):
    i = iter(iterable)
    return any(i) and not any(i)

#returns True if exactly one element of `iterable` is False
def single_false(iterable):
    i = iter(iterable)
    return not all(i) and all(i)

#splits a list into evenly sized chunks
def chunk(input, size):
    return map(None, *([iter(input)] * size))

#ok, these examples don't demonstrate how it can be used to confuse
#the reader. But I promise it can.
#todo: be more evil.

Identical looking variable names

Some unicode characters look identical to ascii ones, but are considered distinct by the interpreter.

value = 42 #ascii e
valuŠµ = 23 #cyrillic e
print(value)
#expected result: 23
#actual result: 42

Tab indentation variability

A tab can be equivalent to a variable number of spaces depending on its position on the line. See http://docs.python.org/2/reference/lexical_analysis.html#indentation

def frob(x):
    if x:
        print "A"
    #this next line is indented with a tab. 
    #although it looks four spaces long in the editor, 
    #in this context the interpreter thinks it's eight spaces long.
    print "B"
frob(False)
#expected result: "B"
#actual result: no output

yield from in generator expressions

Normally, yield from is only used inside function declarations. But the interpreter won’t complain if you use it in a generator. You can even put it in the conditional clause.

g = [(1,2),(2,4),(3,8),(4,16)]
print list((None for g in g if (yield from g) and False))
#result: [1, 2, 2, 4, 3, 8, 4, 16]

__dunder__ methods don’t look at instance level

class-level dunder methods are invoked by operators, even if you override them at the instance level.

class Widget():
    def __init__(self):
        self.__eq__ = lambda self, other: True
    def __eq__(self, other):
        return False

print(Widget() == Widget())
#expected: True
#actual: False

Name resolution ignores class scope

Scopes nested inside class definition ignore names bound at the class level. For example, a generator expression has its own scope. Starting in 3.X, list comprehensions also have their own scope. See this

x = 23
class Fred:
    x = 42
    y = (x for i in range(10))
print(list(Fred.y)[0])
#expected result: 42
#actual result: 23

#3.X only:
x = 23
class Fred:
    x = 42
    y = [x for i in range(10)]
print(Fred.y[0])
#expected result: 42
#actual result: 23 (42 in 2.X)

sys.argv[0] is the filename of the current Python file.

When using sys.argv to accept command line arguments to Python scripts the first argument in sys.argv is the current Python filename. This argument is passed by default and can cause issues if you’re using filename arguments in the command line.

The below code will write over the Python file with the text 'Hello World!'. This snippet was inspired by Al.Sal <3

import sys

with open(sys.argv[0], 'w') as f:
    f.write('Hello World!')