python – How to Iterate Through Field Names and Calculate Fields with PyQGIS

field-calculatorlooppyqgispythonqgis-3

I am using QGIS 3.2.1 Bonn 64 bit on Windows 10. I would like to loop through a list of field names and perform calculations on each field. Only the first loop in the iteration performs any calculations. The rest are skipped.

result_path = r"D:\QGIS\hex_results.geojson"

tracks = QgsProject.instance().mapLayersByName("patrol_tracks")[0]
result = QgsProject.instance().mapLayersByName("hex_results")[0]

t = tracks.getFeatures()
r = result.getFeatures()

r_prov = result.dataProvider()
r_fields = r_prov.fieldNameMap()

months = [x for x in processing.run("qgis:listuniquevalues", {'INPUT':tracks,'FIELDS':"Month_Year",'OUTPUT':'u'})['UNIQUE_VALUES'].split(";")]

for m in months:
    result.startEditing()
    field_index = result.fields().lookupField(m)
    print (m)
    print (field_index)
    a = {field_index : 0}
    for feat in r:
        fid = feat.id()
        r_prov.changeAttributeValues({fid : a})

    result.commitChanges()

The print calls work for every list item but the field attributes are only altered for the first iteration.

Another method I have tried (using the same variable names as above) is:

for m in months:
    processing.run('qgis:fieldcalculator', {'INPUT':result, 'FIELD_NAME':m, 'FIELD_TYPE':1, 'FIELD_LENGTH':10, 'FIELD_PRECISION':0, 'NEW_FIELD': False, 'FORMULA':'0', 'OUTPUT':result_path})

But with the same result – only the first field in the list is calculated. This code will be part of a more complex script that calculates field values based on the intersection of selected features – which is more or less working up until this point.

Any suggestions? I wondered if the dataProvider needs to be refreshed or updated after each iteration but commitChanges(), added later, doesn't seem to have any effect.

Best Answer

The QgsFeatureIterator is closed after returning all features.

Example:

>>layer = iface.activeLayer()
>>feats = layer.getFeatures()

>>[f['GRID_ID'] for f in feats]
['AP7', 'AP8', 'AP9', 'AP3', 'AP4', 'AP5', 'AP6', 'AP1', 'AP2']

>>[f['GRID_ID'] for f in feats]
[] #No more features are return after used once

>>feats = layer.getFeatures() #Recreate it
>>[f['GRID_ID'] for f in feats]
['AP7', 'AP8', 'AP9', 'AP3', 'AP4', 'AP5', 'AP6', 'AP1', 'AP2']

So in your case recreate it for each iteration:

...
for m in months:
    result.startEditing()
    field_index = result.fields().lookupField(m)
    print (m)
    print (field_index)
    a = {field_index : 0}
    for feat in r:
        fid = feat.id()
        r_prov.changeAttributeValues({fid : a})
    result.commitChanges()
    r = result.getFeatures()
Related Question