[{"data":1,"prerenderedAt":1785},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fupdate-attribute-values-pyqgis":3},{"id":4,"title":5,"body":6,"description":1774,"extension":1775,"meta":1776,"navigation":267,"path":1781,"seo":1782,"stem":1783,"__hash__":1784},"docs\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fupdate-attribute-values-pyqgis\u002Findex.md","Update Attribute Values in Bulk with PyQGIS",{"type":7,"value":8,"toc":1762},"minimark",[9,13,17,26,200,205,227,231,234,514,528,547,551,554,691,716,720,723,839,990,1018,1022,1025,1111,1453,1472,1476,1482,1540,1552,1556,1620,1624,1634,1638,1647,1662,1676,1690,1700,1722,1731,1735,1758],[10,11,5],"h1",{"id":12},"update-attribute-values-in-bulk-with-pyqgis",[14,15,16],"p",{},"Writing a value to one feature is trivial. Writing values to fifty thousand features is where the two PyQGIS editing routes stop being interchangeable: the provider route sends one batched instruction and finishes in seconds, while the edit-buffer route sends fifty thousand individually and pushes each one onto an undo stack. Both are correct; picking the wrong one for the context turns a five-second job into a five-minute one, or silently loses an entire batch when a commit fails unchecked.",[14,18,19,20,25],{},"This page is a focused recipe within ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002F","Attribute Tables and Field Management in PyQGIS",". It covers the batched provider update, the transactional edit-buffer form, updating a filtered subset, and the failure handling that keeps a layer usable when something goes wrong.",[14,27,28],{},[29,30,35,39,43,50,67,76,86,92,97,101,107,114,118,122,129,134,138,142,146,150,153,157,161,165,182,185,191,196],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 262","img","A batched provider update sends one instruction containing every change, while a per-feature edit-buffer loop sends each change separately through the undo stack","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"One instruction versus fifty thousand",[40,41,42],"desc",{},"The upper path shows a Python loop building a single dictionary of all changes, which is handed to changeAttributeValues as one provider call. The lower path shows each change being pushed individually through the edit buffer and undo stack before a final commit, with many more arrows crossing the boundary to the data source.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","262","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"updRouteArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Same result, very different number of round-trips",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":84,"style":85},"16","46","200","86","10","#fffdf7","#15803d","stroke-width:2.5",[68,87,91],{"x":88,"y":89,"style":90,"fill":84,"textAnchor":74},"116","72","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","build a dictionary",[68,93,96],{"x":88,"y":94,"style":95,"fill":66,"textAnchor":74},"94","text-anchor:middle;font-size:11px;font-family:sans-serif","{fid: {idx: value}}",[68,98,100],{"x":88,"y":99,"style":95,"fill":66,"textAnchor":74},"114","all in memory, no I\u002FO",[44,102],{"x":103,"y":79,"width":104,"height":81,"rx":82,"fill":105,"stroke":106,"style":85},"292","184","#26322d","#0f766e",[68,108,113],{"x":109,"y":110,"style":111,"fill":112,"textAnchor":74},"384","76","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","#d9f99d","changeAttributeValues",[68,115,117],{"x":109,"y":116,"style":95,"fill":112,"textAnchor":74},"100","one call",[68,119,121],{"x":109,"y":120,"style":95,"fill":112,"textAnchor":74},"118","no undo",[123,124],"line",{"x1":125,"y1":126,"x2":127,"y2":126,"stroke":84,"style":128},"216","89","288","stroke-width:2.5;marker-end:url(#updRouteArrow)",[123,130],{"x1":131,"y1":126,"x2":132,"y2":126,"stroke":84,"style":133},"476","600","stroke-width:3;marker-end:url(#updRouteArrow)",[44,135],{"x":78,"y":136,"width":80,"height":81,"rx":82,"fill":83,"stroke":137,"style":85},"156","#b45309",[68,139,141],{"x":88,"y":140,"style":90,"fill":137,"textAnchor":74},"182","loop the features",[68,143,145],{"x":88,"y":144,"style":95,"fill":66,"textAnchor":74},"204","changeAttributeValue",[68,147,149],{"x":88,"y":148,"style":95,"fill":66,"textAnchor":74},"224","once per feature",[44,151],{"x":103,"y":136,"width":104,"height":81,"rx":82,"fill":83,"stroke":152,"style":85},"#2563eb",[68,154,156],{"x":109,"y":155,"style":111,"fill":152,"textAnchor":74},"186","edit buffer",[68,158,160],{"x":109,"y":159,"style":95,"fill":66,"textAnchor":74},"208","undo stack grows",[68,162,164],{"x":109,"y":163,"style":95,"fill":66,"textAnchor":74},"228","commitChanges() at the end",[166,167,169,172,175,177,180],"g",{"stroke":137,"style":168},"stroke-width:1.6",[123,170],{"x1":125,"y1":171,"x2":127,"y2":171},"176",[123,173],{"x1":125,"y1":174,"x2":127,"y2":174},"188",[123,176],{"x1":125,"y1":80,"x2":127,"y2":80},[123,178],{"x1":125,"y1":179,"x2":127,"y2":179},"212",[123,181],{"x1":125,"y1":148,"x2":127,"y2":148},[123,183],{"x1":131,"y1":184,"x2":132,"y2":184,"stroke":152,"style":133},"199",[44,186],{"x":187,"y":81,"width":188,"height":189,"rx":82,"fill":83,"stroke":190,"style":85},"604","140","120","#59645f",[68,192,195],{"x":193,"y":194,"style":90,"fill":73,"textAnchor":74},"674","136","the data source",[68,197,199],{"x":193,"y":198,"style":95,"fill":66,"textAnchor":74},"160","identical result",[201,202,204],"h2",{"id":203},"prerequisites","Prerequisites",[206,207,208,216,224],"ul",{},[209,210,211,215],"li",{},[212,213,214],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[209,217,218,219,223],{},"A writable vector layer with the target field already present — see ",[21,220,222],{"href":221},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fadd-field-to-layer-pyqgis\u002F","Add a Field to a Layer in PyQGIS"," if it is not.",[209,225,226],{},"A backup, or a GeoPackage you can afford to lose. The provider route has no undo.",[201,228,230],{"id":229},"the-batched-provider-update","The batched provider update",[14,232,233],{},"Build the whole change set in memory, then send it once.",[235,236,241],"pre",{"className":237,"code":238,"language":239,"meta":240,"style":240},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\nindex = layer.fields().indexOf(\"rate_band\")\nif index == -1:\n    raise KeyError(\"field 'rate_band' does not exist\")\n\nupdates = {}\nfor feature in layer.getFeatures():\n    value = feature[\"assessed_value\"]\n    if value is None:\n        continue\n    band = \"A\" if value > 1_000_000 else \"B\" if value > 400_000 else \"C\"\n    updates[feature.id()] = {index: band}\n\nok = layer.dataProvider().changeAttributeValues(updates)\nprint(\"wrote\", len(updates), \"row(s):\", ok)\n","python","",[242,243,244,262,269,294,311,332,349,354,365,380,396,413,419,461,472,477,488],"code",{"__ignoreMap":240},[245,246,248,252,256,259],"span",{"class":123,"line":247},1,[245,249,251],{"class":250},"snl16","from",[245,253,255],{"class":254},"s95oV"," qgis.core ",[245,257,258],{"class":250},"import",[245,260,261],{"class":254}," QgsProject\n",[245,263,265],{"class":123,"line":264},2,[245,266,268],{"emptyLinePlaceholder":267},true,"\n",[245,270,272,275,278,281,285,288,291],{"class":123,"line":271},3,[245,273,274],{"class":254},"layer ",[245,276,277],{"class":250},"=",[245,279,280],{"class":254}," QgsProject.instance().mapLayersByName(",[245,282,284],{"class":283},"sU2Wk","\"parcels\"",[245,286,287],{"class":254},")[",[245,289,46],{"class":290},"sDLfK",[245,292,293],{"class":254},"]\n",[245,295,297,300,302,305,308],{"class":123,"line":296},4,[245,298,299],{"class":254},"index ",[245,301,277],{"class":250},[245,303,304],{"class":254}," layer.fields().indexOf(",[245,306,307],{"class":283},"\"rate_band\"",[245,309,310],{"class":254},")\n",[245,312,314,317,320,323,326,329],{"class":123,"line":313},5,[245,315,316],{"class":250},"if",[245,318,319],{"class":254}," index ",[245,321,322],{"class":250},"==",[245,324,325],{"class":250}," -",[245,327,328],{"class":290},"1",[245,330,331],{"class":254},":\n",[245,333,335,338,341,344,347],{"class":123,"line":334},6,[245,336,337],{"class":250},"    raise",[245,339,340],{"class":290}," KeyError",[245,342,343],{"class":254},"(",[245,345,346],{"class":283},"\"field 'rate_band' does not exist\"",[245,348,310],{"class":254},[245,350,352],{"class":123,"line":351},7,[245,353,268],{"emptyLinePlaceholder":267},[245,355,357,360,362],{"class":123,"line":356},8,[245,358,359],{"class":254},"updates ",[245,361,277],{"class":250},[245,363,364],{"class":254}," {}\n",[245,366,368,371,374,377],{"class":123,"line":367},9,[245,369,370],{"class":250},"for",[245,372,373],{"class":254}," feature ",[245,375,376],{"class":250},"in",[245,378,379],{"class":254}," layer.getFeatures():\n",[245,381,383,386,388,391,394],{"class":123,"line":382},10,[245,384,385],{"class":254},"    value ",[245,387,277],{"class":250},[245,389,390],{"class":254}," feature[",[245,392,393],{"class":283},"\"assessed_value\"",[245,395,293],{"class":254},[245,397,399,402,405,408,411],{"class":123,"line":398},11,[245,400,401],{"class":250},"    if",[245,403,404],{"class":254}," value ",[245,406,407],{"class":250},"is",[245,409,410],{"class":290}," None",[245,412,331],{"class":254},[245,414,416],{"class":123,"line":415},12,[245,417,418],{"class":250},"        continue\n",[245,420,422,425,427,430,433,435,438,441,444,447,449,451,453,456,458],{"class":123,"line":421},13,[245,423,424],{"class":254},"    band ",[245,426,277],{"class":250},[245,428,429],{"class":283}," \"A\"",[245,431,432],{"class":250}," if",[245,434,404],{"class":254},[245,436,437],{"class":250},">",[245,439,440],{"class":290}," 1_000_000",[245,442,443],{"class":250}," else",[245,445,446],{"class":283}," \"B\"",[245,448,432],{"class":250},[245,450,404],{"class":254},[245,452,437],{"class":250},[245,454,455],{"class":290}," 400_000",[245,457,443],{"class":250},[245,459,460],{"class":283}," \"C\"\n",[245,462,464,467,469],{"class":123,"line":463},14,[245,465,466],{"class":254},"    updates[feature.id()] ",[245,468,277],{"class":250},[245,470,471],{"class":254}," {index: band}\n",[245,473,475],{"class":123,"line":474},15,[245,476,268],{"emptyLinePlaceholder":267},[245,478,480,483,485],{"class":123,"line":479},16,[245,481,482],{"class":254},"ok ",[245,484,277],{"class":250},[245,486,487],{"class":254}," layer.dataProvider().changeAttributeValues(updates)\n",[245,489,491,494,496,499,502,505,508,511],{"class":123,"line":490},17,[245,492,493],{"class":290},"print",[245,495,343],{"class":254},[245,497,498],{"class":283},"\"wrote\"",[245,500,501],{"class":254},", ",[245,503,504],{"class":290},"len",[245,506,507],{"class":254},"(updates), ",[245,509,510],{"class":283},"\"row(s):\"",[245,512,513],{"class":254},", ok)\n",[14,515,516,519,520,523,524,527],{},[212,517,518],{},"Breakdown:"," The dictionary maps feature ID to a dictionary of field index to new value — one entry per feature, and a feature can carry several field changes in its inner dictionary. Resolving the index once outside the loop avoids repeating a name lookup fifty thousand times. ",[242,521,522],{},"changeAttributeValues()"," returns a bool: ",[242,525,526],{},"False"," means the provider rejected the batch, and checking it is the only way to notice.",[14,529,530,531,534,535,538,539,542,543,546],{},"Note that ",[242,532,533],{},"continue"," on a ",[242,536,537],{},"NULL"," leaves that feature's band untouched rather than writing ",[242,540,541],{},"None",". That is a deliberate choice worth making consciously — the alternative, ",[242,544,545],{},"updates[feature.id()] = {index: None}",", explicitly blanks it.",[201,548,550],{"id":549},"update-only-what-matched","Update only what matched",[14,552,553],{},"Reading every feature to change a few is wasteful. Push the selection into the request and skip geometry entirely if the rule does not need it.",[235,555,557],{"className":237,"code":556,"language":239,"meta":240,"style":240},"from qgis.core import QgsFeatureRequest, QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\nindex = layer.fields().indexOf(\"status\")\n\nrequest = QgsFeatureRequest()\nrequest.setFilterExpression('\"status\" IS NULL AND \"assessed_value\" > 0')\nrequest.setFlags(QgsFeatureRequest.NoGeometry)\nrequest.setSubsetOfAttributes([\"assessed_value\"], layer.fields())\n\nupdates = {f.id(): {index: \"active\"} for f in layer.getFeatures(request)}\nlayer.dataProvider().changeAttributeValues(updates)\nprint(len(updates), \"row(s) back-filled\")\n",[242,558,559,570,574,590,603,607,617,627,632,642,646,671,676],{"__ignoreMap":240},[245,560,561,563,565,567],{"class":123,"line":247},[245,562,251],{"class":250},[245,564,255],{"class":254},[245,566,258],{"class":250},[245,568,569],{"class":254}," QgsFeatureRequest, QgsProject\n",[245,571,572],{"class":123,"line":264},[245,573,268],{"emptyLinePlaceholder":267},[245,575,576,578,580,582,584,586,588],{"class":123,"line":271},[245,577,274],{"class":254},[245,579,277],{"class":250},[245,581,280],{"class":254},[245,583,284],{"class":283},[245,585,287],{"class":254},[245,587,46],{"class":290},[245,589,293],{"class":254},[245,591,592,594,596,598,601],{"class":123,"line":296},[245,593,299],{"class":254},[245,595,277],{"class":250},[245,597,304],{"class":254},[245,599,600],{"class":283},"\"status\"",[245,602,310],{"class":254},[245,604,605],{"class":123,"line":313},[245,606,268],{"emptyLinePlaceholder":267},[245,608,609,612,614],{"class":123,"line":334},[245,610,611],{"class":254},"request ",[245,613,277],{"class":250},[245,615,616],{"class":254}," QgsFeatureRequest()\n",[245,618,619,622,625],{"class":123,"line":351},[245,620,621],{"class":254},"request.setFilterExpression(",[245,623,624],{"class":283},"'\"status\" IS NULL AND \"assessed_value\" > 0'",[245,626,310],{"class":254},[245,628,629],{"class":123,"line":356},[245,630,631],{"class":254},"request.setFlags(QgsFeatureRequest.NoGeometry)\n",[245,633,634,637,639],{"class":123,"line":367},[245,635,636],{"class":254},"request.setSubsetOfAttributes([",[245,638,393],{"class":283},[245,640,641],{"class":254},"], layer.fields())\n",[245,643,644],{"class":123,"line":382},[245,645,268],{"emptyLinePlaceholder":267},[245,647,648,650,652,655,658,661,663,666,668],{"class":123,"line":398},[245,649,359],{"class":254},[245,651,277],{"class":250},[245,653,654],{"class":254}," {f.id(): {index: ",[245,656,657],{"class":283},"\"active\"",[245,659,660],{"class":254},"} ",[245,662,370],{"class":250},[245,664,665],{"class":254}," f ",[245,667,376],{"class":250},[245,669,670],{"class":254}," layer.getFeatures(request)}\n",[245,672,673],{"class":123,"line":415},[245,674,675],{"class":254},"layer.dataProvider().changeAttributeValues(updates)\n",[245,677,678,680,682,684,686,689],{"class":123,"line":421},[245,679,493],{"class":290},[245,681,343],{"class":254},[245,683,504],{"class":290},[245,685,507],{"class":254},[245,687,688],{"class":283},"\"row(s) back-filled\"",[245,690,310],{"class":254},[14,692,693,695,696,699,700,703,704,707,708,711,712,715],{},[212,694,518],{}," ",[242,697,698],{},"setFilterExpression()"," is translated into SQL on providers that support it, so a GeoPackage or PostGIS layer never sends the non-matching rows at all. ",[242,701,702],{},"NoGeometry"," skips geometry decoding, which on a polygon layer is usually the dominant cost of iteration. ",[242,705,706],{},"setSubsetOfAttributes()"," narrows the columns further. Note ",[242,709,710],{},"IS NULL"," rather than ",[242,713,714],{},"= NULL"," — the latter is valid syntax that is never true, and returns zero rows silently.",[201,717,719],{"id":718},"the-transactional-route","The transactional route",[14,721,722],{},"Inside a plugin, the user expects Ctrl+Z to work and expects a failure to leave the data unchanged. That means the edit buffer, and it means handling the commit properly.",[14,724,725],{},[29,726,729,732,735,738,745,748,753,758,762,765,769,772,776,780,783,785,788,792,797,800,804,808,812,815,820,825,828,832,836],{"viewBox":727,"role":32,"ariaLabel":728,"xmlns":34},"0 0 760 250","The edit session lifecycle: startEditing opens a buffer, changes accumulate, and the session must end in either a successful commit or a rollback, with an exception path leading to rollback",[36,730,731],{},"Every edit session must reach one of two exits",[40,733,734],{},"startEditing opens an edit buffer. Changes accumulate in memory. From there, commitChanges either succeeds and writes to the source, or returns false, in which case commitErrors explains why and rollBack discards the session. An exception raised mid-loop takes the same rollback path. A layer left in edit mode blocks later operations.",[44,736],{"x":46,"y":46,"width":47,"height":737,"fill":49},"250",[51,739,740],{},[54,741,743],{"id":742,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"updTxnArrow",[63,744],{"d":65,"fill":66},[68,746,747],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Leaving a layer in edit mode blocks everything that follows",[44,749],{"x":78,"y":750,"width":751,"height":752,"rx":82,"fill":83,"stroke":152,"style":85},"98","152","66",[68,754,757],{"x":755,"y":756,"style":90,"fill":152,"textAnchor":74},"92","126","startEditing()",[68,759,761],{"x":755,"y":760,"style":95,"fill":66,"textAnchor":74},"146","buffer opens",[44,763],{"x":159,"y":750,"width":764,"height":752,"rx":82,"fill":105,"stroke":106,"style":85},"164",[68,766,768],{"x":767,"y":756,"style":111,"fill":112,"textAnchor":74},"290","changes accumulate",[68,770,771],{"x":767,"y":760,"style":95,"fill":112,"textAnchor":74},"in memory, undoable",[44,773],{"x":774,"y":79,"width":764,"height":775,"rx":82,"fill":83,"stroke":84,"style":85},"412","60",[68,777,779],{"x":778,"y":89,"style":111,"fill":84,"textAnchor":74},"494","commitChanges() → True",[68,781,782],{"x":778,"y":755,"style":95,"fill":66,"textAnchor":74},"written to the source",[44,784],{"x":774,"y":189,"width":764,"height":775,"rx":82,"fill":83,"stroke":137,"style":85},[68,786,787],{"x":778,"y":760,"style":111,"fill":137,"textAnchor":74},"→ False",[68,789,791],{"x":778,"y":790,"style":95,"fill":66,"textAnchor":74},"166","commitErrors() says why",[44,793],{"x":774,"y":794,"width":764,"height":795,"rx":82,"fill":83,"stroke":796,"style":85},"192","48","#b91c1c",[68,798,799],{"x":778,"y":179,"style":111,"fill":796,"textAnchor":74},"exception raised",[68,801,803],{"x":778,"y":802,"style":95,"fill":66,"textAnchor":74},"230","mid-loop",[44,805],{"x":806,"y":189,"width":807,"height":775,"rx":82,"fill":83,"stroke":796,"style":85},"616","128",[68,809,811],{"x":810,"y":760,"style":111,"fill":796,"textAnchor":74},"680","rollBack()",[68,813,814],{"x":810,"y":790,"style":95,"fill":66,"textAnchor":74},"layer usable again",[123,816],{"x1":817,"y1":818,"x2":144,"y2":818,"stroke":66,"style":819},"168","131","stroke-width:2.5;marker-end:url(#updTxnArrow)",[123,821],{"x1":822,"y1":189,"x2":823,"y2":824,"stroke":84,"style":819},"372","408","80",[123,826],{"x1":822,"y1":188,"x2":823,"y2":827,"stroke":137,"style":819},"150",[123,829],{"x1":822,"y1":830,"x2":823,"y2":179,"stroke":796,"style":831},"155","stroke-width:2;stroke-dasharray:5 4;marker-end:url(#updTxnArrow)",[123,833],{"x1":834,"y1":827,"x2":835,"y2":827,"stroke":137,"style":819},"576","612",[123,837],{"x1":834,"y1":179,"x2":838,"y2":104,"stroke":796,"style":831},"640",[235,840,842],{"className":237,"code":841,"language":239,"meta":240,"style":240},"from qgis.core import QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\nindex = layer.fields().indexOf(\"status\")\n\nif not layer.startEditing():\n    raise RuntimeError(\"could not start an edit session\")\n\ntry:\n    for feature in layer.getFeatures():\n        layer.changeAttributeValue(feature.id(), index, \"reviewed\")\n    if not layer.commitChanges():\n        raise RuntimeError(\"; \".join(layer.commitErrors()))\nexcept Exception:\n    layer.rollBack()\n    raise\n",[242,843,844,854,858,874,886,890,900,914,918,925,936,946,955,970,980,985],{"__ignoreMap":240},[245,845,846,848,850,852],{"class":123,"line":247},[245,847,251],{"class":250},[245,849,255],{"class":254},[245,851,258],{"class":250},[245,853,261],{"class":254},[245,855,856],{"class":123,"line":264},[245,857,268],{"emptyLinePlaceholder":267},[245,859,860,862,864,866,868,870,872],{"class":123,"line":271},[245,861,274],{"class":254},[245,863,277],{"class":250},[245,865,280],{"class":254},[245,867,284],{"class":283},[245,869,287],{"class":254},[245,871,46],{"class":290},[245,873,293],{"class":254},[245,875,876,878,880,882,884],{"class":123,"line":296},[245,877,299],{"class":254},[245,879,277],{"class":250},[245,881,304],{"class":254},[245,883,600],{"class":283},[245,885,310],{"class":254},[245,887,888],{"class":123,"line":313},[245,889,268],{"emptyLinePlaceholder":267},[245,891,892,894,897],{"class":123,"line":334},[245,893,316],{"class":250},[245,895,896],{"class":250}," not",[245,898,899],{"class":254}," layer.startEditing():\n",[245,901,902,904,907,909,912],{"class":123,"line":351},[245,903,337],{"class":250},[245,905,906],{"class":290}," RuntimeError",[245,908,343],{"class":254},[245,910,911],{"class":283},"\"could not start an edit session\"",[245,913,310],{"class":254},[245,915,916],{"class":123,"line":356},[245,917,268],{"emptyLinePlaceholder":267},[245,919,920,923],{"class":123,"line":367},[245,921,922],{"class":250},"try",[245,924,331],{"class":254},[245,926,927,930,932,934],{"class":123,"line":382},[245,928,929],{"class":250},"    for",[245,931,373],{"class":254},[245,933,376],{"class":250},[245,935,379],{"class":254},[245,937,938,941,944],{"class":123,"line":398},[245,939,940],{"class":254},"        layer.changeAttributeValue(feature.id(), index, ",[245,942,943],{"class":283},"\"reviewed\"",[245,945,310],{"class":254},[245,947,948,950,952],{"class":123,"line":415},[245,949,401],{"class":250},[245,951,896],{"class":250},[245,953,954],{"class":254}," layer.commitChanges():\n",[245,956,957,960,962,964,967],{"class":123,"line":421},[245,958,959],{"class":250},"        raise",[245,961,906],{"class":290},[245,963,343],{"class":254},[245,965,966],{"class":283},"\"; \"",[245,968,969],{"class":254},".join(layer.commitErrors()))\n",[245,971,972,975,978],{"class":123,"line":463},[245,973,974],{"class":250},"except",[245,976,977],{"class":290}," Exception",[245,979,331],{"class":254},[245,981,982],{"class":123,"line":474},[245,983,984],{"class":254},"    layer.rollBack()\n",[245,986,987],{"class":123,"line":479},[245,988,989],{"class":250},"    raise\n",[14,991,992,695,994,996,997,999,1000,1003,1004,1007,1008,1011,1012,1014,1015,1017],{},[212,993,518],{},[242,995,757],{}," returns ",[242,998,526],{}," on a read-only layer, so checking it fails early rather than mid-loop. ",[242,1001,1002],{},"changeAttributeValue()"," — singular — stages one change on the buffer. ",[242,1005,1006],{},"commitChanges()"," returns a bool rather than raising, and ",[242,1009,1010],{},"commitErrors()"," is the only place the provider's reason appears, so raising with that text attached is what makes the failure diagnosable. The ",[242,1013,974],{}," clause guarantees ",[242,1016,811],{}," runs on any failure; without it the layer stays in edit mode and every later operation on it fails with an unrelated-looking error.",[201,1019,1021],{"id":1020},"chunk-very-large-updates","Chunk very large updates",[14,1023,1024],{},"A single dictionary holding a million entries is fine for memory but gives you nothing to report and nothing to resume from if the write fails halfway. Chunking turns one opaque operation into a progress-reporting, restartable one.",[14,1026,1027],{},[29,1028,1031,1034,1037,1040,1043,1048,1055,1061,1064,1103,1107],{"viewBox":1029,"role":32,"ariaLabel":1030,"xmlns":34},"0 0 760 244","A single monolithic write compared with a chunked write, where each chunk reports progress and a failure only loses the chunk in flight",[36,1032,1033],{},"One write versus chunked writes",[40,1035,1036],{},"The upper bar shows a single write covering a million rows with no progress information, where a failure loses everything. The lower bar shows the same work split into five chunks, each reporting progress on completion, so a failure in the fourth chunk leaves the first three already written.",[44,1038],{"x":46,"y":46,"width":47,"height":1039,"fill":49},"244",[68,1041,1042],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Chunking buys progress and a restart point",[68,1044,1047],{"x":171,"y":110,"style":1045,"fill":73,"textAnchor":1046},"text-anchor:end;font-size:12px;font-weight:bold;font-family:sans-serif","end","one write",[44,1049],{"x":174,"y":1050,"width":1051,"height":1052,"rx":59,"fill":137,"fillOpacity":1053,"stroke":137,"style":1054},"56","540","34",0.3,"stroke-width:2",[68,1056,1060],{"x":1057,"y":1058,"style":1059,"fill":66,"textAnchor":74},"458","78","text-anchor:middle;font-size:11.5px;font-family:sans-serif","1 000 000 rows — silent until it finishes or fails",[68,1062,1063],{"x":171,"y":827,"style":1045,"fill":73,"textAnchor":1046},"chunked",[166,1065,1066,1070,1074,1077,1081,1084,1088,1091,1095,1100],{},[44,1067],{"x":174,"y":1068,"width":116,"height":1052,"rx":59,"fill":84,"fillOpacity":1069,"stroke":84,"style":1054},"130",0.4,[68,1071,1073],{"x":1072,"y":751,"style":95,"fill":66,"textAnchor":74},"238","200k ✓",[44,1075],{"x":1076,"y":1068,"width":116,"height":1052,"rx":59,"fill":84,"fillOpacity":1069,"stroke":84,"style":1054},"296",[68,1078,1080],{"x":1079,"y":751,"style":95,"fill":66,"textAnchor":74},"346","400k ✓",[44,1082],{"x":1083,"y":1068,"width":116,"height":1052,"rx":59,"fill":84,"fillOpacity":1069,"stroke":84,"style":1054},"404",[68,1085,1087],{"x":1086,"y":751,"style":95,"fill":66,"textAnchor":74},"454","600k ✓",[44,1089],{"x":1090,"y":1068,"width":116,"height":1052,"rx":59,"fill":796,"fillOpacity":1053,"stroke":796,"style":1054},"512",[68,1092,1094],{"x":1093,"y":751,"style":95,"fill":66,"textAnchor":74},"562","fails here",[44,1096],{"x":1097,"y":1068,"width":1098,"height":1052,"rx":59,"fill":83,"stroke":190,"style":1099},"620","108","stroke-width:1.5;stroke-dasharray:5 4",[68,1101,1102],{"x":193,"y":751,"style":95,"fill":190,"textAnchor":74},"not attempted",[44,1104],{"x":174,"y":155,"width":1105,"height":1052,"rx":59,"fill":84,"fillOpacity":1106,"stroke":84,"style":1054},"324",0.14,[68,1108,1110],{"x":1109,"y":159,"style":1059,"fill":84,"textAnchor":74},"350","600 000 rows already written — restart from here",[235,1112,1114],{"className":237,"code":1113,"language":239,"meta":240,"style":240},"from itertools import islice\nfrom qgis.core import QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\nindex = layer.fields().indexOf(\"rate_band\")\nprovider = layer.dataProvider()\n\nCHUNK = 50_000\n\n\ndef band(value):\n    return \"A\" if value > 1_000_000 else \"B\" if value > 400_000 else \"C\"\n\n\nrows = (\n    (f.id(), {index: band(f[\"assessed_value\"])})\n    for f in layer.getFeatures()\n    if f[\"assessed_value\"] is not None\n)\n\nwritten = 0\nwhile True:\n    batch = dict(islice(rows, CHUNK))\n    if not batch:\n        break\n    if not provider.changeAttributeValues(batch):\n        raise RuntimeError(f\"write failed after {written} row(s)\")\n    written += len(batch)\n    print(f\"{written:,} written\")\n",[242,1115,1116,1128,1138,1142,1158,1170,1180,1184,1195,1199,1203,1215,1246,1250,1254,1264,1274,1285,1305,1310,1315,1326,1337,1356,1366,1372,1382,1411,1426],{"__ignoreMap":240},[245,1117,1118,1120,1123,1125],{"class":123,"line":247},[245,1119,251],{"class":250},[245,1121,1122],{"class":254}," itertools ",[245,1124,258],{"class":250},[245,1126,1127],{"class":254}," islice\n",[245,1129,1130,1132,1134,1136],{"class":123,"line":264},[245,1131,251],{"class":250},[245,1133,255],{"class":254},[245,1135,258],{"class":250},[245,1137,261],{"class":254},[245,1139,1140],{"class":123,"line":271},[245,1141,268],{"emptyLinePlaceholder":267},[245,1143,1144,1146,1148,1150,1152,1154,1156],{"class":123,"line":296},[245,1145,274],{"class":254},[245,1147,277],{"class":250},[245,1149,280],{"class":254},[245,1151,284],{"class":283},[245,1153,287],{"class":254},[245,1155,46],{"class":290},[245,1157,293],{"class":254},[245,1159,1160,1162,1164,1166,1168],{"class":123,"line":313},[245,1161,299],{"class":254},[245,1163,277],{"class":250},[245,1165,304],{"class":254},[245,1167,307],{"class":283},[245,1169,310],{"class":254},[245,1171,1172,1175,1177],{"class":123,"line":334},[245,1173,1174],{"class":254},"provider ",[245,1176,277],{"class":250},[245,1178,1179],{"class":254}," layer.dataProvider()\n",[245,1181,1182],{"class":123,"line":351},[245,1183,268],{"emptyLinePlaceholder":267},[245,1185,1186,1189,1192],{"class":123,"line":356},[245,1187,1188],{"class":290},"CHUNK",[245,1190,1191],{"class":250}," =",[245,1193,1194],{"class":290}," 50_000\n",[245,1196,1197],{"class":123,"line":367},[245,1198,268],{"emptyLinePlaceholder":267},[245,1200,1201],{"class":123,"line":382},[245,1202,268],{"emptyLinePlaceholder":267},[245,1204,1205,1208,1212],{"class":123,"line":398},[245,1206,1207],{"class":250},"def",[245,1209,1211],{"class":1210},"svObZ"," band",[245,1213,1214],{"class":254},"(value):\n",[245,1216,1217,1220,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244],{"class":123,"line":415},[245,1218,1219],{"class":250},"    return",[245,1221,429],{"class":283},[245,1223,432],{"class":250},[245,1225,404],{"class":254},[245,1227,437],{"class":250},[245,1229,440],{"class":290},[245,1231,443],{"class":250},[245,1233,446],{"class":283},[245,1235,432],{"class":250},[245,1237,404],{"class":254},[245,1239,437],{"class":250},[245,1241,455],{"class":290},[245,1243,443],{"class":250},[245,1245,460],{"class":283},[245,1247,1248],{"class":123,"line":421},[245,1249,268],{"emptyLinePlaceholder":267},[245,1251,1252],{"class":123,"line":463},[245,1253,268],{"emptyLinePlaceholder":267},[245,1255,1256,1259,1261],{"class":123,"line":474},[245,1257,1258],{"class":254},"rows ",[245,1260,277],{"class":250},[245,1262,1263],{"class":254}," (\n",[245,1265,1266,1269,1271],{"class":123,"line":479},[245,1267,1268],{"class":254},"    (f.id(), {index: band(f[",[245,1270,393],{"class":283},[245,1272,1273],{"class":254},"])})\n",[245,1275,1276,1278,1280,1282],{"class":123,"line":490},[245,1277,929],{"class":250},[245,1279,665],{"class":254},[245,1281,376],{"class":250},[245,1283,1284],{"class":254}," layer.getFeatures()\n",[245,1286,1288,1290,1293,1295,1298,1300,1302],{"class":123,"line":1287},18,[245,1289,401],{"class":250},[245,1291,1292],{"class":254}," f[",[245,1294,393],{"class":283},[245,1296,1297],{"class":254},"] ",[245,1299,407],{"class":250},[245,1301,896],{"class":250},[245,1303,1304],{"class":290}," None\n",[245,1306,1308],{"class":123,"line":1307},19,[245,1309,310],{"class":254},[245,1311,1313],{"class":123,"line":1312},20,[245,1314,268],{"emptyLinePlaceholder":267},[245,1316,1318,1321,1323],{"class":123,"line":1317},21,[245,1319,1320],{"class":254},"written ",[245,1322,277],{"class":250},[245,1324,1325],{"class":290}," 0\n",[245,1327,1329,1332,1335],{"class":123,"line":1328},22,[245,1330,1331],{"class":250},"while",[245,1333,1334],{"class":290}," True",[245,1336,331],{"class":254},[245,1338,1340,1343,1345,1348,1351,1353],{"class":123,"line":1339},23,[245,1341,1342],{"class":254},"    batch ",[245,1344,277],{"class":250},[245,1346,1347],{"class":290}," dict",[245,1349,1350],{"class":254},"(islice(rows, ",[245,1352,1188],{"class":290},[245,1354,1355],{"class":254},"))\n",[245,1357,1359,1361,1363],{"class":123,"line":1358},24,[245,1360,401],{"class":250},[245,1362,896],{"class":250},[245,1364,1365],{"class":254}," batch:\n",[245,1367,1369],{"class":123,"line":1368},25,[245,1370,1371],{"class":250},"        break\n",[245,1373,1375,1377,1379],{"class":123,"line":1374},26,[245,1376,401],{"class":250},[245,1378,896],{"class":250},[245,1380,1381],{"class":254}," provider.changeAttributeValues(batch):\n",[245,1383,1385,1387,1389,1391,1394,1397,1400,1403,1406,1409],{"class":123,"line":1384},27,[245,1386,959],{"class":250},[245,1388,906],{"class":290},[245,1390,343],{"class":254},[245,1392,1393],{"class":250},"f",[245,1395,1396],{"class":283},"\"write failed after ",[245,1398,1399],{"class":290},"{",[245,1401,1402],{"class":254},"written",[245,1404,1405],{"class":290},"}",[245,1407,1408],{"class":283}," row(s)\"",[245,1410,310],{"class":254},[245,1412,1414,1417,1420,1423],{"class":123,"line":1413},28,[245,1415,1416],{"class":254},"    written ",[245,1418,1419],{"class":250},"+=",[245,1421,1422],{"class":290}," len",[245,1424,1425],{"class":254},"(batch)\n",[245,1427,1429,1432,1434,1436,1439,1441,1443,1446,1448,1451],{"class":123,"line":1428},29,[245,1430,1431],{"class":290},"    print",[245,1433,343],{"class":254},[245,1435,1393],{"class":250},[245,1437,1438],{"class":283},"\"",[245,1440,1399],{"class":290},[245,1442,1402],{"class":254},[245,1444,1445],{"class":250},":,",[245,1447,1405],{"class":290},[245,1449,1450],{"class":283}," written\"",[245,1452,310],{"class":254},[14,1454,1455,695,1457,1460,1461,1464,1465,1468,1469,1471],{},[212,1456,518],{},[242,1458,1459],{},"rows"," is a generator, so no feature is materialised until ",[242,1462,1463],{},"islice"," pulls it — memory stays flat regardless of layer size. ",[242,1466,1467],{},"islice(rows, CHUNK)"," takes the next slice from the same generator each time round the loop, which is what makes the pattern work without an index variable. Reporting ",[242,1470,1402],{}," in the exception message tells you exactly where to resume. A chunk size of fifty thousand is a reasonable default; smaller chunks give finer progress at the cost of more round-trips.",[201,1473,1475],{"id":1474},"qgis-version-compatibility","QGIS version compatibility",[14,1477,1478,1479,1481],{},"The examples target ",[212,1480,214],{}," (Python 3.12).",[1483,1484,1485,1501],"table",{},[1486,1487,1488],"thead",{},[1489,1490,1491,1495,1498],"tr",{},[1492,1493,1494],"th",{},"QGIS version",[1492,1496,1497],{},"Python",[1492,1499,1500],{},"Notes",[1502,1503,1504,1516,1527],"tbody",{},[1489,1505,1506,1510,1513],{},[1507,1508,1509],"td",{},"3.28 LTR",[1507,1511,1512],{},"3.9",[1507,1514,1515],{},"Identical API for both routes.",[1489,1517,1518,1521,1524],{},[1507,1519,1520],{},"3.34 LTR",[1507,1522,1523],{},"3.12",[1507,1525,1526],{},"Baseline for this page.",[1489,1528,1529,1532,1534],{},[1507,1530,1531],{},"3.40 \u002F 3.44",[1507,1533,1523],{},[1507,1535,1536,1539],{},[242,1537,1538],{},"QgsFeatureRequest.Flag.NoGeometry"," is the newer spelling; the old constant still resolves.",[14,1541,1542,501,1544,501,1546,1548,1549,1551],{},[242,1543,522],{},[242,1545,757],{},[242,1547,1006],{}," and ",[242,1550,811],{}," are unchanged across 3.x.",[201,1553,1555],{"id":1554},"troubleshooting","Troubleshooting",[206,1557,1558,1572,1580,1590,1601,1610],{},[209,1559,1560,1563,1564,1567,1568,1571],{},[212,1561,1562],{},"Nothing was written and no error appeared."," The field index was ",[242,1565,1566],{},"-1",". Resolve it with ",[242,1569,1570],{},"indexOf()"," and raise if it is negative.",[209,1573,1574,1579],{},[212,1575,1576,1578],{},[242,1577,522],{}," returned False."," The provider rejected the batch — often a type mismatch, such as writing a string into a numeric column, or a constraint violation.",[209,1581,1582,1585,1586,1589],{},[212,1583,1584],{},"The layer is stuck and every later call fails."," It is still in edit mode from a failed run. Call ",[242,1587,1588],{},"layer.rollBack()",", and wrap future edit sessions so it cannot happen again.",[209,1591,1592,695,1595,1597,1598,1600],{},[212,1593,1594],{},"A filter matched nothing.",[242,1596,714],{}," was used where ",[242,1599,710],{}," was meant. The former is valid and never true.",[209,1602,1603,1606,1607,1609],{},[212,1604,1605],{},"The update is very slow."," Per-feature edits through the buffer. Batch them into one ",[242,1608,522],{}," call unless undo support is genuinely required.",[209,1611,1612,1615,1616,1619],{},[212,1613,1614],{},"Values look right in the console but not on the map."," The canvas is showing a cached render. Call ",[242,1617,1618],{},"layer.triggerRepaint()",".",[201,1621,1623],{"id":1622},"conclusion","Conclusion",[14,1625,1626,1627,1630,1631,1633],{},"For scripted bulk work, build the entire change set as a ",[242,1628,1629],{},"{fid: {index: value}}"," dictionary and send it in one ",[242,1632,522],{}," call, checking the return value. For anything a user will interact with, use the edit buffer and make sure every session reaches either a verified commit or a rollback — a layer abandoned in edit mode is a problem that surfaces far away from its cause.",[201,1635,1637],{"id":1636},"frequently-asked-questions","Frequently Asked Questions",[14,1639,1640,1643,1644,1646],{},[212,1641,1642],{},"Which is faster, the provider or the edit buffer?","\nThe provider, substantially. One batched ",[242,1645,522],{}," call is a single round-trip; the edit buffer pushes every change onto an undo stack individually. Use the buffer only when undo support matters.",[14,1648,1649,1652,1653,1655,1656,1658,1659,1661],{},[212,1650,1651],{},"Why did my update silently do nothing?","\nAlmost always a field index of ",[242,1654,1566],{},", which ",[242,1657,522],{}," accepts and ignores. Resolve the index with ",[242,1660,1570],{}," and raise explicitly when it is negative.",[14,1663,1664,1667,996,1669,1671,1672,1675],{},[212,1665,1666],{},"How do I know why a commit failed?",[242,1668,1006],{},[242,1670,526],{}," rather than raising. ",[242,1673,1674],{},"layer.commitErrors()"," returns the list of provider messages explaining what was rejected — include it in the exception you raise.",[14,1677,1678,1681,1682,1684,1685,1687,1688,1619],{},[212,1679,1680],{},"Does changeAttributeValues() validate the values I pass?","\nOnly loosely. The provider rejects a value it cannot store — text into a numeric column, for example — and returns ",[242,1683,526],{}," for the whole batch, but it does not enforce field constraints or default-value expressions configured in the project. Those live in the QGIS layer rather than the data source, so a provider write bypasses them entirely. If constraints matter, go through the edit buffer, where ",[242,1686,1006],{}," applies them and reports violations through ",[242,1689,1010],{},[14,1691,1692,1695,1696,1699],{},[212,1693,1694],{},"How do I roll back an update I already committed?","\nYou cannot, through either route — a commit is final and the provider route never had an undo. The practical protections are to work on a copy, to write to a new layer with ",[242,1697,1698],{},"native:fieldcalculator"," rather than editing in place, or to snapshot the original values into a spare column before overwriting them. On a GeoPackage, copying the file before a bulk update takes a second and has saved many afternoons.",[14,1701,1702,1705,1706,1709,1710,1713,1714,1717,1718,1619],{},[212,1703,1704],{},"Can I update only the selected features?","\nYes. Iterate ",[242,1707,1708],{},"layer.getSelectedFeatures()"," instead of ",[242,1711,1712],{},"getFeatures()",", or build a request filtered by ",[242,1715,1716],{},"layer.selectedFeatureIds()",". See ",[21,1719,1721],{"href":1720},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fselect-features-by-expression-pyqgis\u002F","Select Features by Expression in PyQGIS",[14,1723,1724,1727,1728,1730],{},[212,1725,1726],{},"Does changing a value trigger a repaint?","\nNot automatically. Call ",[242,1729,1618],{}," when the changed field drives symbology or labelling, or the map keeps drawing from its cached render.",[201,1732,1734],{"id":1733},"related","Related",[206,1736,1737,1742,1746,1752],{},[209,1738,1739,1741],{},[21,1740,24],{"href":23}," — the guide this recipe belongs to",[209,1743,1744],{},[21,1745,222],{"href":221},[209,1747,1748],{},[21,1749,1751],{"href":1750},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fcount-features-by-attribute-pyqgis\u002F","Count and Summarise Features by Attribute in PyQGIS",[209,1753,1754],{},[21,1755,1757],{"href":1756},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002F","Working with QGIS Expressions in PyQGIS",[1759,1760,1761],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":240,"searchDepth":264,"depth":264,"links":1763},[1764,1765,1766,1767,1768,1769,1770,1771,1772,1773],{"id":203,"depth":264,"text":204},{"id":229,"depth":264,"text":230},{"id":549,"depth":264,"text":550},{"id":718,"depth":264,"text":719},{"id":1020,"depth":264,"text":1021},{"id":1474,"depth":264,"text":1475},{"id":1554,"depth":264,"text":1555},{"id":1622,"depth":264,"text":1623},{"id":1636,"depth":264,"text":1637},{"id":1733,"depth":264,"text":1734},"Update attribute values across many features in PyQGIS. Batch changes through the provider, use the edit buffer for undo, and handle commit failures safely.","md",{"slug":1777,"type":1778,"breadcrumb":1779,"datePublished":1780,"dateModified":1780},"update-attribute-values-pyqgis","article","Update Attribute Values","2026-08-01","\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fupdate-attribute-values-pyqgis",{"title":5,"description":1774},"spatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fupdate-attribute-values-pyqgis\u002Findex","YSTxdMIDYW-f3batYz6tIwgpJaSh-ziJzENv-n6wUcA",1785585990475]