Wandering Thoughts archives

2010-06-29

Converting between IPv4 addresses and 32-bit integers in Python

When you write code that deals with IPv4 addresses, every so often you wind up in situations where you want to convert addresses in conventional text form to unsigned 32-bit integers and manipulate them in various ways (and then convert the results back to normal notation). This is something I've been dealing with recently, so I've been working on this today.

Python doesn't have any standard library functions that directly do these conversions. You can write your own code for this (but it's just long enough to be annoying), or alternately you can glue together the socket and struct modules to get what you want.

socket.inet_aton doesn't directly convert an IP address to a 32-bit integer; instead it convert it to a 32-bit packed binary string. To get an actual integer, we need to unpack the string with struct.unpack. The one gotcha is that inet_aton returns results in network byte order, so if we want our IP addresses as natural numbers (in host byte order) we need to tell struct to do this conversion. To convert the other way we can use socket.inet_ntoa, which also has the same requirement of working in network byte order.

So we wind up with the following small and almost symmetric routines:

def ip_to_uint32(ip):
   t = socket.inet_aton(ip)
   return struct.unpack("!I", t)[0]

def uint32_to_ip(ipn):
   t = struct.pack("!I", ipn)
   return socket.inet_ntoa(t)

If you want your unsigned 32-bit integer IPv4 addresses in network byte order, leave out the ! in the unpack() and pack() format strings. Probably you don't, though, unless you are doing peculiar things with them.

(Given what this does, there is no point in using socket.inet_pton instead of inet_aton; we intrinsically deal only with IPv4 addresses anyways, so extra generality is actually counterproductive.)

PS: if you slap together code like this, it's a good idea to test it with simple IPv4 addresses that you can easily work out the numeric values for by hand. I almost skipped doing this and thus almost missed that inet_aton returns results in network byte order instead of host byte order. Such a mistake is entertaining, but only from a suitable distance.

IpUint32Mapping written at 22:59:48; Add Comment

2010-06-25

Python class definitions can be nested

Here is something that I was surprised to discover the other day: you can nest class definitions inside other things. In a way this is just what I should have expected given that class is an executable statement in Python, just like def is (cf), but I still found it surprising to see it actually happen.

At least in the simple case, the only thing that nesting a class inside another class does is make the first class's name harder to get to; the inner class has no special access to the outer class's namespace or the like. I tend to think that the uses for this are at best somewhat obscure, since it is not very often that you need one class to appear inside another class's namespace.

Class definitions nested inside functions are potentially more useful, because they act as closures. Both the class definition itself and its methods can access variables from the outer function, just as inner functions can. As usual, these outer function variables are read only (well, non-assignable).

(Note that as with all closures, this is a great way to accidentally leak memory by keeping objects alive when you didn't expect it.)

In both cases, you can't tell that these are nested inner classes by any straightforward inspection of the resulting class or instances. They don't have special names or any other marker, and will show up in debugging output as if they were ordinary classes defined at the module level. Given class definitions nested inside functions, this can create the amusing situation where two things that claim to be the same class in repr() output are not; each time you call the function you create a different class, although they all have the same name.

(Since class definitions are evaluated only once, a class nested inside another class does not create this situation.)

NestedClasses written at 02:16:02; Add Comment

2010-06-04

How to set up your module exceptions to be useful

Suppose that you are writing a Python module that has exceptions as part of its interface, and you want them to actually be useful to people. From the perspective of a sometimes grumpy user of module exceptions, here is my opinions on what you should do:

  • all exceptions that you expect people to actually catch should descend from a common ancestor class, so that people can just do 'except YourException, e:' and be done with it.

    If you have an exception you raise for internal errors and other impossible situations, do not make it part of this hierarchy; otherwise people will catch it when you don't want them to.

  • people will mostly use your error exceptions by turning them into strings (and printing them out). Make sure that this gives useful information.

  • you need to document what fields your exceptions have. If you do not, people will just str() your exceptions and ignore any information that this misses.
  • if you have multiple exception classes, try to have as many common fields in them as possible. As a pragmatic thing, expect people to mostly ignore unique fields unless they have important information that people need to act on.

  • do not ever reuse or subclass standard exceptions, especially IOError and OSError.

    (If you are ever tempted to break this rule, make absolutely sure that your versions of these exceptions are exactly identical to the real ones.)

  • if you call code from other modules, you should capture and wrap up their exceptions, turning them into exceptions of your own. This is much easier for your callers to deal with, since they don't have to know what other modules you use (or care if you change what modules you use).

In general, I would say that you should avoid the temptation to get too complex in error handling. Put yourself in the shoes of the typical person using your module; are you going to care about anything beyond the fact that some sort of an error happened? Generally not. Then you just need a single exception class with a useful string error message, and you're done.

(I admit that I don't always stick to this simple model for my own code; I sometimes have an irrepressible urge to subclass my overall module error class so that I can distinguish this sort of error from that sort of error and so on. Then I never use any of these features.)

UsefulModuleExceptions written at 23:19:10; Add Comment

By day for June 2010: 4 25 29; before June; after June.

Page tools: See As Normal.
Search:
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.