[{"data":1,"prerenderedAt":2089},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fread-feature-attributes-and-geometry-pyqgis":3},{"id":4,"title":5,"body":6,"description":2078,"extension":2079,"meta":2080,"navigation":265,"path":2085,"seo":2086,"stem":2087,"__hash__":2088},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fread-feature-attributes-and-geometry-pyqgis\u002Findex.md","Read Feature Attributes and Geometry in PyQGIS",{"type":7,"value":8,"toc":2063},"minimark",[9,13,22,31,186,191,219,223,233,413,448,452,469,640,675,679,693,783,975,1016,1020,1023,1170,1198,1202,1205,1281,1368,1389,1393,1403,1587,1618,1622,1625,1862,1886,1890,1909,1913,1963,1967,1976,1980,1993,2007,2020,2026,2030,2059],[10,11,5],"h1",{"id":12},"read-feature-attributes-and-geometry-in-pyqgis",[14,15,16,17,21],"p",{},"Almost every PyQGIS script reads features. It is the first thing people learn and the place where the most subtle bugs hide: a list of features whose geometries all turn out identical, attribute values that compare unequal to ",[18,19,20],"code",{},"None"," even though they are empty, a loop over a large layer that takes minutes because it fetched forty columns to use two. None of these are hard to avoid once you know what a feature iterator actually returns.",[14,23,24,25,30],{},"This recipe belongs to ",[26,27,29],"a",{"href":28},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002F","Features, Geometries & Memory Layers",". It covers iterating a layer, reading attributes in each of the useful ways, reading geometry and its parts, fetching single features and selections, and limiting what the provider reads.",[14,32,33],{},[34,35,40,44,48,55,72,81,90,96,102,106,113,118,122,125,128,132,137,141,145,148,154,158,165,171,175,179,183],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 280","img","A feature request flows to the data provider, which returns an iterator of features whose attributes and geometry Python reads in several ways","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"From a request to values in Python",[45,46,47],"desc",{},"A QgsFeatureRequest describes which features and which columns to fetch, with a filter rectangle, an expression or a list of ids. layer.getFeatures passes it to the data provider, which returns a QgsFeatureIterator. Each step yields a QgsFeature holding an attribute list and a geometry. Python reads values by name, by index or as a dictionary, and reads the geometry through asPoint, asPolyline, asPolygon or the vertices iterator.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","280","#f6f3ea",[56,57,58],"defs",{},[59,60,67],"marker",{"id":61,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"readFlowArrow","0 0 10 10","8","5","7","auto-start-reverse",[68,69],"path",{"d":70,"fill":71},"M0 0 L10 5 L0 10 z","#2f3b35",[73,74,80],"text",{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","What getFeatures actually hands you",[49,82],{"x":83,"y":84,"width":85,"height":86,"rx":63,"fill":87,"stroke":88,"style":89},"24","60","180","96","#eff3ff","#2563eb","stroke-width:2",[73,91,95],{"x":92,"y":93,"style":94,"fill":88,"textAnchor":79},"114","91.83","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","QgsFeatureRequest",[73,97,101],{"x":92,"y":98,"style":99,"fill":100,"textAnchor":79},"111.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","#59645f","which features?",[73,103,105],{"x":92,"y":104,"style":99,"fill":100,"textAnchor":79},"131.73","which columns?",[107,108],"line",{"x1":109,"y1":110,"x2":111,"y2":110,"stroke":71,"style":112},"204","108","246","stroke-width:1.8;marker-end:url(#readFlowArrow)",[49,114],{"x":115,"y":84,"width":85,"height":86,"rx":63,"fill":116,"stroke":117,"style":89},"254","#eef7f4","#0f766e",[73,119,121],{"x":120,"y":93,"style":94,"fill":117,"textAnchor":79},"344","data provider",[73,123,124],{"x":120,"y":98,"style":99,"fill":100,"textAnchor":79},"reads only",[73,126,127],{"x":120,"y":104,"style":99,"fill":100,"textAnchor":79},"what was asked",[107,129],{"x1":130,"y1":110,"x2":131,"y2":110,"stroke":71,"style":112},"434","476",[49,133],{"x":134,"y":84,"width":135,"height":86,"rx":63,"fill":136,"stroke":100,"style":89},"484","252","#fffdf7",[73,138,140],{"x":139,"y":93,"style":94,"fill":78,"textAnchor":79},"610","QgsFeature, one at a time",[73,142,144],{"x":139,"y":98,"style":143,"fill":71,"textAnchor":79},"text-anchor:middle;font-size:10.0px;font-family:monospace","attributes()  → list",[73,146,147],{"x":139,"y":104,"style":143,"fill":71,"textAnchor":79},"geometry()  → QgsGeometry",[107,149],{"x1":150,"y1":151,"x2":152,"y2":153,"stroke":71,"style":112},"560","156","460","196",[107,155],{"x1":156,"y1":151,"x2":157,"y2":153,"stroke":71,"style":112},"660","640",[49,159],{"x":160,"y":161,"width":162,"height":84,"rx":63,"fill":163,"stroke":164,"style":89},"240","200","260","#e8efe6","#15803d",[73,166,170],{"x":167,"y":168,"style":94,"fill":169,"textAnchor":79},"370","223.805","#166534","values",[73,172,174],{"x":167,"y":173,"style":143,"fill":71,"textAnchor":79},"243.755","f[\"name\"] · f.attribute(i)",[49,176],{"x":177,"y":161,"width":178,"height":84,"rx":63,"fill":163,"stroke":164,"style":89},"520","216",[73,180,182],{"x":181,"y":168,"style":94,"fill":169,"textAnchor":79},"628","shape",[73,184,185],{"x":181,"y":173,"style":143,"fill":71,"textAnchor":79},"asPoint() · vertices()",[187,188,190],"h2",{"id":189},"prerequisites","Prerequisites",[192,193,194,198],"ul",{},[195,196,197],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[195,199,200,201,204,205,207,208,207,211,214,215,218],{},"A loaded vector layer. The examples use a polygon layer called ",[18,202,203],{},"districts"," with fields ",[18,206,18],{},", ",[18,209,210],{},"name",[18,212,213],{},"population"," and ",[18,216,217],{},"area_km2",".",[187,220,222],{"id":221},"iterate-a-layer-and-read-attributes","Iterate a layer and read attributes",[14,224,225,228,229,232],{},[18,226,227],{},"layer.getFeatures()"," returns an iterator. Each item is a ",[18,230,231],{},"QgsFeature",", and attributes can be read by field name, by position, or all at once.",[234,235,240],"pre",{"className":236,"code":237,"language":238,"meta":239,"style":239},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject\n\ndistricts = QgsProject.instance().mapLayersByName(\"districts\")[0]\nfields = districts.fields()\npop_idx = fields.indexOf(\"population\")\n\nfor f in districts.getFeatures():\n    code = f[\"code\"]                       # by name\n    pop = f.attribute(pop_idx)             # by index: faster in tight loops\n    row = dict(zip(fields.names(), f.attributes()))   # whole row as a dict\n    print(f.id(), code, pop, row[\"name\"])\n","python","",[18,241,242,260,267,292,303,320,325,340,361,375,398],{"__ignoreMap":239},[243,244,246,250,254,257],"span",{"class":107,"line":245},1,[243,247,249],{"class":248},"snl16","from",[243,251,253],{"class":252},"s95oV"," qgis.core ",[243,255,256],{"class":248},"import",[243,258,259],{"class":252}," QgsProject\n",[243,261,263],{"class":107,"line":262},2,[243,264,266],{"emptyLinePlaceholder":265},true,"\n",[243,268,270,273,276,279,283,286,289],{"class":107,"line":269},3,[243,271,272],{"class":252},"districts ",[243,274,275],{"class":248},"=",[243,277,278],{"class":252}," QgsProject.instance().mapLayersByName(",[243,280,282],{"class":281},"sU2Wk","\"districts\"",[243,284,285],{"class":252},")[",[243,287,51],{"class":288},"sDLfK",[243,290,291],{"class":252},"]\n",[243,293,295,298,300],{"class":107,"line":294},4,[243,296,297],{"class":252},"fields ",[243,299,275],{"class":248},[243,301,302],{"class":252}," districts.fields()\n",[243,304,306,309,311,314,317],{"class":107,"line":305},5,[243,307,308],{"class":252},"pop_idx ",[243,310,275],{"class":248},[243,312,313],{"class":252}," fields.indexOf(",[243,315,316],{"class":281},"\"population\"",[243,318,319],{"class":252},")\n",[243,321,323],{"class":107,"line":322},6,[243,324,266],{"emptyLinePlaceholder":265},[243,326,328,331,334,337],{"class":107,"line":327},7,[243,329,330],{"class":248},"for",[243,332,333],{"class":252}," f ",[243,335,336],{"class":248},"in",[243,338,339],{"class":252}," districts.getFeatures():\n",[243,341,343,346,348,351,354,357],{"class":107,"line":342},8,[243,344,345],{"class":252},"    code ",[243,347,275],{"class":248},[243,349,350],{"class":252}," f[",[243,352,353],{"class":281},"\"code\"",[243,355,356],{"class":252},"]                       ",[243,358,360],{"class":359},"sjoCn","# by name\n",[243,362,364,367,369,372],{"class":107,"line":363},9,[243,365,366],{"class":252},"    pop ",[243,368,275],{"class":248},[243,370,371],{"class":252}," f.attribute(pop_idx)             ",[243,373,374],{"class":359},"# by index: faster in tight loops\n",[243,376,378,381,383,386,389,392,395],{"class":107,"line":377},10,[243,379,380],{"class":252},"    row ",[243,382,275],{"class":248},[243,384,385],{"class":288}," dict",[243,387,388],{"class":252},"(",[243,390,391],{"class":288},"zip",[243,393,394],{"class":252},"(fields.names(), f.attributes()))   ",[243,396,397],{"class":359},"# whole row as a dict\n",[243,399,401,404,407,410],{"class":107,"line":400},11,[243,402,403],{"class":288},"    print",[243,405,406],{"class":252},"(f.id(), code, pop, row[",[243,408,409],{"class":281},"\"name\"",[243,411,412],{"class":252},"])\n",[14,414,415,419,420,423,424,427,428,431,432,435,436,439,440,443,444,447],{},[416,417,418],"strong",{},"Breakdown:"," ",[18,421,422],{},"f[\"code\"]"," looks the name up in the feature's fields every call, which is fine for readability and costs a little in loops over millions of features — resolving the index once with ",[18,425,426],{},"indexOf"," and using ",[18,429,430],{},"attribute(idx)"," avoids that. ",[18,433,434],{},"f.attributes()"," returns values in field order, so zipping with ",[18,437,438],{},"fields.names()"," produces a dictionary in one line, convenient for writing out JSON or building a pandas row. ",[18,441,442],{},"f.id()"," is the feature id the provider assigned — on GeoPackage the ",[18,445,446],{},"fid",", on shapefiles a zero-based row number that can change when the file is rewritten — so it is a handle, not a stable business key.",[187,449,451],{"id":450},"treat-null-values-correctly","Treat NULL values correctly",[14,453,454,455,457,458,461,462,465,466,468],{},"Empty attributes are not Python ",[18,456,20],{}," in QGIS 3. They are ",[18,459,460],{},"NULL"," — a ",[18,463,464],{},"QVariant"," that is falsy but not equal to ",[18,467,20],{}," — and that one detail breaks more comparisons than anything else in PyQGIS.",[234,470,472],{"className":236,"code":471,"language":238,"meta":239,"style":239},"from qgis.core import NULL\n\nfor f in districts.getFeatures():\n    pop = f[\"population\"]\n    if pop == NULL:                 # correct on QGIS 3\n        print(f[\"code\"], \"has no population value\")\n    elif pop > 100_000:\n        print(f[\"code\"], \"is large\")\n\n# portable check, works on QGIS 3 and QGIS 4\ndef is_null(value):\n    return value is None or (hasattr(value, \"isNull\") and value.isNull())\n",[18,473,474,485,489,499,511,531,549,565,580,584,589,601],{"__ignoreMap":239},[243,475,476,478,480,482],{"class":107,"line":245},[243,477,249],{"class":248},[243,479,253],{"class":252},[243,481,256],{"class":248},[243,483,484],{"class":288}," NULL\n",[243,486,487],{"class":107,"line":262},[243,488,266],{"emptyLinePlaceholder":265},[243,490,491,493,495,497],{"class":107,"line":269},[243,492,330],{"class":248},[243,494,333],{"class":252},[243,496,336],{"class":248},[243,498,339],{"class":252},[243,500,501,503,505,507,509],{"class":107,"line":294},[243,502,366],{"class":252},[243,504,275],{"class":248},[243,506,350],{"class":252},[243,508,316],{"class":281},[243,510,291],{"class":252},[243,512,513,516,519,522,525,528],{"class":107,"line":305},[243,514,515],{"class":248},"    if",[243,517,518],{"class":252}," pop ",[243,520,521],{"class":248},"==",[243,523,524],{"class":288}," NULL",[243,526,527],{"class":252},":                 ",[243,529,530],{"class":359},"# correct on QGIS 3\n",[243,532,533,536,539,541,544,547],{"class":107,"line":322},[243,534,535],{"class":288},"        print",[243,537,538],{"class":252},"(f[",[243,540,353],{"class":281},[243,542,543],{"class":252},"], ",[243,545,546],{"class":281},"\"has no population value\"",[243,548,319],{"class":252},[243,550,551,554,556,559,562],{"class":107,"line":327},[243,552,553],{"class":248},"    elif",[243,555,518],{"class":252},[243,557,558],{"class":248},">",[243,560,561],{"class":288}," 100_000",[243,563,564],{"class":252},":\n",[243,566,567,569,571,573,575,578],{"class":107,"line":342},[243,568,535],{"class":288},[243,570,538],{"class":252},[243,572,353],{"class":281},[243,574,543],{"class":252},[243,576,577],{"class":281},"\"is large\"",[243,579,319],{"class":252},[243,581,582],{"class":107,"line":363},[243,583,266],{"emptyLinePlaceholder":265},[243,585,586],{"class":107,"line":377},[243,587,588],{"class":359},"# portable check, works on QGIS 3 and QGIS 4\n",[243,590,591,594,598],{"class":107,"line":400},[243,592,593],{"class":248},"def",[243,595,597],{"class":596},"svObZ"," is_null",[243,599,600],{"class":252},"(value):\n",[243,602,604,607,610,613,616,619,622,625,628,631,634,637],{"class":107,"line":603},12,[243,605,606],{"class":248},"    return",[243,608,609],{"class":252}," value ",[243,611,612],{"class":248},"is",[243,614,615],{"class":288}," None",[243,617,618],{"class":248}," or",[243,620,621],{"class":252}," (",[243,623,624],{"class":288},"hasattr",[243,626,627],{"class":252},"(value, ",[243,629,630],{"class":281},"\"isNull\"",[243,632,633],{"class":252},") ",[243,635,636],{"class":248},"and",[243,638,639],{"class":252}," value.isNull())\n",[14,641,642,644,645,648,649,651,652,655,656,658,659,662,663,665,666,669,670,674],{},[416,643,418],{}," On QGIS 3, ",[18,646,647],{},"f[\"population\"] is None"," is always False for a NULL value, so a script that tests for ",[18,650,20],{}," treats every empty value as present and then fails at ",[18,653,654],{},"pop > 100_000",". Comparing with the ",[18,657,460],{}," constant works, as does the truthiness test ",[18,660,661],{},"if not pop",", though that also catches a genuine zero. QGIS 4 returns plain ",[18,664,20],{}," for NULL values; the ",[18,667,668],{},"is_null"," helper works on both. ",[26,671,673],{"href":672},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fhandle-null-values-and-qvariant-pyqgis\u002F","Handling NULL values and QVariant"," goes through every case, including dates and writing NULL back.",[187,676,678],{"id":677},"read-geometry-and-its-parts","Read geometry and its parts",[14,680,681,684,685,688,689,692],{},[18,682,683],{},"f.geometry()"," returns a ",[18,686,687],{},"QgsGeometry",". What you do with it depends on the geometry type: the ",[18,690,691],{},"as…"," methods return Python lists of points, while the vertices iterator walks every coordinate whatever the type.",[14,694,695],{},[34,696,699,702,705,708,711,716,721,725,729,733,737,740,742,745,747,750,753,758,762,765,768,771,774,779],{"viewBox":697,"role":37,"ariaLabel":698,"xmlns":39},"0 0 760 270","Accessors for points, lines and polygons, single and multipart, with the vertices iterator as the type-independent alternative that keeps Z and M",[41,700,701],{},"Which accessor for which geometry",[45,703,704],{},"Point geometry: asPoint returns one QgsPointXY; asMultiPoint returns a list. Line geometry: asPolyline returns a list of points; asMultiPolyline returns a list of lists. Polygon geometry: asPolygon returns a list of rings, the first being the exterior; asMultiPolygon adds another level of nesting. The vertices iterator works for all types and keeps Z and M values, which the as methods drop.",[49,706],{"x":51,"y":51,"width":52,"height":707,"fill":54},"270",[73,709,710],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"The as… methods follow the nesting of the type",[49,712],{"x":83,"y":713,"width":714,"height":715,"rx":63,"fill":116,"stroke":117,"style":89},"52","222","140",[73,717,720],{"x":718,"y":719,"style":94,"fill":117,"textAnchor":79},"135","85.88","point",[73,722,724],{"x":718,"y":723,"style":143,"fill":71,"textAnchor":79},"105.83","asPoint()",[73,726,728],{"x":718,"y":727,"style":99,"fill":100,"textAnchor":79},"125.78","→ QgsPointXY",[73,730,732],{"x":718,"y":731,"style":143,"fill":71,"textAnchor":79},"145.73","asMultiPoint()",[73,734,736],{"x":718,"y":735,"style":99,"fill":100,"textAnchor":79},"165.68","→ list of points",[49,738],{"x":739,"y":713,"width":714,"height":715,"rx":63,"fill":87,"stroke":88,"style":89},"269",[73,741,107],{"x":75,"y":719,"style":94,"fill":88,"textAnchor":79},[73,743,744],{"x":75,"y":723,"style":143,"fill":71,"textAnchor":79},"asPolyline()",[73,746,736],{"x":75,"y":727,"style":99,"fill":100,"textAnchor":79},[73,748,749],{"x":75,"y":731,"style":143,"fill":71,"textAnchor":79},"asMultiPolyline()",[73,751,752],{"x":75,"y":735,"style":99,"fill":100,"textAnchor":79},"→ list of lists",[49,754],{"x":755,"y":713,"width":714,"height":715,"rx":63,"fill":756,"stroke":757,"style":89},"514","#fdf2e2","#b45309",[73,759,761],{"x":760,"y":719,"style":94,"fill":757,"textAnchor":79},"625","polygon",[73,763,764],{"x":760,"y":723,"style":143,"fill":71,"textAnchor":79},"asPolygon()",[73,766,767],{"x":760,"y":727,"style":99,"fill":100,"textAnchor":79},"→ list of rings",[73,769,770],{"x":760,"y":731,"style":143,"fill":71,"textAnchor":79},"asMultiPolygon()",[73,772,773],{"x":760,"y":735,"style":99,"fill":100,"textAnchor":79},"→ list of polygons",[49,775],{"x":715,"y":776,"width":777,"height":778,"rx":63,"fill":163,"stroke":164,"style":89},"210","480","46",[73,780,782],{"x":75,"y":781,"style":94,"fill":169,"textAnchor":79},"236.78","vertices() — every type, keeps Z and M",[234,784,786],{"className":236,"code":785,"language":238,"meta":239,"style":239},"from qgis.core import QgsWkbTypes\n\nfeature = next(districts.getFeatures())\ngeom = feature.geometry()\nprint(QgsWkbTypes.displayString(geom.wkbType()), geom.isMultipart())\n\nif geom.isMultipart():\n    parts = geom.asMultiPolygon()\n    exterior = parts[0][0]\nelse:\n    exterior = geom.asPolygon()[0]\nprint(len(exterior), \"vertices in the first exterior ring\")\n\nprint(\"area\", geom.area(), \"centroid\", geom.centroid().asPoint())\nfor v in geom.vertices():\n    print(v.x(), v.y(), v.z() if v.is3D() else \"\")\n    break\n",[18,787,788,799,803,816,826,834,838,846,856,875,882,895,912,917,936,949,969],{"__ignoreMap":239},[243,789,790,792,794,796],{"class":107,"line":245},[243,791,249],{"class":248},[243,793,253],{"class":252},[243,795,256],{"class":248},[243,797,798],{"class":252}," QgsWkbTypes\n",[243,800,801],{"class":107,"line":262},[243,802,266],{"emptyLinePlaceholder":265},[243,804,805,808,810,813],{"class":107,"line":269},[243,806,807],{"class":252},"feature ",[243,809,275],{"class":248},[243,811,812],{"class":288}," next",[243,814,815],{"class":252},"(districts.getFeatures())\n",[243,817,818,821,823],{"class":107,"line":294},[243,819,820],{"class":252},"geom ",[243,822,275],{"class":248},[243,824,825],{"class":252}," feature.geometry()\n",[243,827,828,831],{"class":107,"line":305},[243,829,830],{"class":288},"print",[243,832,833],{"class":252},"(QgsWkbTypes.displayString(geom.wkbType()), geom.isMultipart())\n",[243,835,836],{"class":107,"line":322},[243,837,266],{"emptyLinePlaceholder":265},[243,839,840,843],{"class":107,"line":327},[243,841,842],{"class":248},"if",[243,844,845],{"class":252}," geom.isMultipart():\n",[243,847,848,851,853],{"class":107,"line":342},[243,849,850],{"class":252},"    parts ",[243,852,275],{"class":248},[243,854,855],{"class":252}," geom.asMultiPolygon()\n",[243,857,858,861,863,866,868,871,873],{"class":107,"line":363},[243,859,860],{"class":252},"    exterior ",[243,862,275],{"class":248},[243,864,865],{"class":252}," parts[",[243,867,51],{"class":288},[243,869,870],{"class":252},"][",[243,872,51],{"class":288},[243,874,291],{"class":252},[243,876,877,880],{"class":107,"line":377},[243,878,879],{"class":248},"else",[243,881,564],{"class":252},[243,883,884,886,888,891,893],{"class":107,"line":400},[243,885,860],{"class":252},[243,887,275],{"class":248},[243,889,890],{"class":252}," geom.asPolygon()[",[243,892,51],{"class":288},[243,894,291],{"class":252},[243,896,897,899,901,904,907,910],{"class":107,"line":603},[243,898,830],{"class":288},[243,900,388],{"class":252},[243,902,903],{"class":288},"len",[243,905,906],{"class":252},"(exterior), ",[243,908,909],{"class":281},"\"vertices in the first exterior ring\"",[243,911,319],{"class":252},[243,913,915],{"class":107,"line":914},13,[243,916,266],{"emptyLinePlaceholder":265},[243,918,920,922,924,927,930,933],{"class":107,"line":919},14,[243,921,830],{"class":288},[243,923,388],{"class":252},[243,925,926],{"class":281},"\"area\"",[243,928,929],{"class":252},", geom.area(), ",[243,931,932],{"class":281},"\"centroid\"",[243,934,935],{"class":252},", geom.centroid().asPoint())\n",[243,937,939,941,944,946],{"class":107,"line":938},15,[243,940,330],{"class":248},[243,942,943],{"class":252}," v ",[243,945,336],{"class":248},[243,947,948],{"class":252}," geom.vertices():\n",[243,950,952,954,957,959,962,964,967],{"class":107,"line":951},16,[243,953,403],{"class":288},[243,955,956],{"class":252},"(v.x(), v.y(), v.z() ",[243,958,842],{"class":248},[243,960,961],{"class":252}," v.is3D() ",[243,963,879],{"class":248},[243,965,966],{"class":281}," \"\"",[243,968,319],{"class":252},[243,970,972],{"class":107,"line":971},17,[243,973,974],{"class":248},"    break\n",[14,976,977,979,980,982,983,986,987,990,991,993,994,997,998,1001,1002,1005,1006,214,1009,1012,1013,218],{},[416,978,418],{}," Calling ",[18,981,764],{}," on a multipolygon returns an empty list rather than raising, which is why checking ",[18,984,985],{},"isMultipart()"," first matters; many layers declared as ",[18,988,989],{},"MultiPolygon"," hold only single-part shapes, and code written against one sample can break on the next. The ",[18,992,691],{}," methods return 2D ",[18,995,996],{},"QgsPointXY"," objects. ",[18,999,1000],{},"vertices()"," yields ",[18,1003,1004],{},"QgsPoint"," objects with Z and M preserved, and works the same on every type, which makes it the right choice for generic code. ",[18,1007,1008],{},"area()",[18,1010,1011],{},"length()"," are in layer units; for ellipsoidal measurements use ",[18,1014,1015],{},"QgsDistanceArea",[187,1017,1019],{"id":1018},"fetch-single-features-ids-and-selections","Fetch single features, ids and selections",[14,1021,1022],{},"Not every task needs the whole layer. Fetching one feature by id, the current selection, or a list of ids from an earlier step avoids a full scan.",[234,1024,1026],{"className":236,"code":1025,"language":238,"meta":239,"style":239},"from qgis.core import QgsFeatureRequest\n\none = districts.getFeature(17)\nif not one.isValid():\n    print(\"no feature with id 17\")\n\nselected = districts.selectedFeatures()          # list, all in memory\nfor f in districts.getSelectedFeatures():        # iterator, lazy\n    print(f[\"name\"])\n\nids = [3, 17, 42]\nfor f in districts.getFeatures(QgsFeatureRequest().setFilterFids(ids)):\n    print(f.id(), f[\"code\"])\n",[18,1027,1028,1039,1043,1058,1068,1079,1083,1096,1110,1120,1124,1148,1159],{"__ignoreMap":239},[243,1029,1030,1032,1034,1036],{"class":107,"line":245},[243,1031,249],{"class":248},[243,1033,253],{"class":252},[243,1035,256],{"class":248},[243,1037,1038],{"class":252}," QgsFeatureRequest\n",[243,1040,1041],{"class":107,"line":262},[243,1042,266],{"emptyLinePlaceholder":265},[243,1044,1045,1048,1050,1053,1056],{"class":107,"line":269},[243,1046,1047],{"class":252},"one ",[243,1049,275],{"class":248},[243,1051,1052],{"class":252}," districts.getFeature(",[243,1054,1055],{"class":288},"17",[243,1057,319],{"class":252},[243,1059,1060,1062,1065],{"class":107,"line":294},[243,1061,842],{"class":248},[243,1063,1064],{"class":248}," not",[243,1066,1067],{"class":252}," one.isValid():\n",[243,1069,1070,1072,1074,1077],{"class":107,"line":305},[243,1071,403],{"class":288},[243,1073,388],{"class":252},[243,1075,1076],{"class":281},"\"no feature with id 17\"",[243,1078,319],{"class":252},[243,1080,1081],{"class":107,"line":322},[243,1082,266],{"emptyLinePlaceholder":265},[243,1084,1085,1088,1090,1093],{"class":107,"line":327},[243,1086,1087],{"class":252},"selected ",[243,1089,275],{"class":248},[243,1091,1092],{"class":252}," districts.selectedFeatures()          ",[243,1094,1095],{"class":359},"# list, all in memory\n",[243,1097,1098,1100,1102,1104,1107],{"class":107,"line":342},[243,1099,330],{"class":248},[243,1101,333],{"class":252},[243,1103,336],{"class":248},[243,1105,1106],{"class":252}," districts.getSelectedFeatures():        ",[243,1108,1109],{"class":359},"# iterator, lazy\n",[243,1111,1112,1114,1116,1118],{"class":107,"line":363},[243,1113,403],{"class":288},[243,1115,538],{"class":252},[243,1117,409],{"class":281},[243,1119,412],{"class":252},[243,1121,1122],{"class":107,"line":377},[243,1123,266],{"emptyLinePlaceholder":265},[243,1125,1126,1129,1131,1134,1137,1139,1141,1143,1146],{"class":107,"line":400},[243,1127,1128],{"class":252},"ids ",[243,1130,275],{"class":248},[243,1132,1133],{"class":252}," [",[243,1135,1136],{"class":288},"3",[243,1138,207],{"class":252},[243,1140,1055],{"class":288},[243,1142,207],{"class":252},[243,1144,1145],{"class":288},"42",[243,1147,291],{"class":252},[243,1149,1150,1152,1154,1156],{"class":107,"line":603},[243,1151,330],{"class":248},[243,1153,333],{"class":252},[243,1155,336],{"class":248},[243,1157,1158],{"class":252}," districts.getFeatures(QgsFeatureRequest().setFilterFids(ids)):\n",[243,1160,1161,1163,1166,1168],{"class":107,"line":914},[243,1162,403],{"class":288},[243,1164,1165],{"class":252},"(f.id(), f[",[243,1167,353],{"class":281},[243,1169,412],{"class":252},[14,1171,1172,419,1174,1177,1178,1181,1182,1185,1186,1189,1190,1193,1194,1197],{},[416,1173,418],{},[18,1175,1176],{},"getFeature(fid)"," returns an invalid feature rather than raising when the id does not exist — check ",[18,1179,1180],{},"isValid()",". ",[18,1183,1184],{},"selectedFeatures()"," builds a list immediately, which is convenient for a handful and wasteful for a selection of a hundred thousand; ",[18,1187,1188],{},"getSelectedFeatures()"," iterates lazily. ",[18,1191,1192],{},"setFilterFids"," turns a list of ids into a single provider request, far faster than calling ",[18,1195,1196],{},"getFeature"," in a loop against a database.",[187,1199,1201],{"id":1200},"read-only-what-you-need","Read only what you need",[14,1203,1204],{},"The provider reads every attribute and the geometry for every feature unless the request says otherwise. On wide tables and remote sources, restricting the request is the cheapest optimisation available.",[14,1206,1207],{},[34,1208,1211,1214,1217,1219,1226,1229,1235,1240,1244,1248,1252,1256,1259,1263,1266,1269,1272,1275],{"viewBox":1209,"role":37,"ariaLabel":1210,"xmlns":39},"0 0 760 222","A default request reads every column and geometry, while a trimmed request limits columns, skips geometry and filters rows",[41,1212,1213],{},"A request that asks for less",[45,1215,1216],{},"Default request: all forty columns and the full geometry are read for every feature. Trimmed request: setSubsetOfAttributes limits columns to code and population, the NoGeometry flag skips geometry decoding, and setFilterExpression or setFilterRect limits rows. The trimmed request reads a small fraction of the data and runs proportionally faster on databases and web services.",[49,1218],{"x":51,"y":51,"width":52,"height":714,"fill":54},[56,1220,1221],{},[59,1222,1224],{"id":1223,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"readTrimArrow",[68,1225],{"d":70,"fill":71},[73,1227,1228],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Columns × geometry × rows",[49,1230],{"x":83,"y":1231,"width":1232,"height":1233,"rx":63,"fill":756,"stroke":1234,"style":89},"56","330","150","#b91c1c",[73,1236,1239],{"x":1237,"y":1238,"style":94,"fill":1234,"textAnchor":79},"189","94.88","default request",[73,1241,1243],{"x":1237,"y":1242,"style":99,"fill":71,"textAnchor":79},"114.83","all 40 columns",[73,1245,1247],{"x":1237,"y":1246,"style":99,"fill":71,"textAnchor":79},"134.78","full geometry decoded",[73,1249,1251],{"x":1237,"y":1250,"style":99,"fill":71,"textAnchor":79},"154.73","every row",[73,1253,1255],{"x":1237,"y":1254,"style":99,"fill":100,"textAnchor":79},"174.68","slow on wide or remote data",[49,1257],{"x":1258,"y":1231,"width":1232,"height":1233,"rx":63,"fill":163,"stroke":164,"style":89},"406",[73,1260,1262],{"x":1261,"y":1238,"style":94,"fill":169,"textAnchor":79},"571","trimmed request",[73,1264,1265],{"x":1261,"y":1242,"style":143,"fill":71,"textAnchor":79},"setSubsetOfAttributes(…)",[73,1267,1268],{"x":1261,"y":1246,"style":143,"fill":71,"textAnchor":79},"setFlags(NoGeometry)",[73,1270,1271],{"x":1261,"y":1250,"style":143,"fill":71,"textAnchor":79},"setFilterExpression(…)",[73,1273,1274],{"x":1261,"y":1254,"style":99,"fill":100,"textAnchor":79},"reads a fraction",[107,1276],{"x1":1277,"y1":1278,"x2":1279,"y2":1278,"stroke":71,"style":1280},"354","131","402","stroke-width:1.8;marker-end:url(#readTrimArrow)",[234,1282,1284],{"className":236,"code":1283,"language":238,"meta":239,"style":239},"request = (QgsFeatureRequest()\n           .setSubsetOfAttributes([\"code\", \"population\"], districts.fields())\n           .setFlags(QgsFeatureRequest.NoGeometry)\n           .setFilterExpression('\"population\" > 50000'))\n\ntotal = sum(f[\"population\"] for f in districts.getFeatures(request))\nprint(\"population in large districts:\", total)\n",[18,1285,1286,1296,1310,1315,1326,1330,1356],{"__ignoreMap":239},[243,1287,1288,1291,1293],{"class":107,"line":245},[243,1289,1290],{"class":252},"request ",[243,1292,275],{"class":248},[243,1294,1295],{"class":252}," (QgsFeatureRequest()\n",[243,1297,1298,1301,1303,1305,1307],{"class":107,"line":262},[243,1299,1300],{"class":252},"           .setSubsetOfAttributes([",[243,1302,353],{"class":281},[243,1304,207],{"class":252},[243,1306,316],{"class":281},[243,1308,1309],{"class":252},"], districts.fields())\n",[243,1311,1312],{"class":107,"line":269},[243,1313,1314],{"class":252},"           .setFlags(QgsFeatureRequest.NoGeometry)\n",[243,1316,1317,1320,1323],{"class":107,"line":294},[243,1318,1319],{"class":252},"           .setFilterExpression(",[243,1321,1322],{"class":281},"'\"population\" > 50000'",[243,1324,1325],{"class":252},"))\n",[243,1327,1328],{"class":107,"line":305},[243,1329,266],{"emptyLinePlaceholder":265},[243,1331,1332,1335,1337,1340,1342,1344,1347,1349,1351,1353],{"class":107,"line":322},[243,1333,1334],{"class":252},"total ",[243,1336,275],{"class":248},[243,1338,1339],{"class":288}," sum",[243,1341,538],{"class":252},[243,1343,316],{"class":281},[243,1345,1346],{"class":252},"] ",[243,1348,330],{"class":248},[243,1350,333],{"class":252},[243,1352,336],{"class":248},[243,1354,1355],{"class":252}," districts.getFeatures(request))\n",[243,1357,1358,1360,1362,1365],{"class":107,"line":327},[243,1359,830],{"class":288},[243,1361,388],{"class":252},[243,1363,1364],{"class":281},"\"population in large districts:\"",[243,1366,1367],{"class":252},", total)\n",[14,1369,1370,419,1372,1375,1376,1379,1380,1383,1384,1388],{},[416,1371,418],{},[18,1373,1374],{},"setSubsetOfAttributes"," takes names plus the fields object, and the provider fetches only those columns — on PostGIS this changes the ",[18,1377,1378],{},"SELECT"," list. Attributes outside the subset come back as NULL, so do not read them. ",[18,1381,1382],{},"NoGeometry"," skips decoding shapes entirely, often the most expensive part. The expression filter is translated into SQL by database providers where possible. The ",[26,1385,1387],{"href":1386},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fspeed-up-feature-iteration-with-qgsfeaturerequest-pyqgis\u002F","feature request performance guide"," measures each of these against real layers.",[187,1390,1392],{"id":1391},"avoid-the-reused-feature-and-live-edit-pitfalls","Avoid the reused-feature and live-edit pitfalls",[14,1394,1395,1396,1398,1399,1402],{},"Two iteration patterns corrupt results without raising an error. The first comes from older examples that reuse one ",[18,1397,231],{}," object with ",[18,1400,1401],{},"nextFeature","; the second is changing a layer while a loop is still reading it.",[234,1404,1406],{"className":236,"code":1405,"language":238,"meta":239,"style":239},"from qgis.core import QgsFeature, QgsGeometry\n\n# pitfall 1: one feature object, refilled on every step\nit = districts.getFeatures()\nf = QgsFeature()\nkept = []\nwhile it.nextFeature(f):\n    kept.append(f)                  # the same object appended N times\nprint(len({id(x) for x in kept}))   # 1 — every entry is the last feature\n\nkept = [QgsFeature(x) for x in districts.getFeatures()]   # explicit copies: safe\ngeoms = [QgsGeometry(x.geometry()) for x in districts.getFeatures()]\n\n# pitfall 2: deleting inside the loop that reads the layer\nto_delete = [x.id() for x in districts.getFeatures('\"population\" = 0')]\ndistricts.dataProvider().deleteFeatures(to_delete)\n",[18,1407,1408,1419,1423,1428,1438,1448,1458,1466,1474,1504,1508,1529,1548,1552,1557,1582],{"__ignoreMap":239},[243,1409,1410,1412,1414,1416],{"class":107,"line":245},[243,1411,249],{"class":248},[243,1413,253],{"class":252},[243,1415,256],{"class":248},[243,1417,1418],{"class":252}," QgsFeature, QgsGeometry\n",[243,1420,1421],{"class":107,"line":262},[243,1422,266],{"emptyLinePlaceholder":265},[243,1424,1425],{"class":107,"line":269},[243,1426,1427],{"class":359},"# pitfall 1: one feature object, refilled on every step\n",[243,1429,1430,1433,1435],{"class":107,"line":294},[243,1431,1432],{"class":252},"it ",[243,1434,275],{"class":248},[243,1436,1437],{"class":252}," districts.getFeatures()\n",[243,1439,1440,1443,1445],{"class":107,"line":305},[243,1441,1442],{"class":252},"f ",[243,1444,275],{"class":248},[243,1446,1447],{"class":252}," QgsFeature()\n",[243,1449,1450,1453,1455],{"class":107,"line":322},[243,1451,1452],{"class":252},"kept ",[243,1454,275],{"class":248},[243,1456,1457],{"class":252}," []\n",[243,1459,1460,1463],{"class":107,"line":327},[243,1461,1462],{"class":248},"while",[243,1464,1465],{"class":252}," it.nextFeature(f):\n",[243,1467,1468,1471],{"class":107,"line":342},[243,1469,1470],{"class":252},"    kept.append(f)                  ",[243,1472,1473],{"class":359},"# the same object appended N times\n",[243,1475,1476,1478,1480,1482,1485,1488,1491,1493,1496,1498,1501],{"class":107,"line":363},[243,1477,830],{"class":288},[243,1479,388],{"class":252},[243,1481,903],{"class":288},[243,1483,1484],{"class":252},"({",[243,1486,1487],{"class":288},"id",[243,1489,1490],{"class":252},"(x) ",[243,1492,330],{"class":248},[243,1494,1495],{"class":252}," x ",[243,1497,336],{"class":248},[243,1499,1500],{"class":252}," kept}))   ",[243,1502,1503],{"class":359},"# 1 — every entry is the last feature\n",[243,1505,1506],{"class":107,"line":377},[243,1507,266],{"emptyLinePlaceholder":265},[243,1509,1510,1512,1514,1517,1519,1521,1523,1526],{"class":107,"line":400},[243,1511,1452],{"class":252},[243,1513,275],{"class":248},[243,1515,1516],{"class":252}," [QgsFeature(x) ",[243,1518,330],{"class":248},[243,1520,1495],{"class":252},[243,1522,336],{"class":248},[243,1524,1525],{"class":252}," districts.getFeatures()]   ",[243,1527,1528],{"class":359},"# explicit copies: safe\n",[243,1530,1531,1534,1536,1539,1541,1543,1545],{"class":107,"line":603},[243,1532,1533],{"class":252},"geoms ",[243,1535,275],{"class":248},[243,1537,1538],{"class":252}," [QgsGeometry(x.geometry()) ",[243,1540,330],{"class":248},[243,1542,1495],{"class":252},[243,1544,336],{"class":248},[243,1546,1547],{"class":252}," districts.getFeatures()]\n",[243,1549,1550],{"class":107,"line":914},[243,1551,266],{"emptyLinePlaceholder":265},[243,1553,1554],{"class":107,"line":919},[243,1555,1556],{"class":359},"# pitfall 2: deleting inside the loop that reads the layer\n",[243,1558,1559,1562,1564,1567,1569,1571,1573,1576,1579],{"class":107,"line":938},[243,1560,1561],{"class":252},"to_delete ",[243,1563,275],{"class":248},[243,1565,1566],{"class":252}," [x.id() ",[243,1568,330],{"class":248},[243,1570,1495],{"class":252},[243,1572,336],{"class":248},[243,1574,1575],{"class":252}," districts.getFeatures(",[243,1577,1578],{"class":281},"'\"population\" = 0'",[243,1580,1581],{"class":252},")]\n",[243,1583,1584],{"class":107,"line":951},[243,1585,1586],{"class":252},"districts.dataProvider().deleteFeatures(to_delete)\n",[14,1588,1589,419,1591,1594,1595,1598,1599,1602,1603,1606,1607,1610,1611,1614,1615,218],{},[416,1590,418],{},[18,1592,1593],{},"nextFeature(f)"," fills the object you pass in, so appending ",[18,1596,1597],{},"f"," stores many references to one object whose contents end up as the last feature read. The plain ",[18,1600,1601],{},"for x in layer.getFeatures()"," form yields a new object on every step and has no such problem; copying with ",[18,1604,1605],{},"QgsFeature(x)"," or ",[18,1608,1609],{},"QgsGeometry(...)"," is cheap insurance when the objects will be kept and modified later. Collecting ids first and changing the layer afterwards is the general pattern for edits that depend on a scan: the iterator never sees a layer changing underneath it. Passing an expression string straight to ",[18,1612,1613],{},"getFeatures"," is shorthand for a request with ",[18,1616,1617],{},"setFilterExpression",[187,1619,1621],{"id":1620},"order-limit-and-page-through-results","Order, limit and page through results",[14,1623,1624],{},"Some reads need a defined order — the ten most populous districts, features in the order a report lists them, or a stable sequence for paging through a remote layer. Iteration order is otherwise whatever the provider returns, which for a database is not guaranteed to be the same twice.",[234,1626,1628],{"className":236,"code":1627,"language":238,"meta":239,"style":239},"from qgis.core import QgsFeatureRequest\n\ntop = (QgsFeatureRequest()\n       .addOrderBy('\"population\"', ascending=False)\n       .setLimit(10)\n       .setFlags(QgsFeatureRequest.NoGeometry))\nfor rank, f in enumerate(districts.getFeatures(top), start=1):\n    print(rank, f[\"name\"], f[\"population\"])\n\n# deterministic paging by id for a large remote layer\nlast_id, page_size = -1, 2000\nwhile True:\n    page = QgsFeatureRequest().setFilterExpression(f\"$id > {last_id}\") \\\n                              .addOrderBy(\"$id\").setLimit(page_size)\n    rows = list(districts.getFeatures(page))\n    if not rows:\n        break\n    process(rows)                       # your per-page work\n    last_id = rows[-1].id()\n",[18,1629,1630,1640,1644,1653,1674,1684,1689,1715,1731,1735,1740,1757,1766,1796,1807,1820,1829,1834,1843],{"__ignoreMap":239},[243,1631,1632,1634,1636,1638],{"class":107,"line":245},[243,1633,249],{"class":248},[243,1635,253],{"class":252},[243,1637,256],{"class":248},[243,1639,1038],{"class":252},[243,1641,1642],{"class":107,"line":262},[243,1643,266],{"emptyLinePlaceholder":265},[243,1645,1646,1649,1651],{"class":107,"line":269},[243,1647,1648],{"class":252},"top ",[243,1650,275],{"class":248},[243,1652,1295],{"class":252},[243,1654,1655,1658,1661,1663,1667,1669,1672],{"class":107,"line":294},[243,1656,1657],{"class":252},"       .addOrderBy(",[243,1659,1660],{"class":281},"'\"population\"'",[243,1662,207],{"class":252},[243,1664,1666],{"class":1665},"s9osk","ascending",[243,1668,275],{"class":248},[243,1670,1671],{"class":288},"False",[243,1673,319],{"class":252},[243,1675,1676,1679,1682],{"class":107,"line":305},[243,1677,1678],{"class":252},"       .setLimit(",[243,1680,1681],{"class":288},"10",[243,1683,319],{"class":252},[243,1685,1686],{"class":107,"line":322},[243,1687,1688],{"class":252},"       .setFlags(QgsFeatureRequest.NoGeometry))\n",[243,1690,1691,1693,1696,1698,1701,1704,1707,1709,1712],{"class":107,"line":327},[243,1692,330],{"class":248},[243,1694,1695],{"class":252}," rank, f ",[243,1697,336],{"class":248},[243,1699,1700],{"class":288}," enumerate",[243,1702,1703],{"class":252},"(districts.getFeatures(top), ",[243,1705,1706],{"class":1665},"start",[243,1708,275],{"class":248},[243,1710,1711],{"class":288},"1",[243,1713,1714],{"class":252},"):\n",[243,1716,1717,1719,1722,1724,1727,1729],{"class":107,"line":342},[243,1718,403],{"class":288},[243,1720,1721],{"class":252},"(rank, f[",[243,1723,409],{"class":281},[243,1725,1726],{"class":252},"], f[",[243,1728,316],{"class":281},[243,1730,412],{"class":252},[243,1732,1733],{"class":107,"line":363},[243,1734,266],{"emptyLinePlaceholder":265},[243,1736,1737],{"class":107,"line":377},[243,1738,1739],{"class":359},"# deterministic paging by id for a large remote layer\n",[243,1741,1742,1745,1747,1750,1752,1754],{"class":107,"line":400},[243,1743,1744],{"class":252},"last_id, page_size ",[243,1746,275],{"class":248},[243,1748,1749],{"class":248}," -",[243,1751,1711],{"class":288},[243,1753,207],{"class":252},[243,1755,1756],{"class":288},"2000\n",[243,1758,1759,1761,1764],{"class":107,"line":603},[243,1760,1462],{"class":248},[243,1762,1763],{"class":288}," True",[243,1765,564],{"class":252},[243,1767,1768,1771,1773,1776,1778,1781,1784,1787,1790,1793],{"class":107,"line":914},[243,1769,1770],{"class":252},"    page ",[243,1772,275],{"class":248},[243,1774,1775],{"class":252}," QgsFeatureRequest().setFilterExpression(",[243,1777,1597],{"class":248},[243,1779,1780],{"class":281},"\"$id > ",[243,1782,1783],{"class":288},"{",[243,1785,1786],{"class":252},"last_id",[243,1788,1789],{"class":288},"}",[243,1791,1792],{"class":281},"\"",[243,1794,1795],{"class":252},") \\\n",[243,1797,1798,1801,1804],{"class":107,"line":919},[243,1799,1800],{"class":252},"                              .addOrderBy(",[243,1802,1803],{"class":281},"\"$id\"",[243,1805,1806],{"class":252},").setLimit(page_size)\n",[243,1808,1809,1812,1814,1817],{"class":107,"line":938},[243,1810,1811],{"class":252},"    rows ",[243,1813,275],{"class":248},[243,1815,1816],{"class":288}," list",[243,1818,1819],{"class":252},"(districts.getFeatures(page))\n",[243,1821,1822,1824,1826],{"class":107,"line":951},[243,1823,515],{"class":248},[243,1825,1064],{"class":248},[243,1827,1828],{"class":252}," rows:\n",[243,1830,1831],{"class":107,"line":971},[243,1832,1833],{"class":248},"        break\n",[243,1835,1837,1840],{"class":107,"line":1836},18,[243,1838,1839],{"class":252},"    process(rows)                       ",[243,1841,1842],{"class":359},"# your per-page work\n",[243,1844,1846,1849,1851,1854,1857,1859],{"class":107,"line":1845},19,[243,1847,1848],{"class":252},"    last_id ",[243,1850,275],{"class":248},[243,1852,1853],{"class":252}," rows[",[243,1855,1856],{"class":248},"-",[243,1858,1711],{"class":288},[243,1860,1861],{"class":252},"].id()\n",[14,1863,1864,419,1866,1869,1870,1873,1874,1877,1878,1881,1882,1885],{},[416,1865,418],{},[18,1867,1868],{},"addOrderBy"," takes an expression, so ordering by a computed value such as ",[18,1871,1872],{},"\"population\" \u002F \"area_km2\""," works as well as ordering by a column; database providers translate simple cases into ",[18,1875,1876],{},"ORDER BY"," and QGIS sorts locally otherwise. ",[18,1879,1880],{},"setLimit"," stops the iterator after the given number of features and, on databases, becomes a ",[18,1883,1884],{},"LIMIT"," clause. Keyset paging — \"everything after the last id I saw\" — is more reliable than offset paging on a layer that may change between pages, and it keeps each request small, which matters for web services with response size limits.",[187,1887,1889],{"id":1888},"qgis-version-compatibility","QGIS version compatibility",[14,1891,1892,1893,214,1895,1897,1898,1900,1901,1904,1905,1908],{},"Iteration, ",[18,1894,1196],{},[18,1896,95],{}," are unchanged between QGIS 3 and QGIS 4, with two exceptions: NULL attributes are ",[18,1899,20],{}," on QGIS 4, and request flags are written ",[18,1902,1903],{},"Qgis.FeatureRequestFlag.NoGeometry",". The short form ",[18,1906,1907],{},"QgsFeatureRequest.NoGeometry"," still works on 3.40 LTR.",[187,1910,1912],{"id":1911},"troubleshooting","Troubleshooting",[192,1914,1915,1930,1945,1951],{},[195,1916,1917,1920,1921,1923,1924,1926,1927,218],{},[416,1918,1919],{},"Every feature in my list is the same."," The loop reused one object with ",[18,1922,1401],{},"; iterate with ",[18,1925,330],{}," or copy with ",[18,1928,1929],{},"QgsFeature(f)",[195,1931,1932,1937,1938,1941,1942,1944],{},[416,1933,1934,218],{},[18,1935,1936],{},"TypeError: '>' not supported between QVariant and int"," The value is NULL; test with ",[18,1939,1940],{},"== NULL"," or the ",[18,1943,668],{}," helper first.",[195,1946,1947,1950],{},[416,1948,1949],{},"An attribute is always NULL."," It is outside the requested attribute subset.",[195,1952,1953,1958,1959,1606,1961,218],{},[416,1954,1955,1957],{},[18,1956,764],{}," returns an empty list."," The geometry is multipart; use ",[18,1960,770],{},[18,1962,1000],{},[187,1964,1966],{"id":1965},"conclusion","Conclusion",[14,1968,1969,1970,1972,1973,1975],{},"Iterate with ",[18,1971,1613],{},", read attributes by name for clarity and by index for speed, test NULL explicitly, choose the geometry accessor by type or use ",[18,1974,1000],{},", fetch ids and selections without full scans, trim requests to the columns and rows you need, and never modify a layer while iterating it.",[187,1977,1979],{"id":1978},"frequently-asked-questions","Frequently Asked Questions",[14,1981,1982,1985,1988,1989,1992],{},[416,1983,1984],{},"How do I get a feature's attributes as a dict directly?",[18,1986,1987],{},"dict(zip(layer.fields().names(), f.attributes()))","; on recent releases ",[18,1990,1991],{},"f.attributeMap()"," returns the same.",[14,1994,1995,2002,2003,2006],{},[416,1996,1997,1998,2001],{},"Is ",[18,1999,2000],{},"getFeatures()"," thread-safe?","\nNot on the same layer from several threads. In a background task, iterate a ",[18,2004,2005],{},"QgsVectorLayerFeatureSource"," created on the main thread.",[14,2008,2009,2012,2013,2015,2016,2019],{},[416,2010,2011],{},"How do I count features matching an expression without reading them?","\nIterate a request with ",[18,2014,1382],{}," and an empty attribute subset; or use ",[18,2017,2018],{},"layer.aggregate"," for sums and counts.",[14,2021,2022,2025],{},[416,2023,2024],{},"Why are the ids not consecutive?","\nDeleted features leave gaps, and some providers never use zero.",[187,2027,2029],{"id":2028},"related","Related",[192,2031,2032,2037,2042,2048,2054],{},[195,2033,2034,2036],{},[26,2035,29],{"href":28}," — the guide this recipe belongs to",[195,2038,2039],{},[26,2040,2041],{"href":672},"Handle NULL Values and QVariant in PyQGIS",[195,2043,2044],{},[26,2045,2047],{"href":2046},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fadd-features-to-vector-layer-pyqgis\u002F","Add Features to a Vector Layer in PyQGIS",[195,2049,2050],{},[26,2051,2053],{"href":2052},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Faccess-active-layer-and-selection-pyqgis\u002F","Access the Active Layer and Selection in PyQGIS",[195,2055,2056],{},[26,2057,2058],{"href":1386},"Speed Up Feature Iteration with QgsFeatureRequest",[2060,2061,2062],"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 pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}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}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":239,"searchDepth":262,"depth":262,"links":2064},[2065,2066,2067,2068,2069,2070,2071,2072,2073,2074,2075,2076,2077],{"id":189,"depth":262,"text":190},{"id":221,"depth":262,"text":222},{"id":450,"depth":262,"text":451},{"id":677,"depth":262,"text":678},{"id":1018,"depth":262,"text":1019},{"id":1200,"depth":262,"text":1201},{"id":1391,"depth":262,"text":1392},{"id":1620,"depth":262,"text":1621},{"id":1888,"depth":262,"text":1889},{"id":1911,"depth":262,"text":1912},{"id":1965,"depth":262,"text":1966},{"id":1978,"depth":262,"text":1979},{"id":2028,"depth":262,"text":2029},"Iterate a vector layer's features and read their attributes and geometry correctly — by field name, by index, as a dictionary, with only the columns you need, from selections and single feature ids, and without the iterator pitfalls that corrupt results.","md",{"slug":2081,"type":2082,"breadcrumb":2083,"datePublished":2084,"dateModified":2084},"read-feature-attributes-and-geometry-pyqgis","article","Read Feature Attributes and Geometry","2026-10-02","\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fread-feature-attributes-and-geometry-pyqgis",{"title":5,"description":2078},"pyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fread-feature-attributes-and-geometry-pyqgis\u002Findex","tMmGFghxPGKB_QH2MexMZ5bHBesAy9T6ywXf3cx1EmE",1790966252949]