Quick answer: For modern Python, migrate old ipaddr-style code to the standard-library ipaddress module. Keep address and network objects explicit, validate input at the boundary, and test parsing, membership, formatting, and IPv4/IPv6 behavior before removing the old dependency.

ipaddr is a legacy Python package for working with IPv4 and IPv6 addresses, networks, and ranges. For new Python code, prefer the standard library ipaddress module.
The main references are ipaddr on PyPI, the official Python ipaddress documentation, and the ipaddress HOWTO.
The reason to know ipaddr today is maintenance. You may see it in older projects, but modern Python includes ipaddress without an extra dependency.
The standard module can parse addresses, validate input, compare address versions, check network membership, and iterate through networks. It supports IPv4 and IPv6 with similar APIs.
When migrating, update imports first, then compare the behavior of address creation, network parsing, and formatting in tests. Network strictness and exception types may differ from old assumptions.
Use ip_address() for one host address and ip_network() for a network or CIDR range. Use strict=False when user input may include host bits in a network string and you want Python to normalize the network.
Do not mix the concepts of an address and a network. An address such as 192.0.2.10 identifies one endpoint. A network such as 192.0.2.0/24 describes a range of addresses. Keeping those objects separate makes validation and membership checks much clearer.
For old codebases, migrate in small steps. Add tests around the current behavior, replace imports and constructors, then verify string output, containment checks, and exception handling. This is safer than changing all IP parsing code at once.
The ipaddress module also exposes useful properties such as is_private, is_loopback, and is_global. Use those properties instead of hard-coded string-prefix checks.
If a project still pins ipaddr, check why before removing it. Old Python 2 compatibility, serialized object formats, or tests that expect exact exception messages may require a staged migration.
For new Python 3 projects, the standard library module is the practical default because it avoids an extra dependency and receives updates with Python itself.
Create An IP Address
Use ipaddress.ip_address() for either IPv4 or IPv6 input.
import ipaddress
address = ipaddress.ip_address("192.0.2.10")
print(address)
print(address.version)
The object represents one address.
The version attribute tells you whether it is IPv4 or IPv6.
This replaces many legacy ipaddr.IPAddress(...) uses in new code.
Address objects compare and sort by numeric address value, which is more reliable than comparing raw strings.
Validate User Input
Invalid addresses raise ValueError.
import ipaddress
raw_value = "999.1.1.1"
try:
address = ipaddress.ip_address(raw_value)
except ValueError:
print("invalid address")
Use this pattern at input boundaries.
It is clearer than manual string splitting or regular expressions.
Keep the original input for error messages and logs when validation fails.
Validation should happen before storing, routing, or comparing user-provided address text.

Create A Network
Use ip_network() for CIDR network strings.
import ipaddress
network = ipaddress.ip_network("192.0.2.0/24")
print(network)
print(network.num_addresses)
The result represents the whole network, not one host.
num_addresses reports the size of the network.
Use network objects for containment checks, iteration, and subnet logic.
Network objects can also be converted back to strings when you need a normalized CIDR representation.
Check Network Membership
Address objects can be tested against network objects.
import ipaddress
address = ipaddress.ip_address("192.0.2.25")
network = ipaddress.ip_network("192.0.2.0/24")
print(address in network)
This is useful for allowlists, deny lists, and routing-style checks.
Make sure the address and network versions match.
An IPv4 address will not belong to an IPv6 network.
When input can be either IPv4 or IPv6, parse both values first and compare their version attributes before making assumptions.
Handle Host Bits In Networks
By default, network strings must describe a real network address.
import ipaddress
network = ipaddress.ip_network("192.0.2.25/24", strict=False)
print(network)
strict=False normalizes host bits to the network address.
Use it for user input when the intended behavior is normalization.
Keep strict=True when invalid network strings should fail.
This choice is a policy decision. Normalization is convenient for forms and logs, while strict parsing is better when configuration files should reject ambiguous input.

Iterate Through Hosts
Use hosts() to iterate usable host addresses.
import ipaddress
network = ipaddress.ip_network("192.0.2.0/30")
for host in network.hosts():
print(host)
For IPv4 networks, this excludes the network and broadcast addresses where applicable.
Do not iterate large networks unless you really need every address.
A large IPv6 network can represent an enormous number of addresses, so prefer containment checks and subnet calculations over full iteration.
For audits, search for old constructor names and add tests around every parsing path. Address parsing often sits near security, logging, or routing decisions, so small behavior changes deserve explicit review.
In short, treat ipaddr as legacy maintenance knowledge, use ipaddress for new Python code, validate input with parser functions, and test migration behavior around networks, strictness, and formatting.
Map The Old API
Inventory imports and operations first: address parsing, network construction, containment, subnetting, iteration, and string formatting may have different names or edge behavior after migration.

Use Standard Objects
ipaddress.ip_address and ipaddress.ip_network return objects with explicit IPv4 or IPv6 semantics. Keep these objects through the policy or routing layer instead of repeatedly parsing strings.
Validate At The Boundary
Treat input from files, users, and network services as untrusted. Catch ValueError, reject ambiguous host bits when a strict network is required, and record a useful sanitized error.
Check Network Semantics
Membership, subnet_of, supernet_of, hosts, and address arithmetic are useful but have precise IPv4 and IPv6 rules. Test the boundary address, network address, broadcast behavior, and prefix length.

Preserve Formatting Contracts
If downstream systems compare strings, define whether compressed IPv6, exploded IPv6, prefix notation, or integer forms are required. Object equality and display formatting are separate concerns.
Test The Migration
Run old and new implementations against valid addresses, invalid input, IPv4, IPv6, networks with host bits, overlapping ranges, empty policy lists, and serialized configuration before switching production traffic.
Use the official Python ipaddress documentation for object behavior and exceptions. Related Python Pool references include testing and configuration mappings.
For related address workflows, compare migration tests, network policy mappings, and address lists before replacing ipaddr.
Frequently Asked Questions
What replaced the Python ipaddr module?
For modern Python projects, the standard-library ipaddress module is the usual replacement for third-party ipaddr-style address and network operations.
How do I import ipaddress in Python?
Use import ipaddress, then construct objects such as ipaddress.ip_address() or ipaddress.ip_network() from validated input.
Does ipaddress support IPv4 and IPv6?
Yes. It provides separate IPv4 and IPv6 address and network classes with common operations such as membership and comparison.
How should I handle invalid addresses?
Catch the documented ValueError from parsing, report the input problem, and avoid treating malformed user input as a trusted network rule.