ipaddress: Interface inheriting from Address
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
participants (4)
-
Andrew Barnert
-
Eric V. Smith
-
Jon Foster
-
Nick Coghlan