gh-141647: fix IP address/network comparison methods#141842
gh-141647: fix IP address/network comparison methods#141842picnixz wants to merge 4 commits intopython:mainfrom
Conversation
e88a7c0 to
bd5eeda
Compare
bd5eeda to
ca36ea4
Compare
serhiy-storchaka
left a comment
There was a problem hiding this comment.
Please make benchmarks for equality and order comparison.
|
So I'm a bit surprised but here are the numbers: This benchmark uses the Benchmark scriptimport ipaddress
import itertools
import operator
import pyperf
seen = set()
def bench_eq(runner, label, x, y, seen):
name = f"{label}.__eq__[{x},{y}]"
if name not in seen:
runner.bench_func(name, operator.__eq__, x, y)
seen.add(name)
def bench_lt(runner, label, x, y, seen):
name = f"{label}.__lt__[{x},{y}]"
if name not in seen:
runner.bench_func(name, operator.__lt__, x, y)
seen.add(name)
if __name__ == "__main__":
runner = pyperf.Runner()
args = runner.parse_args()
seen = set()
for x, y in itertools.combinations_with_replacement(
[
ipaddress.IPv4Address("1.2.3.4"),
ipaddress.IPv4Address("5.6.7.8"),
], 2
):
bench_eq(runner, "address", x, y, seen)
bench_lt(runner, "address", x, y, seen)
for x, y in itertools.combinations_with_replacement(
[
ipaddress.IPv4Interface("192.0.2.0"),
ipaddress.IPv4Interface("192.0.8.0"),
ipaddress.IPv4Interface("192.0.2.0/24"),
ipaddress.IPv4Interface("192.0.8.0/32"),
], 2
):
bench_eq(runner, "interface", x, y, seen)
bench_lt(runner, "interface", x, y, seen)
for x, y in itertools.combinations_with_replacement(
[
ipaddress.IPv4Network("192.0.2.0"),
ipaddress.IPv4Network("192.0.8.0"),
ipaddress.IPv4Network("192.0.2.0/24"),
ipaddress.IPv4Network("192.0.8.0/32"),
], 2
):
bench_eq(runner, "network", x, y, seen)
bench_lt(runner, "network", x, y, seen)
for x, y in itertools.combinations_with_replacement(
[
ipaddress.IPv4Address("1.2.3.4"),
ipaddress.IPv4Address("5.6.7.8"),
ipaddress.IPv4Interface("1.2.3.4"),
ipaddress.IPv4Interface("5.6.7.8"),
ipaddress.IPv4Interface("1.2.3.4/24"),
ipaddress.IPv4Interface("5.6.7.8/32"),
], 2
):
bench_lt(runner, "mixed", x, y, seen) |
serhiy-storchaka
left a comment
There was a problem hiding this comment.
I think there is a documentation and an implementation bugs in collapse_addresses(). They cab considered a separate issue(s).
| nets = [] | ||
|
|
||
| # split IP addresses and networks | ||
| # split IP addresses/interfaces and networks |
There was a problem hiding this comment.
According to the documentation, all input objects should be networks. The code supports addresses (this is undocumented behavior). Support of interfaces is a new feature.
There was a problem hiding this comment.
I think there is an online docs issue as well. We allow ordering with interfaces and adresses (or some weird mixed comparisons that I do not remember)
| if nets: | ||
| _check_ip_version(nets[-1], ip) |
There was a problem hiding this comment.
What will happen if we have separate networks and addresses of different versions? There is a bug here, this is a separate issue.
There was a problem hiding this comment.
AFAIR different versions are not allowed with comparisons. So I guess Inshould consider the first item being compared to as the one who gives the version to use.
| # compare interfaces by their network first | ||
| return (self.network < other.network | ||
| or (self.network == other.network and address_less)) | ||
| return address_less |
There was a problem hiding this comment.
Should be False if unassociated address is considered less than all interfaces.
| # should __contains__ actually implement subnet_of() | ||
| # and supernet_of() instead? |
There was a problem hiding this comment.
A subset is usually not considered to be an element of the superset.
There is a weird behavior of strings, but it is an exception.
There was a problem hiding this comment.
Yeah but since there was a branch with "dealing with other networks" I wondered why we even supported that check. Asking whether network A is contained in another network B rings like "A is a subnet of B" to me.
There was a problem hiding this comment.
This branch is just to prevent interpretation of a network as an address, since they share common attributes and base classes. It is a guard against incorrect application of the Liskov substitution principle.
There was a problem hiding this comment.
Oh I see! ok I will remove that comment then now that I know why we did this check.
There was a problem hiding this comment.
If _BaseNetwork is not a subclass of _BaseAddress, we can remove this check.
There was a problem hiding this comment.
_BaseNetwork is a subclass of _IPAddressBase. However, people doing net_a in net_b will now get a AttributeError (probably) instead of "False". So maybe it's better to keep the check.
| # should __contains__ actually implement subnet_of() | ||
| # and supernet_of() instead? |
There was a problem hiding this comment.
If _BaseNetwork is not a subclass of _BaseAddress, we can remove this check.
| self.version == other.version | ||
| and (other._ip & self.netmask._ip) == self.network_address._ip | ||
| ) | ||
| return NotImplemented |
There was a problem hiding this comment.
Is NotImplemented supported for __contains__?
There was a problem hiding this comment.
Mmh, I was under the impression that dunders needed to raise NotImplemented for the generic unsupported case but maybe a TypeError should be raised directly?
There was a problem hiding this comment.
Mmh, I was under the impression that dunders needed to raise NotImplemented for the generic unsupported case but maybe a TypeError should be raised directly?
I think this patch should cover the cases. I'm unsure whether this will break more stuff or not but I do know that using
<and__lt__return different boolean results which can break many things.@serhiy-storchaka I really want a second eye here so PTAL whenever you've got time. TiA!
.versionattribute inipaddressclasses break many methods #141647