Python is a duck typed language, right? That means you don’t care what value something is, you only care what it quacks like. On the whole Python does pretty well at this. Functions that take lists can usually also take any sort of sequence or iterable; functions that take files can usually also take anything that looks sufficiently file-like (has a read method).
Where it disappoints me is the number of times a builtin requires an int instead of an integer. First a little bit of terminology. When I say “int” I mean the language type int; when I say “integer” I mean any value that has a mathematical value that is an integer. So 7 is an int (in Python); it’s also an integer. 7.0 is an integer, but it’s not an int.
So, I have a suggestion: anything in Python that currently requires an int should accept an integer.
So what sort of things am I talking about?
range. I should be able to go «range(1e6)» to get very long lists:
>>> len(range(1e6)) __main__:1: DeprecationWarning: integer argument expected, got float 1000000
I find this just annoying. 1e6 is an integer. Yes it happens to be stored in a float, but you, Python, ought not to care about that. Hmm, or perhaps you would rather I wrote «1e6» as «1*10**6»?
Another way of constructing very long lists is thwarted:
>>> [0]*1e6 Traceback (most recent call last): File "", line 1, in TypeError: can't multiply sequence by non-int of type 'float'
At least here the error message correctly refers to int instead of integer. But I would say that since 1e6 is an integer then the code has a perfectly clear and unambiguous meaning. So it should work.
Linguistically, what do I mean by an integer? Well, here’s a neat definition:
def isinteger(x): return int(x) == x
In Python we can define an integer to be any value that converts to int and is equal. Observe that this works for int, long, float, decimal, and hopefully any other vaguely numerical type that might be defined in the future:
>>> map(isinteger, [7, 7L, 7.0, 7.1, decimal.Decimal(7)]) [True, True, True, False, True]
If you were actually going to use this in real code, you need to catch exceptions in isinteger:
def isinteger(x):
try:
return int(x) == x
except:
return False
otherwise isinteger would barf on things like «list()».
It’s not just large integers, like 1e6, which happen to be more conveniently written in floating point format, that cause a problem. It can happen, quite reasonably, with smaller numbers. Especially when they are the results of computations.
Let’s say I’m creating a PNG file with bit depth of 2, and I have a sequence of pixel values for a single row. Each pixel is a value «in range(4)»; at some point I’m going to have to pack the pixels into bytes, 4 pixels per byte. It can be more convenient to do this if I round my row length up to a multiple of 4; that way my packer only has to deal with rows that fit into an exact number of bytes. So let’s say the row-length is l and I want it rounded up:
u = math.ceil(l/4.0)*4.0
(in real code, the “4.0″ would probably be a parameter, and I’d probably have to use «float(pixels_per_byte)» to get the right sort of division (or use Python 3.0))
The amount of padding I need to add is therefore «u – l»:
padding = [0]*(u-l)
Alas, this barfs, because u is a float. So I end up having to use what feels to me like a gratuitious int:
padding = [0]*int(u-l)
Note that u came out of «math.ceil» so it’s already an integer; so «u-l» is an integer too.
If you were to take this “accept integers, not just ints” philosophy on board, you might find the following function useful:
def preferint(x):
if isinteger(x):
return int(x)
return x
preferint will change any integer to int, leaving other values unchanged. You can stick «foo = preferint(foo)» at the front of all your functions. Or use those outré decorators.
We can write a prefer that works for any type:
def prefer(value, type):
try:
if type(value) == value:
return type(value)
except:
return value
I suppose there are probably other parts of Python that this applies to, and I could go looking for them, but the two I’ve mentioned are the ones that bug me on a regular basis. List and string indexing would be one, but I can’t remember it annoying me:
>>> 'foo'[2.0] Traceback (most recent call last): File "", line 1, in TypeError: string indices must be integers >>> range(3)[2.0] Traceback (most recent call last): File "", line 1, in TypeError: list indices must be integers
But yes, if I was campaigning for change then that should be changed too. Scanning through the builtins: chr already works (but «chr(7.1)» also works which is a bug), hex needs changing.
PS: (rant for another article) isinteger does not work for «complex(7)». Should it?




