How to Validate a Phone Number in Python (phonenumbers Library)
To validate a phone number in Python, use phonenumbers, the Python port of Google's libphonenumber:
import phonenumbers
n = phonenumbers.parse("(415) 555-2671", "US")
phonenumbers.is_valid_number(n) # True
phonenumbers.format_number(n, phonenumbers.PhoneNumberFormat.E164) # '+14155552671'
phonenumbers.region_code_for_number(n) # 'US'
It validates against every country's numbering plan, which a regex can't. All outputs in this post come from phonenumbers 9.0.41. It's the same library our bulk phone validation runs on.
Install
pip install phonenumbers
Pure Python, no system dependencies. Update it regularly: the metadata changes as countries open new number ranges.
Parse with a default region
A number without + needs a region to make sense. Pass an ISO country code:
phonenumbers.parse("020 7946 0958", "GB") # UK number, valid
phonenumbers.parse("+44 20 7946 0958", None) # no region needed with +
Unparseable input raises an exception rather than returning None:
from phonenumbers import NumberParseException
try:
phonenumbers.parse("hello", "US")
except NumberParseException as e:
print(e) # (1) The string supplied did not seem to be a phone number.
Valid vs possible
n = phonenumbers.parse("(555) 123-4567", "US")
phonenumbers.is_possible_number(n) # True: right length
phonenumbers.is_valid_number(n) # False: 555 isn't an area code
Use is_valid_number for anything you store.
Line type
from phonenumbers import PhoneNumberType
phonenumbers.number_type(phonenumbers.parse("+44 7400 123456")) # MOBILE
phonenumbers.number_type(phonenumbers.parse("+1 800 275 2273")) # TOLL_FREE
phonenumbers.number_type(phonenumbers.parse("+1 415 555 2671")) # FIXED_LINE_OR_MOBILE
number_type returns an integer constant from PhoneNumberType. FIXED_LINE_OR_MOBILE is the normal answer for US and Canadian numbers, because their numbering plan doesn't separate the two. See mobile vs landline lookup.
Formatting
n = phonenumbers.parse("4155552671", "US")
fmt = phonenumbers.PhoneNumberFormat
phonenumbers.format_number(n, fmt.E164) # '+14155552671'
phonenumbers.format_number(n, fmt.INTERNATIONAL) # '+1 415-555-2671'
phonenumbers.format_number(n, fmt.NATIONAL) # '(415) 555-2671'
Store E.164, display national or international. Why that matters: E.164 format explained.
Geography, time zone and original carrier
The package ships offline data for these:
from phonenumbers import carrier, geocoder, timezone
us = phonenumbers.parse("+1 415 555 2671")
geocoder.description_for_number(us, "en") # 'San Francisco, CA'
timezone.time_zones_for_number(us) # ('America/Los_Angeles',)
uk = phonenumbers.parse("+44 7400 123456")
carrier.name_for_number(uk, "en") # 'Three'
The carrier name is the network the range was originally allocated to, not the current one. UK mobile numbers can be ported between networks and keep their digits, so this can be wrong for any number that has moved. For the current carrier you need a live lookup service.
Finding phone numbers in text
text = "Call us on 020 7946 0958 or +1 (415) 555-2671 today."
for match in phonenumbers.PhoneNumberMatcher(text, "GB"):
print(phonenumbers.format_number(match.number, phonenumbers.PhoneNumberFormat.E164))
# +442079460958
# +14155552671
Useful for pulling numbers out of email signatures, scraped pages or notes fields.
Cleaning a CSV with pandas
import pandas as pd
import phonenumbers
from phonenumbers import NumberParseException
def to_e164(raw, region="US"):
try:
n = phonenumbers.parse(str(raw), region)
except NumberParseException:
return None
if not phonenumbers.is_valid_number(n):
return None
return phonenumbers.format_number(n, phonenumbers.PhoneNumberFormat.E164)
df = pd.read_csv("leads.csv", dtype={"phone": str})
df["phone_e164"] = df["phone"].map(to_e164)
valid = df.dropna(subset=["phone_e164"]).drop_duplicates(subset="phone_e164")
valid.to_csv("leads_clean.csv", index=False)
Note dtype={"phone": str}. Without it, pandas reads phone columns as numbers and drops the + and leading zeros, just as Excel does. The full process, including choosing a default region for mixed lists, is in how to clean phone numbers in a CSV.
What validation can't tell you
- In service or disconnected: both sit in valid ranges.
- Current carrier: the offline data is the original allocation.
- Real or example:
(212) 555-0100is valid but reserved for fiction. See 555 phone numbers.
No code needed for files
If you just need a file cleaned, upload the CSV on the SimpleVerifier verifier. Every row gets phone_status, phone_e164, phone_country and phone_type, computed with this same library, and email columns in the file get a live mailbox check. Phone checks are included on paid plans; see pricing. For email in Python, see validate email in Python.
Related: validate a phone number in JavaScript · phone number validation regex
Common questions
How do I validate a phone number in Python?
Install the phonenumbers package, parse the number with a default region, and call is_valid_number: phonenumbers.is_valid_number(phonenumbers.parse('(415) 555-2671', 'US')) returns True. It checks the number against the country's numbering plan.
How do I format a phone number as E.164 in Python?
phonenumbers.format_number(number, phonenumbers.PhoneNumberFormat.E164) after parsing. '(415) 555-2671' parsed with region 'US' becomes '+14155552671'.
Does phonenumbers' carrier lookup show the current carrier?
No. phonenumbers.carrier.name_for_number returns the carrier a number range was originally allocated to, from offline data. If the number was ported, it's out of date. Current carrier needs a live lookup API.
What's the difference between is_possible_number and is_valid_number?
is_possible_number only checks the length for the region. is_valid_number also checks that the prefix is allocated. (555) 123-4567 is possible but not valid, because 555 isn't a US area code.
Check your list before you send
Real mailbox checks over SMTP, and nothing is sent to the address. Sign in with Google and your first 100 email checks are free, no card. Plans for bigger lists are on the pricing page.