Test if a python string is printable

Viewed 58099

I have some code that pulls data from a com-port and I want to make sure that what I got really is a printable string (i.e. ASCII, maybe UTF-8) before printing it. Is there a function for doing this? The first half dozen places I looked, didn't have anything that looks like what I want. (string has printable but I didn't see anything (there, or in the string methods) to check if every char in one string is in another.

Note: control characters are not printable for my purposes.


Edit: I was/am looking for a single function, not a roll-your-own solution:

What I ended up with is:

all(ord(c) < 127 and c in string.printable for c in input_str)
10 Answers

This Python 3 string contains all kinds of special characters:

s = 'abcd\x65\x66 äüöë\xf1 \u00a0\u00a1\u00a2 漢字 \a\b\r\t\n\v\\ \231\x9a \u2640\u2642\uffff'

If you try to show it in the console (or use repr), it makes a pretty good job of escaping all non-printable characters from that string:

>>> s
'abcdef äüöëñ \xa0¡¢ 漢字 \x07\x08\r\t\n\x0b\\ \x99\x9a ♀♂\uffff'

It is smart enough to recognise e.g. horizontal tab (\t) as printable, but vertical tab (\v) as not printable (shows up as \x0b rather than \v).

Every other non printable character also shows up as either \xNN or \uNNNN in the repr. Therefore, we can use that as the test:

def is_printable(s):
    return not any(repr(ch).startswith("'\\x") or repr(ch).startswith("'\\u") for ch in s)

There may be some borderline characters, for example non-breaking white space (\xa0) is treated as non-printable here. Maybe it shouldn't be, but those special ones could then be hard-coded.


P.S.

You could do this to extract only printable characters from a string:

>>> ''.join(ch for ch in s if is_printable(ch))
'abcdef äüöëñ ¡¢ 漢字 \r\t\n\\  ♀♂'

In Python 3, strings have an isprintable() method:

>>> 'a, '.isprintable()
True

For Python 2.7, see Dave Webb's answer.

The category function from the unicodedata module might suit your needs. For instance, you can use this to check whether there are any control characters in a string while still allowing non-ASCII characters.

>>> import unicodedata

>>> def has_control_chars(s):
...     return any(unicodedata.category(c) == 'Cc' for c in s)

>>> has_control_chars('Hello 世界')
False

>>> has_control_chars('Hello \x1f 世界')
True
# Here is the full routine to display an arbitrary binary string
# Python 2

ctrlchar = "\n\r| "

# ------------------------------------------------------------------------

def isprint(chh):
    if ord(chh) > 127:
        return False
    if ord(chh) < 32:
        return False
    if chh in ctrlchar:
        return False
    if chh in string.printable:
        return True
    return False


# ------------------------------------------------------------------------
# Return a hex dump formatted string

def hexdump(strx, llen = 16):
    lenx = len(strx)
    outx = ""
    for aa in range(lenx/16):
        outx += " "
        for bb in range(16):
            outx += "%02x " % ord(strx[aa * 16 + bb])
        outx += " | "     
        for cc in range(16):
            chh = strx[aa * 16 + cc]
            if isprint(chh):
                outx += "%c" % chh
            else:
                outx += "."
        outx += " | \n"

    # Print remainder on last line
    remn = lenx % 16 ;   divi = lenx / 16
    if remn:
        outx += " "
        for dd in range(remn):
            outx += "%02x " % ord(strx[divi * 16 + dd])
        outx += " " * ((16 - remn) * 3) 
        outx += " | "     
        for cc in range(remn):
            chh = strx[divi * 16 + cc]
            if isprint(chh):
                outx += "%c" % chh
            else:
                outx += "."
        outx += " " * ((16 - remn)) 
        outx += " | \n"


    return(outx)

In the ASCII table, [\x20-\x7e] are printable characters.
Use regular expressions to check whether characters other than these characters are included in the string.
You can make sure whether this is a printable string.

>>> import re

>>> # Printable
>>> print re.search(r'[^\x20-\x7e]', 'test')
None

>>> # Unprintable
>>> re.search(r'[^\x20-\x7e]', 'test\x00') != None
True

>>> # Optional expression
>>> pattern = r'[^\t-\r\x20-\x7e]'

Mine is a solution to get rid of any known set of characters. it might help.

non_printable_chars = set("\n\t\r ")     # Space included intensionally
is_printable = lambda string:bool(set(string) - set(non_printable_chars))
...
...
if is_printable(string):
    print("""do something""")

...

ctrlchar = "\n\r| "

# ------------------------------------------------------------------------
# This will let you control what you deem 'printable'
# Clean enough to display any binary 

def isprint(chh):
    if ord(chh) > 127:
        return False
    if ord(chh) < 32:
        return False
    if chh in ctrlchar:
        return False
    if chh in string.printable:
        return True
    return False
Related