Search code examples
pythonregexpycharmcommentsmultiline

Commenting out specific Python code via a regex search and replace operation in PyCharm


I'm trying to comment out specific occurrences of code using PyCharm's 'Replace in Files' functionality.

Specifically, I want the following to be commented out:

if TYPE_CHECKING:
    from foo import bar
    from x import y

So it can be replaced by:

#if TYPE_CHECKING:
#    from foo import bar
#    from x import y

I need this because I am checking for cyclical dependencies using pydeps, which at the time of writing doesn't seem to have an option to ignore imports under TYPE_CHECKING guards. Commenting these out manually is tedious for the project I'm working on.

Right now I'm using this regex, which matches as expected:

(^if TYPE_CHECKING:\n)(^\s+from.?)+

And I'm trying to replace it using:

#$1#$2

I didn't expect this to work, as I think $2 should only match the first occurrence of the second group.

Another way would be to simply replace every line starting with if TYPE_CHECKING: or \s+from.+\n individually using e.g.:

(^if TYPE_CHECKING:\n|^\s+from.+\n)+

And then simply replacing by $1. This works as long as no other 'from' imports are preceded by whitespace. However, this also replaces occurrences in comments or already commented out code (I know this may be considered bad practice, but I'm looking for a way to make this work robustly regardless).

Would anyone have suggestions for an approach?


Solution

  • An option (if supported) could be making use of the \G anchor.

    In the replacement use # followed by the full match using #$0

    (?:^if TYPE_CHECKING:\R|\G(?!\A)^[^\S\r\n]*(?: from .*)?(?:\R|$))
    

    The pattern matches:

    • (?: Non capture group
      • ^if TYPE_CHECKING:\R Match if TYPE_CHECKING: and a newline from the start of the line
      • | Or
      • \G(?!\A) Assert the position at the end of the previous match, not at the start of the string
      • ^[^\S\r\n]* Match optional spaces without a newline from the start of the line
      • (?: from .*)? Optionally match a line with from (to cross empty lines if they are present)
      • (?:\R|$) Match either a newline or assert the end of the line
    • ) Close non capture group

    Regex demo

    Output

    #if TYPE_CHECKING:
    #    from foo import bar
    #    from x import y
    

    An option using code:

    You could match all the lines that start with spaces and from, and replace all the beginnings of the string with #.

    ^if TYPE_CHECKING:(?:\n\s*from .*)*
    

    The pattern matches:

    • ^ Start of string
    • if TYPE_CHECKING: Match literally
    • (?: Non capture group to repeat as a whole
      • \n\s*from .* Match a newline, optional whitespace chars, match from and the rest of the line
    • )* Close non capture grou

    Regex demo

    For example

    import re
    
    pattern = r"^if TYPE_CHECKING:(?:\n\s*from .*)*"
    
    s = ("if TYPE_CHECKING:\n"
                "    from foo import bar\n"
                "    from x import y")
    
    res = re.sub(pattern, lambda x: re.sub(r"^", "#", x.group(), 0, re.MULTILINE), s)
    print(res)
    

    Output

    #if TYPE_CHECKING:
    #    from foo import bar
    #    from x import y