Composing QGIS expression based on a QGIS layer tree I am developing a plugin for a GIS (Geographic Information Systems) software which uses Python. I have been using Python for over a year (on-off) and have the following code which works. It basically reads the names of files loaded into the GIS software to compose a QGIS expression for every group of files. Finally it deletes the list, ready for the next group.

The script is quite chunky and uses repetition. I am wondering how would one improve it to make it more compact and run more efficiently?

root = QgsProject.instance().layerTreeRoot()
policy_group = root.findGroup('Names')
formula_score4 = []
formula_score3a = []
formula_score3 = []
formula_score2a = []
formula_score2 = []
formula_score1 = []
for group in policy_group.children():
    for layer in group.children():
        score4 = '"' + layer.layerName() + '_Score' + '"' + ' = 4 OR '
        formula_score4.append(score4)
    for layer in group.children():
        score3a = 'coalesce(' + layer.layerName() + ' = 3, 0.00)*3 OR '
        formula_score3a.append(score3a)
    for layer in group.children():
        score3 = '"' + layer.layerName() + '_Score' + '"' + ' = 3 OR '
        formula_score3.append(score3)
    for layer in group.children():
        score2a = 'coalesce(' + layer.layerName() + ' = 2, 0.00)*2 OR '
        formula_score2a.append(score2a)
    for layer in group.children():
        score2 = '"' + layer.layerName() + '_Score' + '"' + ' = 2 OR '
        formula_score2.append(score2)
    for layer in group.children():
        score1 = '"' + layer.layerName() + '_Score' + '"' + ' = 1 OR '
        formula_score1.append(score1)   
    formula1 = "CASE WHEN " + "".join(str(x) for x in formula_score4) + "".join(str(x) for x in formula_score3a) + "&gt;=9 THEN 4 " + "WHEN " + "".join(str(x) for x in formula_score3) + "".join(str(x) for x in formula_score2a) + "&gt;=6 THEN 3 " + "WHEN " + "".join(str(x) for x in formula_score2) + "THEN 2 " + "WHEN " + "".join(str(x) for x in formula_score1) + "THEN 1 ELSE 1 END"
    formula2 = formula1.replace("OR &gt;=9 THEN 4 ", "&gt;=9 THEN 4 ")
    formula3 = formula2.replace("OR &gt;=6 THEN 3 ", "&gt;=6 THEN 3 ")
    formula4 = formula3.replace("OR THEN 2 ", "THEN 2 ")
    final_formula = formula4.replace("OR THEN 1 ELSE 1 END", "THEN 1 ELSE 1 END")
    del formula_score4[:]
    del formula_score3a[:]
    del formula_score3[:]
    del formula_score2a[:]
    del formula_score2[:]
    del formula_score1[:]




I have included a screenshot of running the above code in the Python Console of QGIS. I believe the result is what I was wanting from the code as it matches the same sql expression I wrote manually which is stored in a text file (instead of hardcoding the names of layers and reading the textfile, I wanted a more dynamic approach). Here is the image:



And here is the output for the first group (formatted for readability):

'CASE WHEN "Layer_1a_Score" = 4 OR 
"Layer_1b_Score" = 4 OR 
"Layer_1c_Score" = 4 OR 
"Layer_1d_Score" = 4 OR 
"Layer_1e_Score" = 4 OR 
"Layer_1f_Score" = 4 OR 
coalesce(Layer_1a = 3, 0.00)*3 OR 
coalesce(Layer_1b = 3, 0.00)*3 OR 
coalesce(Layer_1c = 3, 0.00)*3 OR 
coalesce(Layer_1d = 3, 0.00)*3 OR 
coalesce(Layer_1e = 3, 0.00)*3 OR 
coalesce(Layer_1f = 3, 0.00)*3 &gt;=9 
THEN 4 WHEN 
"Layer_1a_Score" = 3 OR 
"Layer_1b_Score" = 3 OR 
"Layer_1c_Score" = 3 OR 
"Layer_1d_Score" = 3 OR 
"Layer_1e_Score" = 3 OR 
"Layer_1f_Score" = 3 OR 
coalesce(Layer_1a = 2, 0.00)*2 OR 
coalesce(Layer_1b = 2, 0.00)*2 OR 
coalesce(Layer_1c = 2, 0.00)*2 OR 
coalesce(Layer_1d = 2, 0.00)*2 OR 
coalesce(Layer_1e = 2, 0.00)*2 OR 
coalesce(Layer_1f = 2, 0.00)*2 &gt;=6 
THEN 3 WHEN 
"Layer_1a_Score" = 2 OR 
"Layer_1b_Score" = 2 OR 
"Layer_1c_Score" = 2 OR 
"Layer_1d_Score" = 2 OR 
"Layer_1e_Score" = 2 OR 
"Layer_1f_Score" = 2 
THEN 2 WHEN 
"Layer_1a_Score" = 1 OR 
"Layer_1b_Score" = 1 OR 
"Layer_1c_Score" = 1 OR 
"Layer_1d_Score" = 1 OR 
"Layer_1e_Score" = 1 OR 
"Layer_1f_Score" = 1
THEN 1 ELSE 1 END'
...
You do quite a lot or repetition. Using str.format you should be able to make your code smaller by using a comprehensions or map.
Doing these inside a ''.join can allow you to reduce a lot of your code.
And if you make a list to iterate over you can do this all in two comprehensions.
I also think you should make a comprehension to get the layer name from your group.children().
Finally you will want to build formula1 from this which can be done with another string with format.

As all the strings used to format the data are constant, you can use UPPER_SNAKE_CASE variable names to say it's a constant.
And should result in something like:

SCORE_FORMATS = [
    '"{}_Score" = 1 OR ',
    '"{}_Score" = 2 OR ',
    '"{}_Score" = 3 OR ',
    '"{}_Score" = 4 OR ',
    'coalesce({} = 2, 0.00)*2 OR ',
    'coalesce({} = 3, 0.00)*3 OR ']
FORMULA1 = "CASE WHEN {3}{5}&gt;=9 THEN 4 WHEN {2}{4}&gt;=6 THEN 3 WHEN {1}THEN 2 WHEN {0}THEN 1 ELSE 1 END"


Finally to make the main part of your code, formula1 to final_formula, you can use the following as I explained above:

for group in policy_group.children():
    group_children = [layer.layerName() for layer in group.children()]
    formulas = [
        ''.join(map(formula_format.format, group_children))
        for formula_format in SCORE_FORMATS
    ]
    formula1 = FORMULA1.format(*formulas)
    formula2 = formula1.replace("OR &gt;=9 THEN 4 ", "&gt;=9 THEN 4 ")
    formula3 = formula2.replace("OR &gt;=6 THEN 3 ", "&gt;=6 THEN 3 ")
    formula4 = formula3.replace("OR THEN 2 ", "THEN 2 ")
    final_formula = formula4.replace("OR THEN 1 ELSE 1 END", "THEN 1 ELSE 1 END")




The output of this function only uses final_formula where I was originally unsure if you were using all the formulas.
You also used the wrong delimiter for the coalesce.

To improve on the above I'd change SCORE_FORMATS to be tuples of (delimiter, format).
This will allow you to easily change both for all your different functions.
This will also change the ''.join(map(...)) to use your delimiter and will remove the need for all the replace calls.

This unfortunately makes the FORMULA a bit longer,
but give us an excuse to use implicit string concatenation which can make it much easier to read.

SCORE_FORMATS = [
    (' OR ', '"{}_Score" = 1'),
    (' OR ', '"{}_Score" = 2'),
    (' OR ', '"{}_Score" = 3'),
    (' OR ', '"{}_Score" = 4'),
    (' + ', 'coalesce({} = 2, 0.00)*2'),
    (' + ', 'coalesce({} = 3, 0.00)*3')]
FORMULA = ('CASE WHEN {3} OR {5} &gt;= 9 THEN 4 '
            'WHEN {2} OR {4} &gt;= 6 THEN 3 '
            'WHEN {1} THEN 2 '
            'WHEN {0} THEN 1 '
            'ELSE 1 END')

for group in policy_group.children():
    group_children = [layer.layerName() for layer in group.children()]
    formula = FORMULA.format(*[
        delimiter.join(map(formula_format.format, group_children))
        for delimiter, formula_format in SCORE_FORMATS
    ])