Can it be shorter?: DNA Sequence Analyzer from CS50 written in Python This is my first time requesting a code review. This is code I wrote in Python to create a DNA sequence analyzer. This is for the Harvard CS50 course.
I was really hoping to get some constructive criticism on this code I finished for the DNA problem in Problem Set 6. This code passed all tests and is fully functional as far as I am aware.
I was mainly wondering if there were any more succinct ways to rewrite parts of it. I spent several hours writing this and my brain is pretty exhausted, so near the end I just went for whatever worked.
Any input is appreciated! (Also, sorry if this is an excessive amount of comments, but they help me keep everything clear in my head.)
import re
import csv
from sys import argv

# Checks for correct number of command-line arguments
if not len(argv) == 3:
    print(&quot;Incorrect number of command-line arguments.&quot;)
    exit(1)

# Declares a dictionary containing STR counts
str_counts = {
    &quot;AGATC&quot;: &quot;0&quot;,
    &quot;TTTTTTCT&quot;: &quot;0&quot;,
    &quot;AATG&quot;: &quot;0&quot;,
    &quot;TCTAG&quot;: &quot;0&quot;,
    &quot;GATA&quot;: &quot;0&quot;,
    &quot;TATC&quot;: &quot;0&quot;,
    &quot;GAAA&quot;: &quot;0&quot;,
    &quot;TCTG&quot;: &quot;0&quot;
}

# Opens the csv and txt files to read (and closes them later)
with open(argv[1], &quot;r&quot;, newline=&quot;&quot;) as csv_file, open(argv[2], &quot;r&quot;) as sequence:
    # Reads the database into a dict and the sequence into a string
    db = csv.DictReader(csv_file)
    sq = sequence.read()

    # Stores the fieldname of the dictionary and stores the keys in STRS, skipping first line
    keys = db.fieldnames
    key_len = len(keys)
    # Skips first row of headers
    STRs = keys[1:key_len]

    # Bool to signal if match was found
    matched = False

    # Checks for STR matches and length of those matches, then stores those values in str_counts
    for STR in STRs:
        # Executes following code only if there are 1 or more matches
        if re.search(rf&quot;(?:{STR}+)&quot;, sq):
            # Creates a list of matches
            matches = re.findall(rf&quot;(?:{STR})+&quot;, sq)
            # Finds the longest match
            longest = max(matches, key = len)
            # Stores that value in corresponding dict key
            str_counts[STR] = len(longest) / len(STR)
            # Compares str_counts values to database to find match

    # Compares the match count values to the database values
    for row in db:
        # Declares a counter to later check if all STR values are matched
        # Resets counter to zero every iteration
        match_count = 0
        # Declares a temporary dictionary with only int match values to compare STR counts to
        compare = {}
        # Stores the names field for use later
        match_name = row['name']
        # Deletes the names field so we only have ints in our compare dict
        del row['name']
        # Converts values to integers for easy comparison
        for key, value in row.items():
            compare[key] = int(value)
        # Increments match count
        for STR in STRs:
            if compare[STR] == str_counts[STR]:
                match_count += 1
        # If all fields are matched, print match name, switch bool to true
        if match_count == len(STRs):
            matched = True
            print(match_name)

    if not matched:
        print(&quot;No Match&quot;)
I would group the code in functions and call them from a central main. With how you have it now, simply loading the file into an interactive console will cause all the code to run, which, if the code is long running or causes side-effects, may be less than ideal. Say I wrote a port-scanner, and it takes 2-3 minutes to run. I may not want it to start simply because I loaded the file into a REPL in Pycharm. Not only would that waste a few minutes each time, port-scanning can get you in trouble if done on a network that you don't have permission to scan.
Code wrapped in functions is simply easier to control the execution of, because it will only run when the function is called.
Grouping code into functions also allows you to easily test each chunk in isolation from the rest of the code. It's difficult to test code when you're dependent on the first half of the code to supply test data to the latter-half. It's much easier however to simply load the script into a REPL, and throw test data at the function (or use proper unit-tests). Then you can test one piece of code in isolation without touching the rest of the code.

I personally believe that STRs and STR should be lower-case. According to PEP8, Python's style and convention guide, regular variables should be lower case, with words separated by underscores. Variables that are considered to be constants however should be upper-case, separated by underscores.
Local variables that remain unchanged have never clicked as &quot;constants&quot; in my head, so it depends on what you decide.
Regardless though, STRs should either be all upper-case, or all lower-case; in which case the STR counterpart will need to be renamed so it doesn't clash with the str built-in. A name like str_ could be used, or you could use a more descriptive name.

compare = {}

. . .

for key, value in row.items():
    compare[key] = int(value)

This can make use of a dictionary comprehension. Whenever you have the pattern of creating a set/list/dictionary before a loop, then populating the structure within the loop, you should consider a comprehension:
compare = {key: int(value) for key, value in row.items()}


Similarly, to ensure that a condition holds for an entire collection (or two in this case), you can combine all with a generator expression (the part passed to all):
if all(compare[STR] == str_counts[STR] for STR in STRs):
    matched = True
    print(match_name)

&quot;If all corresponding values in the two dictionaries match, set the matched flag&quot;. It reads quite nicely.
That gets rid of the need for match_count, and the second last loop.

You're looping more than you need to. You appear to only care about if at least one match is found; yet you keep looping even after one is.
I'd break once one is found. If you combine that with for's else clause, you can prevent unnecessary looping and still detect failure:
for row in db:
    . . .
    if all(compare[STR] == str_counts[STR] for STR in STRs):
        print(match_name)
        break

else:
    print(&quot;No Match&quot;)