[Python-ideas] ipaddress: Interface inheriting from Address

Jon Foster jon at jon-foster.co.uk
Thu Aug 22 00:55:20 CEST 2013


Hi all,

I'd like to propose changing ipaddress.IPv[46]Interface to not inherit from IPv[46]Address.
(And I hope I've got the right mailing list?)

The "ipaddress" module is currently in the standard library on a provisional basis, so "backwards 
incompatible changes may occur".  But obviously there needs to be a very good reason.  I think 
there is.

The problem is that IPv[46]Interface can't currently be passed to a function that expects an 
IPv[46]Address, because it redefines str(), ".exploded", ".compressed", "==" and "<" in an 
incompatible way.

E.g suppose we have:
   >>> from ipaddress import IPv4Address, IPv4Interface
   >>> my_dict = {IPv4Address('1.2.3.4'): 'Hello'}

Obviously lookup with an IPv4Address works:
   >>> addr = IPv4Address('1.2.3.4')
   >>> print(my_dict.get(addr))
   Hello

But with the IPv4Interface subclass, the lookup doesn't work:
   >>> intf = IPv4Interface('1.2.3.4/24')
   >>> print(my_dict.get(intf))
   None

And that's because equality has been redefined:
   >>> IPv4Address('1.2.3.4') == IPv4Interface('1.2.3.4/24')
   False

When doing inheritance the usual expectation is that "Functions that use references to base classes 
must be able to use objects of derived classes without knowing it".  This is called the "Liskov 
Substitution Principle".  And IPv4Interface isn't following that principle.

More informally, since IPv4Interface inherits from IPv4Address you'd expect that an IPv4Interface 
"is a" IPv4Address, but it really isn't.  It's really a "has a" relationship, which is more 
commonly done by giving IPv4Interface a property that's an IPv4Address.

This problem can't be solved easily by just redefining "==".  If we let IPv4Address('1.2.3.4') == 
IPv4Interface('1.2.3.4/24'), and IPv4Address('1.2.3.4') == IPv4Interface('1.2.3.4/16'), then to 
keep the normal transitive behaviour of equals we'd have to let IPv4Interface('1.2.3.4/16') == 
IPv4Interface('1.2.3.4/24'), and that seems wrong.  Where people actually know they're comparing 
IPv4Interface objects they will really want to compare both the IP address and netmask.

My proposed solution is just to change IPv4Interface to not inherit from IPv4Address. 
IPv4Interface already has a "ip" property that gives you the IP address as a proper IPv4Address 
object.  So code written for Python 3.4, using the "ip" property, would be backward-compatible with 
Python 3.3.  And people could obviousy start writing code that way today, to be compatible with 
both Python 3.3 and Python 3.4.

I.e. people should write code like this:
   >>> extracted_address = intf.ip
   >>> extracted_address
   IPv4Address('1.2.3.4')
   >>> print(my_dict.get(extracted_address))
   Hello

I'm proposing that IPv4Interface would not have a (public) base class, and the only remaining 
properties and methods on IPv4Interface would be:
   __init__()
   ip
   network
   compressed
   exploded
   with_prefixlen
   with_netmask
   with_hostmask
   __eq__() / __ne__()
   __hash__()
   __lt__(), __gt__() etc
   __str__()
And all these properties and methods would do exactly what they do today.

I.e. IPv4Interface becomes just a container for the "ip" and "network" fields, plus the parsing 
code in __init__() and a few formatting and comparison functions.  This is a lot simpler to 
understand than the current design.

With this design, IPv4Interface wouldn't be comparable to IPv4Address or IPv4Network objects.  If 
the user wants to do a comparison like that, they can get the .ip or .network properties and 
compare that.  Explicit is better than implicit.

If there is interest in this idea, I'll try to put together a patch next week.

Thanks to anyone who's read this far.  What do you think?

Kind regards,

Jon


More information about the Python-ideas mailing list