Wednesday, October 25, 2017

Today in things I didn't know about Python - __call__

If you define a __call__ method in your class it will get called when you call an instance of that class. I didn't know that. That is all.

In [336]: class foo:
    def __init__(self):
        print "in init"
    def __call__(self):
        print "in call"

In [337]: bar = foo()
in init

In [338]: bar()
in call

Monday, May 22, 2017

Today in things I didn't know in Python - sets

I'm slowly and thoroughly reading through the Python documentation. From the amount I'm learning in the process, I clearly should have done this some time ago.

Today I'm reading about sets. I knew a few things about sets:
- Sets are mutable, unordered, collections of unique objects
- You can declare an empty set with 'set()'
- You can declare a set with things in it like this: '{1, 2, 2}'
- You can get the difference, union, intersection, etc. like this:
 - my_set.thing_I_want(other_set)
- How to create a set comprehension

But there were things I didn't know about sets:
- You don't have to use the .operation() version, you can use operators! Some of which are more intuitive to me than others.

Operation Equivalent Result
s.issubset(t) s <= t test whether every element in s is in t
s.issuperset(t) s >= t test whether every element in t is in s
s.union(t) s | t new set with elements from both s and t
s.intersection(t) s & t new set with elements common to s and t
s.difference(t) s - t new set with elements in s but not in t
s.symmetric_difference(t) s ^ t new set with elements in either s or t but not both
s.update(t) s |= t return set s with elements added from t
s.intersection_update(t) s &= t return set s keeping only elements also found in t
s.difference_update(t) s -= t return set s after removing elements found in t
s.symmetric_difference_update(t) s ^= t return set s with elements from s or t but not both

- There is such a thing as immutable set (it's another class)*
- If you do use the operation_name function version for union, intersection, difference, or symmetric_difference, you don't need to cast the second thing to a set (it just needs to be iterable)

*Edited to add that I've realised it's depreciated and replaced by frozenset since 2.6

Monday, April 24, 2017

Can you use a tuple as a dictionary key? Well, that depends.

Today I was asked if you can use a tuple as a dictionary key. I wasn't sure, and looked into it. And the answer seems to be, 'it depends'.

Can you ever use a tuple as a dictionary key? Yes:

In [35]: my_tup = (1, 2)

In [36]: my_dict = {my_tup: 1}

In [37]: my_dict
Out[37]: {(1, 2): 1}

Can you ALWAYS use a tuple as a dictionary key? Nope:

In [38]: my_other_tup = ([1, 2], 2)

In [39]: my_dict = {my_other_tup: 2}
TypeError                 Traceback (most recent call last)
<ipython-input...> in <module>()
----> 1 my_dict = {my_other_tup: 2}

TypeError: unhashable type: 'list'

You can use a tuple as a dictionary key, but only if it's hashable all the way down.

Thursday, March 30, 2017

Don't put nullable fields in your MySQL unique keys

MySQL will allow you to include a nullable field in a unique key. I heartilly recommend that you don't do it.

You may already be aware that NULL isn't equal to anything. That's why we always have 'IS NULL' instead of '= NULL' in our where clauses. When they say not equal to anything, they really mean not equal to anything. Not even itself.

So let's say you have a table that looks a bit like this:

always1 always2 always3 sometimes1 sometimes2
a b c d e
i value1 value2 NULL NULL

and you have a unique key on always2, always3, and sometimes1

You build some logic around the fairly reasonable idea 'we'll try to insert, and if we get a duplicate key error, we'll update instead'.

And then you try to insert
'always2 = "value1", always3 = "value2", sometimes1 = NULL, always1 = m, sometimes2 = n'
expecting this to fail because of a duplicate key error and update the values for always1 and sometimes2 for the third row above. But it won't. Because NULL isn't equal to itself. So you'll end up with a table like this:

always1 always2 always3 sometimes1 sometimes2
a b c d e
i value1 value2 NULL NULL
m value1 value2 NULL n


Tuesday, October 20, 2015

How long is the pound symbol?

In Python 3, the pound symbol, £, is one character long, as you would expect.

Python 3.4.0 (default, Jun 19 2015, 14:20:21)
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> len('£')

But in Python 2 (which I, like many people, have to use for work), it is two characters long.

Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> len('£')

Unless you convert it to unicode.

In [152]: len(u'£')
Out[152]: 1

Worth knowing!

It's also worth being aware that 'str's get a bit funny in Python 2 when you wander out of ascii-land. And iterating through them will not do what you expect. I recommend sticking to unicode where you can.

Sunday, October 4, 2015

"java.lang.RuntimeException: Reader tag must be a symbol" Clojure

Very quick blog post because I couldn't see this anywhere on the top page of results:

If you're learning clojure, and you get this error
'java.lang.RuntimeException: Reader tag must be a symbol'

check if you're putting whitespace between # and ( in an anonymous function literal. Don't do that. 

(If it was something else causing that error for you, please add it to the comments when you work it out)

Sunday, August 9, 2015

Object attribute spacing, floats, and .real, a Python WAT

Recently, while code reviewing a colleague's code, I spotted something like this:
object  .  attribute

I thought 'surely that won't work'. So I went to ipython and did something like this:

In [52]: class MyClass():
   ....:     def __init__(self, attribute):
   ....:         self.attribute = attribute

In [53]: my_object = MyClass(3)

In [54]: my_object.attribute
Out[54]: 3

In [55]: my_object  .  attribute
Out[55]: 3

In [56]: my_object.attribute == my_object . attribute
Out[56]: True

Given that it appeared that python allowed any number of spaces on either side of the full stop (period, if you're speaking American English), which was news to me, I tweeted this:

The ever helpful @brandon_rhodes checked and couldn't see anyplace in pep8 that disallowed it.
But I recommended that my colleague get rid of it anyway because it looked a bit odd.

A few days later I got another reply from @bmispelon

I would have expected the first two would return the same thing and the last one would give a syntax error. But in the first and third statements the full stop is interpreted as a floating point operator, so the first one gives a syntax error, the second gives 1, and the last one gives 1.0.

Knowing all that, can you predict which of the following will give syntax errors, and what the others evaluate to? I suggest using the python interpreter or ipython to check your answers. Have fun!

1) 1.real
2) 1 .real
3) 1. real
4) 1 . real
5) 1..real
6) 1 ..real
7) 1.. real
8) 1 .. real
9) 1. .real
10) 1.     .real
11) 1 . . real

- The full stop in python is both the attribute access operator and the float operator.
- The attribute access operator can have any number of spaces on either side of it.
- The float operator cannot have any spaces on either side of it.
- If there is a full stop immediately after an integer, Python will interpret it as the float operator, even if there are no numbers after it, even if you meant it to be an access operator.