Question: I have written a small program to convert IPv6 addresses to ints and back, and I have managed to beat built-in ipaddress.IPv6Address in terms of performance. import re MAX_IPV6 = 2**128-1 DIGITS = set("0123456789abcdef") def parse_ipv6(ip: str) -> int: assert isinstance(ip, str) and len(ip) <= 39 segments = ip.lower().split(":") l, n, p, count, compressed = len(segments), 0, 7, 0, False last = l - 1 for i, s in enumerate(segments): assert count <= 8 and len(s) <= 4 and not set(s) - DIGITS if not s: if i in (0, last): continue assert not compressed p = l - i - 2 compressed = True else: n += int(s, 16) << p*16 p -= 1 count += 1 return n def to_ipv6(n: int, compress=False) -> str: assert isinstance(n, int) and 0 <= n <= MAX_IPV6 ip = '{:032_x}'.format(n).replace('_', ':') if not compress: return ip return re.sub('0{1,3}([\da-f]+)', '\\1', ip)  I am currently trying to implement compression, namely find the longest run of two alternating characters 0 and :, and replace the first occurrence of it with ::. For example, given this address: 'abcd:0:ef12::a:0:0', parse_ipv6('abcd:0:ef12::a:0:0') gives this number: 228362408209208931942454293848746098688, but to_ipv6(parse_ipv6('abcd:0:ef12::a:0:0'), 1) gives this: 'abcd:0:ef12:0:0:a:0:0'. As you see the result is not properly compressed. In short I want a regex pattern to be used with re.findall to find sequences like [':0', ':0:', ':0:0:', '0:', '0:0:', '0:0:0:']. I have Google searched this, and found many questions on this site with similar phrasing, but none of them solves my problem. I have tried this regex: In [277]: ip Out[277]: 'abcd:0:ef12:0:0:a:0:0' In [278]: re.findall('((:0)+)|((0:)+)', ip) Out[278]: [(':0', ':0', '', ''), (':0:0', ':0', '', ''), (':0:0', ':0', '', '')]  I was expecting [':0:', ':0:0:', ':0:0']. How to fix this?  Using the correct regex I updated my code to this: EMPTY = re.compile(r':?\b(?:0\b:?)+') def to_ipv6(n: int, compress:bool=False) -> str: assert isinstance(n, int) and 0 <= n <= MAX_IPV6 ip = '{:039_x}'.format(n).replace('_', ':') if not compress: return ip ip = ':'.join(s.lstrip('0') if s != '0000' else '0' for s in ip.split(':')) longest = max(EMPTY.findall(ip), key=len, default='') if len(longest) > 2: ip = ip.replace(longest, '::', 1) return ip  It correctly compresses the given example: In [334]: to_ipv6(228362408209208931942454293848746098688, True) Out[334]: 'abcd:0:ef12::a:0:0'  I stopped using re.sub('0{1,3}([\da-f]+)', '\\1', ip) because somehow it took 8 microseconds and some to complete. The comprehension is faster.  The original code in this question doesn't actually work, the parser correctly parses valid IPv6 addresses but fails to identify invalid IPv6 addresses, thus gives incorrect output when it should raise exceptions. And the formatter also doesn't work, it gives incorrect outputs as well, namely it can sometimes fail to choose the longest consecutive empty fields, which is wrong. And will raise exceptions when the address cannot be compressed. DO NOT USE MY CODE IN YOUR PRODUCTION CODE, it is buggy and not well-tested. That said, I have fixed every bug I can find and made the code raise exceptions for all invalid inputs I can think of, a long time ago. But I didn't bother update the code in this question. Because I am lazy and there might still be edge cases I didn't catch. And today this question received an upvote so long after it was posted, an upvote it didn't deserve. So I felt compelled to update the question and edit the code, I fixed the formatter and not the parser, because the parser became too long, and I meant to discourage its use (well I would keep using my code, but nobody else should use it).

Answer: See the Python manual -> re.findall  If multiple groups are present, return a list of tuples of strings matching the groups. Non-capturing groups do not affect the form of the result.  To match the desired sequences and prevent matching such as :00: a0: 0a: here an idea. res = re.findall(r":?\b(?:0\b:?)+", ip)  See this demo at regex101 or a Python demo at tio.run It matches an optional : followed by a word boundary, followed by a repeated (?: non capturing group ) one or more times which contains 0 at a word boundary followed by an optional colon. If you don't want to match 0 without colon, another variant: (?::0\b)+:?|(?:\b0:)+(?:0\b)?