'How can you convert all single-quoted strings to double-quoted strings in PyCharm?

I want to do some code cleanup and make all of the strings within a module consistently double-quoted strings. My problem is there are tons of them and going through by hand (alt+enter, arrow, enter) is tedious and my regex-Fu is weak. (keep running into oddball cases where single quotes are in doc strings, etc)

Is there some faster way to convert all single-quoted strings to double-quoted strings in Pycharm?



Solution 1:[1]

In Preferences, type double-quoted into the search bar. This will suggest Editor->Intentions. There's an option there for converting double-quoted strings to single-quoted strings and vice-versa. Check this to see if it is enabled.

When you select one instance of such a string, and click on the 'light bulb', this may, or may not, offer you the option to apply it to the entire file by tapping the right arrow.

If not, it may at least ease your pain in that you can simply click once on each instance and use the light bulb to fix that on. Finding documentation on Intentions isn't all that fruitful, regarding how to apply them across a file or include them in code inspection. This may be a difference between the Community edition and the Full Version.

Though, as @acushner's comment suggests, and in my personal experience and preference, single-quoting seems the more consistently-used styling, and arguably sub rosa canon.

(To be honest, it's so ingrained in me at this point that I find myself single-quoting prose. But I reckon that's a personal problem, and TMI. 'Be yourself.')

Solution 2:[2]

I think there's an easier solution not being discussed, which is PyCharm's find and replace feature to ignore comments and string literals, which seems like it would do exactly what you want without much work and with little room for edge cases: enter image description here

To ignore triple single quotes, you can use this regex, which I copied from this SO post:

(?<!')'(?!')

enter image description here

Solution 3:[3]

I know you asked how to do it in PyCharm, but I'll propose a tool instead, since in my tests it works flawlessly. You can still run it from within PyCharm's terminal window, which makes this a valid answer to your question ;-).

The tool (source code below) is based on Python's tokenizer. This guarantees that only single quotes enclosing string literals can be replaced.

Furthermore, conversions that would not be straightforward and safe, and possibly not even desirable, are avoided. The file positions that may need manual intervention are reported.

With the help of the find command you can apply this tool to a whole source tree at once.

import argparse
import re
from tokenize import generate_tokens, STRING, untokenize

# Tool for the automatic (and manual) conversion of single-quoted strings to double-quoted ones

# Reference for string literals: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals
LONG_STRING_REGEX  = re.compile(r"([fruFRU]*)'''(.*)'''", re.DOTALL)
SHORT_STRING_REGEX = re.compile(r"([fruFRU]*)'(.*)'")  # only if LONG_STRING_REGEX doesn't match

def single_to_double_quotes(infilename, outfilename, long_too):
    convs_done = 0
    manual_conv_starts = []
    output_tokens = []
    with open(infilename) as infile:
        for t in generate_tokens(infile.readline):
            if t.type == STRING:
                if m := LONG_STRING_REGEX.fullmatch(t.string):
                    if long_too:
                        prefix, value = m.groups()
                        if '"""' in value:
                            manual_conv_starts.append(t.start)
                        else:
                            t = (STRING, f'{prefix}"""{value}"""', t.start, t.end, t.line)
                            convs_done += 1
                elif m := SHORT_STRING_REGEX.fullmatch(t.string):
                    prefix, value = m.groups()
                    if '"' in value or "'" in value:
                        manual_conv_starts.append(t.start)
                    else:
                        t = (STRING, f'{prefix}"{value}"', t.start, t.end, t.line)
                        convs_done += 1
            output_tokens.append(t)

    with open(outfilename, 'w') as outfile:
        outfile.write(untokenize(output_tokens))
    return convs_done, manual_conv_starts

parser = argparse.ArgumentParser(description='safely convert single quotes to double quotes')
parser.add_argument('infilename', metavar='INPUT_FILE', help='the input file')
parser.add_argument('outfilename', metavar='OUTPUT_FILE', help='the output file')
parser.add_argument('--long', '-l', action='store_true', help='also convert "long" (triple-quoted) strings')
args = parser.parse_args()  # type: argparse.Namespace
infilename = args.infilename  # type: str
outfilename = args.outfilename  # type: str
long_too = bool(args.long)
print(f"input file: {infilename}")
print(f"long string conversion {('disabled', 'enabled')[long_too]}")
convs_done, manual_conv_starts = single_to_double_quotes(infilename, outfilename, long_too)
print(f"strings converted: {convs_done}")
print(f"possible manual conversions: {len(manual_conv_starts)}")
for start in manual_conv_starts:
    print(f"  {outfilename} line {start[0]} char {start[1] + 1}")
print(f"output file: {outfilename}")

Example output (without -l option):

input file: single_to_double_quotes_TEST.py
long string conversion disabled
strings converted: 5
possible manual conversions: 3
  single_to_double_quotes_CONV.py line 3 char 5
  single_to_double_quotes_CONV.py line 4 char 5
  single_to_double_quotes_CONV.py line 12 char 7
output file: single_to_double_quotes_CONV.py

single_to_double_quotes_TEST.py:

n = 123
s = 'abc'
t = 'd"e"f'
u = 'g\'h\'i'
l = '''
"let's see"
'''
w, x, y, z = '''xyz''', '''"""yz''', '''x"""z''', '''xy"""'''
print(f'{s=}')
d = {'a': 1, 'b': 2, 'c': 3}
print(f"{d['a']=}")
print(f'{d["a"]=}')

single_to_double_quotes_CONV.py:

n = 123
s = "abc"
t = 'd"e"f'
u = 'g\'h\'i'
l = '''
"let's see"
'''
w, x, y, z = '''xyz''', '''"""yz''', '''x"""z''', '''xy"""'''
print(f"{s=}")
d = {"a": 1, "b": 2, "c": 3}
print(f"{d['a']=}")
print(f'{d["a"]=}')

On a copy of your source tree, like on a git checkout, you can do the following:

find . -name \*.py -exec python3 PATH/TO/TOOL.py {} {} \;

This will convert each file onto itself.

In case you are not familiar with argparse: start from

python3 TOOL.py -h

I recommend you modify the tool to best fit your needs.

Solution 4:[4]

This can be done using black formatting

Install black by doing pip install black

Then do black . to perform the formatting.

This will not just take care of the single quotes, but will format your code to a much readable way. There are a lot of options that you can provide to the black cli to customize how black does the formatting.

You can check those options here: https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#command-line-options

Solution 5:[5]

Just use the find and replace function of PyCharm by pressing "Ctrl+R"

But just be careful that it will convert all the double-quoted symbols to single-quoted. So, if you have a triple-quoted string like """===""", then you'll have to exclude those kinds of string and then replace all. So, if you have a big code, it'll take you just as much as time as doing it manually.

Or you can also use the replace method of python. Copy your code down into a string and then use the code, .replace("\"", "'")

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Chris Larson
Solution 2
Solution 3
Solution 4 Saroopashree Kumaraguru
Solution 5 EasyWay Coder