Saturday, May 12, 2018

Captain, the python grammar can't take anymore!

The expressions are going to tear themselves to pieces!
>>> 'a'       .strip    (    ) [    0 ]

Friday, April 20, 2018

DISappearing and

Python has a very rich set of operators that can be overloaded.  From __get__ to __getattr__, __repr__ to __format__, and __complex__ to __iadd__ you can modify almost every behavior of your type.  Conspicuously absent however, are the boolean operators.

This is why Django ORM and SQLAlchemy use bitwise & and | to represent SQL and / or.

Let's take a closer look at how the Python compiler treats these operators:

>>> import dis
>>> dis.dis(lambda: a & b)
  1           0 LOAD_GLOBAL              0 (a)
              3 LOAD_GLOBAL              1 (b)
              6 BINARY_AND
              7 RETURN_VALUE
>>> dis.dis(lambda: a and b)
  1           0 LOAD_GLOBAL              0 (a)
              3 JUMP_IF_FALSE_OR_POP     9
              6 LOAD_GLOBAL              1 (b)
        >>    9 RETURN_VALUE

Not only can you not override the and operator, the Python VM doesn't even have an opcode for it.

In return, Python gives you the semantics that a or b returns not True or False, but either or b (or False if neither is truthy).

Thursday, March 22, 2018

The Zen of Empty Lists

"There should be one-- and preferably only one --obvious way to do it". One of the many philosophies that has earned Python its acclaim.

But while the Zen of Python limits on the number of obvious ways, the Zen of Python says nothing about the boundless freedom of unobvious ways.

Let's empty a list named bucket.

The most obvious way is to simply not. 99 times out of 100, you want to assign a new empty list rather than mutating the old.
bucket = []
But let's say you really wanted to empty it, well the clearest way is clear:
But even the docs say this is equivalent to:
del bucket[:]
I guess that's crossing the obvious line. Of course, it may be more obvious than Norvig's "dumbell" operator:
Actually the slice assignment can take any iterable, so our list can lift plates of many shapes:
If you don't want your list getting ripped and/or cut, maybe keep it warm with Norvig's ski hat:
bucket *=0
The ski hat is of particular interest because it's using a very obvious list feature, much more commonly used than list.clear(). Nobody would bat an eye at:
bucket = [0, 1, 2, 3] * 2
# bucket = [0, 1, 2, 3, 0, 1, 2, 3]
If you multiply any list by 0, you make a new list of length 0. Bizarrely, this is actually true of any multiplier less than 0, too.
bucket *= -3
# bucket = []
Safe to say we are deep in the territory of the unobvious. Is there a syntax we might meditate on to take us further?

Sunday, January 21, 2018

None on the left

A natural default, None is probably the most commonly assigned value in Python. But what happens if you move it to the left side of that equation?

In Python 2:
>>> None = 2
  File "<stdin>", line 1
SyntaxError: cannot assign to None
This is similar to what happens when you assign to a literal:
>>> 1 = 2
  File "<stdin>", line 1
SyntaxError: can't assign to literal
In Python 3 this walk on the wild side will get you a slightly different error:
>>> None = 1
  File "<stdin>", line 1
SyntaxError: can't assign to keyword
None has graduated from useful snowflake to full-blown keyword!

Thursday, November 30, 2017

python3 set literals in 3, 2, 1....

>>> {1,2}.add(3)
>>> {1}.add(2)
>>> {}.add(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'add'

why no empty set literal:

Wednesday, November 29, 2017

a __main__ by any other __name__

$ cat <<EOF >
if __name__ == "__main__":
    import what_is_happening
    print("what is happening?")

$ python
what is happening?

Ambiguous entrypoints can create a maze of state in your program.  In case the above example doesn't seem so bad, lets make it worse.

$ cat <<EOF >
import what_is_happening

def func(): raise what_is_happening.TrustFall('catch me!')

$ cat <<EOF >
import innocent_bystander

class TrustFall(Exception): pass

if __name__ == "__main__":
    except TrustFall:
    except Exception as e:
        print('oops, butterfingers!')
        print('{} is not {}.... what have I done?'.format(
            type(e), TrustFall))

$ python
oops, butterfingers!
<class 'what_is_happening.TrustFall'> is not <class '__main__.TrustFall'>.... what have I done?

What happened?  This is executing the main module twice, a special case of double import.

One solution is to put import guards in all entrypoint scripts:
if __name__ != "__main__":
    raise ImportError('double import of __main__')

UnicodeDecode SyntaxError

When executing a bytecode for the '+' operation, an invalid byte will raise UnicodeDecodeError.  However, when concatenating adjacent string and unicode constants, it will be a SyntaxError.  (I guess because there is not byte-code executing this is happening at compile time.)

>>> u'a' + '\xff'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xff in position 0: ordinal not in range(128)

>>> u'a' '\xff'
  File "<stdin>", line 1
SyntaxError: (unicode error) 'ascii' codec can't decode byte 0xff in position 0: ordinal not in range(128)