[{"data":1,"prerenderedAt":1575},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fevaluate-qgis-expression-in-pyqgis":3},{"id":4,"title":5,"body":6,"description":1564,"extension":1565,"meta":1566,"navigation":286,"path":1571,"seo":1572,"stem":1573,"__hash__":1574},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fevaluate-qgis-expression-in-pyqgis\u002Findex.md","Evaluate a QGIS Expression in PyQGIS",{"type":7,"value":8,"toc":1551},"minimark",[9,13,26,35,191,196,217,221,224,390,436,450,454,457,591,609,613,616,731,744,748,755,854,991,1016,1020,1023,1094,1122,1125,1200,1216,1319,1323,1329,1388,1395,1399,1462,1466,1472,1476,1488,1502,1508,1518,1522,1547],[10,11,5],"h1",{"id":12},"evaluate-a-qgis-expression-in-pyqgis",[14,15,16,17,21,22,25],"p",{},"Running a QGIS expression from Python is a three-line job once you know the shape, and a source of quiet wrong answers until you do. The two objects involved — ",[18,19,20],"code",{},"QgsExpression",", which holds the parsed expression, and ",[18,23,24],{},"QgsExpressionContext",", which supplies everything the expression refers to — have to be created in the right order and checked at two separate points, because a syntax error and a per-feature evaluation failure are different conditions reported through different methods.",[14,27,28,29,34],{},"This page is a focused recipe within ",[30,31,33],"a",{"href":32},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002F","Working with QGIS Expressions in PyQGIS",". It covers evaluating an expression against a single feature, evaluating without any feature at all, checking both error channels, and preparing the expression so a loop over a large layer stays fast.",[14,36,37],{},[38,39,44,48,52,59,76,85,95,102,107,113,118,123,129,134,141,145,147,151,156,160,163,166,169,172,178,183,187],"svg",{"viewBox":40,"role":41,"ariaLabel":42,"xmlns":43},"0 0 760 244","img","The two checkpoints when evaluating an expression: a parser check after construction and an evaluation check after each feature, each with its own error method","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[45,46,47],"title",{},"Two error checks, at two different moments",[49,50,51],"desc",{},"An expression string is constructed into a QgsExpression, where hasParserError is checked once. It is then evaluated once per feature inside a loop, where hasEvalError must be checked every time. Parser errors are syntax problems caught before the loop; evaluation errors are data problems that can affect one feature and not the next.",[53,54],"rect",{"x":55,"y":55,"width":56,"height":57,"fill":58},"0","760","244","#f6f3ea",[60,61,62],"defs",{},[63,64,71],"marker",{"id":65,"viewBox":66,"refX":67,"refY":68,"markerWidth":69,"markerHeight":69,"orient":70},"evalFlowArrow","0 0 10 10","8","5","7","auto-start-reverse",[72,73],"path",{"d":74,"fill":75},"M0 0 L10 5 L0 10 z","#2f3b35",[77,78,84],"text",{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Check twice: once for syntax, once per feature for data",[53,86],{"x":87,"y":88,"width":89,"height":90,"rx":91,"fill":92,"stroke":93,"style":94},"16","52","180","72","10","#26322d","#0f766e","stroke-width:2.5",[77,96,101],{"x":97,"y":98,"style":99,"fill":100,"textAnchor":83},"106","80","text-anchor:middle;font-size:12.5px;font-weight:bold;font-family:sans-serif","#d9f99d","QgsExpression(text)",[77,103,106],{"x":97,"y":104,"style":105,"fill":100,"textAnchor":83},"102","text-anchor:middle;font-size:11.5px;font-family:sans-serif","parsed once",[53,108],{"x":87,"y":109,"width":89,"height":110,"rx":91,"fill":111,"stroke":112,"style":94},"152","66","#fffdf7","#b91c1c",[77,114,117],{"x":97,"y":115,"style":116,"fill":112,"textAnchor":83},"178","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","hasParserError()",[77,119,122],{"x":97,"y":120,"style":121,"fill":75,"textAnchor":83},"199","text-anchor:middle;font-size:11px;font-family:sans-serif","unbalanced quote, typo'd fn",[53,124],{"x":125,"y":88,"width":126,"height":127,"rx":91,"fill":111,"stroke":128,"style":94},"248","228","166","#2563eb",[77,130,133],{"x":131,"y":132,"style":99,"fill":128,"textAnchor":83},"362","78","for feature in layer:",[53,135],{"x":136,"y":137,"width":89,"height":138,"rx":67,"fill":111,"stroke":139,"style":140},"272","94","44","#59645f","stroke-width:2",[77,142,144],{"x":131,"y":143,"style":105,"fill":75,"textAnchor":83},"121","context.setFeature(f)",[53,146],{"x":136,"y":109,"width":89,"height":138,"rx":67,"fill":111,"stroke":93,"style":140},[77,148,150],{"x":131,"y":149,"style":105,"fill":93,"textAnchor":83},"179","expression.evaluate(ctx)",[53,152],{"x":153,"y":88,"width":154,"height":90,"rx":91,"fill":111,"stroke":155,"style":94},"528","216","#15803d",[77,157,159],{"x":158,"y":98,"style":99,"fill":155,"textAnchor":83},"636","a value per feature",[77,161,162],{"x":158,"y":104,"style":105,"fill":75,"textAnchor":83},"int · float · str · None",[53,164],{"x":153,"y":109,"width":154,"height":110,"rx":91,"fill":111,"stroke":165,"style":94},"#b45309",[77,167,168],{"x":158,"y":115,"style":116,"fill":165,"textAnchor":83},"hasEvalError()",[77,170,171],{"x":158,"y":120,"style":121,"fill":75,"textAnchor":83},"divide by zero, no geometry",[173,174],"line",{"x1":97,"y1":175,"x2":97,"y2":176,"stroke":112,"style":177},"124","148","stroke-width:2.5;marker-end:url(#evalFlowArrow)",[173,179],{"x1":180,"y1":181,"x2":57,"y2":182,"stroke":75,"style":177},"196","88","110",[173,184],{"x1":185,"y1":182,"x2":186,"y2":181,"stroke":155,"style":177},"476","524",[173,188],{"x1":185,"y1":189,"x2":186,"y2":89,"stroke":165,"style":190},"174","stroke-width:2.5;stroke-dasharray:5 4;marker-end:url(#evalFlowArrow)",[192,193,195],"h2",{"id":194},"prerequisites","Prerequisites",[197,198,199,207,210],"ul",{},[200,201,202,206],"li",{},[203,204,205],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer, with the Python Console available.",[200,208,209],{},"A loaded vector layer with at least one numeric field.",[200,211,212,213,216],{},"A basic grasp of how the expression engine resolves names — the ",[30,214,215],{"href":32},"scope stack"," is the one concept worth reading first.",[192,218,220],{"id":219},"evaluate-against-a-single-feature","Evaluate against a single feature",[14,222,223],{},"The minimal working version builds a context, pushes the standard scopes, sets a feature, and evaluates.",[225,226,231],"pre",{"className":227,"code":228,"language":229,"meta":230,"style":230},"language-python shiki shiki-themes github-dark","from qgis.core import (\n    QgsExpression,\n    QgsExpressionContext,\n    QgsExpressionContextUtils,\n    QgsProject,\n)\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\nfeature = next(layer.getFeatures())\n\nexpression = QgsExpression('\"assessed_value\" * 1.05')\n\ncontext = QgsExpressionContext()\ncontext.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\ncontext.setFeature(feature)\n\nprint(expression.evaluate(context))\n","python","",[18,232,233,251,257,263,269,275,281,288,313,327,332,348,353,364,370,376,381],{"__ignoreMap":230},[234,235,237,241,245,248],"span",{"class":173,"line":236},1,[234,238,240],{"class":239},"snl16","from",[234,242,244],{"class":243},"s95oV"," qgis.core ",[234,246,247],{"class":239},"import",[234,249,250],{"class":243}," (\n",[234,252,254],{"class":173,"line":253},2,[234,255,256],{"class":243},"    QgsExpression,\n",[234,258,260],{"class":173,"line":259},3,[234,261,262],{"class":243},"    QgsExpressionContext,\n",[234,264,266],{"class":173,"line":265},4,[234,267,268],{"class":243},"    QgsExpressionContextUtils,\n",[234,270,272],{"class":173,"line":271},5,[234,273,274],{"class":243},"    QgsProject,\n",[234,276,278],{"class":173,"line":277},6,[234,279,280],{"class":243},")\n",[234,282,284],{"class":173,"line":283},7,[234,285,287],{"emptyLinePlaceholder":286},true,"\n",[234,289,291,294,297,300,304,307,310],{"class":173,"line":290},8,[234,292,293],{"class":243},"layer ",[234,295,296],{"class":239},"=",[234,298,299],{"class":243}," QgsProject.instance().mapLayersByName(",[234,301,303],{"class":302},"sU2Wk","\"parcels\"",[234,305,306],{"class":243},")[",[234,308,55],{"class":309},"sDLfK",[234,311,312],{"class":243},"]\n",[234,314,316,319,321,324],{"class":173,"line":315},9,[234,317,318],{"class":243},"feature ",[234,320,296],{"class":239},[234,322,323],{"class":309}," next",[234,325,326],{"class":243},"(layer.getFeatures())\n",[234,328,330],{"class":173,"line":329},10,[234,331,287],{"emptyLinePlaceholder":286},[234,333,335,338,340,343,346],{"class":173,"line":334},11,[234,336,337],{"class":243},"expression ",[234,339,296],{"class":239},[234,341,342],{"class":243}," QgsExpression(",[234,344,345],{"class":302},"'\"assessed_value\" * 1.05'",[234,347,280],{"class":243},[234,349,351],{"class":173,"line":350},12,[234,352,287],{"emptyLinePlaceholder":286},[234,354,356,359,361],{"class":173,"line":355},13,[234,357,358],{"class":243},"context ",[234,360,296],{"class":239},[234,362,363],{"class":243}," QgsExpressionContext()\n",[234,365,367],{"class":173,"line":366},14,[234,368,369],{"class":243},"context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\n",[234,371,373],{"class":173,"line":372},15,[234,374,375],{"class":243},"context.setFeature(feature)\n",[234,377,379],{"class":173,"line":378},16,[234,380,287],{"emptyLinePlaceholder":286},[234,382,384,387],{"class":173,"line":383},17,[234,385,386],{"class":309},"print",[234,388,389],{"class":243},"(expression.evaluate(context))\n",[14,391,392,395,396,399,400,403,404,407,408,411,412,415,416,419,420,423,424,427,428,431,432,435],{},[203,393,394],{},"Breakdown:"," ",[18,397,398],{},"globalProjectLayerScopes()"," returns the global, project and layer scopes already populated and ordered, so you never have to remember which variables belong where. ",[18,401,402],{},"setFeature()"," adds the feature scope on top, which is what makes ",[18,405,406],{},"\"assessed_value\""," resolvable. ",[18,409,410],{},"evaluate()"," returns a Python object converted from the underlying ",[18,413,414],{},"QVariant",": an ",[18,417,418],{},"int"," or ",[18,421,422],{},"float"," for numbers, a ",[18,425,426],{},"str"," for text, and ",[18,429,430],{},"None"," for SQL ",[18,433,434],{},"NULL",".",[14,437,438,439,441,442,445,446,449],{},"Note the quoting. ",[18,440,406],{}," in double quotes is a ",[203,443,444],{},"field reference",". Writing ",[18,447,448],{},"'assessed_value'"," in single quotes would produce the literal fifteen-character string instead — valid syntax, silently wrong result.",[192,451,453],{"id":452},"check-both-error-channels","Check both error channels",[14,455,456],{},"An expression can fail in two unrelated ways, and each has its own predicate. Checking only one is the most common defect in expression code.",[225,458,460],{"className":227,"code":459,"language":229,"meta":230,"style":230},"expression = QgsExpression('\"assessed_value\" \u002F \"lot_area\"')\n\nif expression.hasParserError():\n    raise ValueError(f\"syntax: {expression.parserErrorString()}\")\n\nvalue = expression.evaluate(context)\n\nif expression.hasEvalError():\n    print(f\"feature {feature.id()}: {expression.evalErrorString()}\")\nelse:\n    print(value)\n",[18,461,462,475,479,487,518,522,532,536,543,576,584],{"__ignoreMap":230},[234,463,464,466,468,470,473],{"class":173,"line":236},[234,465,337],{"class":243},[234,467,296],{"class":239},[234,469,342],{"class":243},[234,471,472],{"class":302},"'\"assessed_value\" \u002F \"lot_area\"'",[234,474,280],{"class":243},[234,476,477],{"class":173,"line":253},[234,478,287],{"emptyLinePlaceholder":286},[234,480,481,484],{"class":173,"line":259},[234,482,483],{"class":239},"if",[234,485,486],{"class":243}," expression.hasParserError():\n",[234,488,489,492,495,498,501,504,507,510,513,516],{"class":173,"line":265},[234,490,491],{"class":239},"    raise",[234,493,494],{"class":309}," ValueError",[234,496,497],{"class":243},"(",[234,499,500],{"class":239},"f",[234,502,503],{"class":302},"\"syntax: ",[234,505,506],{"class":309},"{",[234,508,509],{"class":243},"expression.parserErrorString()",[234,511,512],{"class":309},"}",[234,514,515],{"class":302},"\"",[234,517,280],{"class":243},[234,519,520],{"class":173,"line":271},[234,521,287],{"emptyLinePlaceholder":286},[234,523,524,527,529],{"class":173,"line":277},[234,525,526],{"class":243},"value ",[234,528,296],{"class":239},[234,530,531],{"class":243}," expression.evaluate(context)\n",[234,533,534],{"class":173,"line":283},[234,535,287],{"emptyLinePlaceholder":286},[234,537,538,540],{"class":173,"line":290},[234,539,483],{"class":239},[234,541,542],{"class":243}," expression.hasEvalError():\n",[234,544,545,548,550,552,555,557,560,562,565,567,570,572,574],{"class":173,"line":315},[234,546,547],{"class":309},"    print",[234,549,497],{"class":243},[234,551,500],{"class":239},[234,553,554],{"class":302},"\"feature ",[234,556,506],{"class":309},[234,558,559],{"class":243},"feature.id()",[234,561,512],{"class":309},[234,563,564],{"class":302},": ",[234,566,506],{"class":309},[234,568,569],{"class":243},"expression.evalErrorString()",[234,571,512],{"class":309},[234,573,515],{"class":302},[234,575,280],{"class":243},[234,577,578,581],{"class":173,"line":329},[234,579,580],{"class":239},"else",[234,582,583],{"class":243},":\n",[234,585,586,588],{"class":173,"line":334},[234,587,547],{"class":309},[234,589,590],{"class":243},"(value)\n",[14,592,593,395,595,597,598,600,601,603,604,608],{},[203,594,394],{},[18,596,117],{}," is meaningful immediately after construction and never changes — it reports unbalanced quotes, unknown function names, and malformed operators. ",[18,599,168],{}," is reset on every ",[18,602,410],{}," call and reports failures that depend on the data, such as dividing by a zero lot area or calling a geometry function on a feature with no geometry. Because it is per-call, it must be checked ",[605,606,607],"em",{},"inside"," the loop, not once afterwards.",[192,610,612],{"id":611},"evaluate-without-a-feature","Evaluate without a feature",[14,614,615],{},"Expressions that only reference variables, constants or functions need no feature at all. This is how you build dynamic filenames, timestamps and titles that match what the GUI would produce.",[225,617,619],{"className":227,"code":618,"language":229,"meta":230,"style":230},"from qgis.core import (\n    QgsExpression,\n    QgsExpressionContext,\n    QgsExpressionContextUtils,\n    QgsProject,\n)\n\ncontext = QgsExpressionContext()\ncontext.appendScopes([\n    QgsExpressionContextUtils.globalScope(),\n    QgsExpressionContextUtils.projectScope(QgsProject.instance()),\n])\n\nfor text in ('@qgis_version', \"format_date(now(), 'yyyy-MM-dd')\", '@project_title'):\n    print(text, '->', QgsExpression(text).evaluate(context))\n",[18,620,621,631,635,639,643,647,651,655,663,668,673,678,683,687,718],{"__ignoreMap":230},[234,622,623,625,627,629],{"class":173,"line":236},[234,624,240],{"class":239},[234,626,244],{"class":243},[234,628,247],{"class":239},[234,630,250],{"class":243},[234,632,633],{"class":173,"line":253},[234,634,256],{"class":243},[234,636,637],{"class":173,"line":259},[234,638,262],{"class":243},[234,640,641],{"class":173,"line":265},[234,642,268],{"class":243},[234,644,645],{"class":173,"line":271},[234,646,274],{"class":243},[234,648,649],{"class":173,"line":277},[234,650,280],{"class":243},[234,652,653],{"class":173,"line":283},[234,654,287],{"emptyLinePlaceholder":286},[234,656,657,659,661],{"class":173,"line":290},[234,658,358],{"class":243},[234,660,296],{"class":239},[234,662,363],{"class":243},[234,664,665],{"class":173,"line":315},[234,666,667],{"class":243},"context.appendScopes([\n",[234,669,670],{"class":173,"line":329},[234,671,672],{"class":243},"    QgsExpressionContextUtils.globalScope(),\n",[234,674,675],{"class":173,"line":334},[234,676,677],{"class":243},"    QgsExpressionContextUtils.projectScope(QgsProject.instance()),\n",[234,679,680],{"class":173,"line":350},[234,681,682],{"class":243},"])\n",[234,684,685],{"class":173,"line":355},[234,686,287],{"emptyLinePlaceholder":286},[234,688,689,692,695,698,701,704,707,710,712,715],{"class":173,"line":366},[234,690,691],{"class":239},"for",[234,693,694],{"class":243}," text ",[234,696,697],{"class":239},"in",[234,699,700],{"class":243}," (",[234,702,703],{"class":302},"'@qgis_version'",[234,705,706],{"class":243},", ",[234,708,709],{"class":302},"\"format_date(now(), 'yyyy-MM-dd')\"",[234,711,706],{"class":243},[234,713,714],{"class":302},"'@project_title'",[234,716,717],{"class":243},"):\n",[234,719,720,722,725,728],{"class":173,"line":372},[234,721,547],{"class":309},[234,723,724],{"class":243},"(text, ",[234,726,727],{"class":302},"'->'",[234,729,730],{"class":243},", QgsExpression(text).evaluate(context))\n",[14,732,733,735,736,739,740,743],{},[203,734,394],{}," Only the scopes the expression actually needs are pushed; there is no layer or feature involved, so adding them would be wasted work. ",[18,737,738],{},"format_date()"," and ",[18,741,742],{},"now()"," are expression-engine functions rather than Python ones — the point of using them here is that the same string works unchanged in a layout item or a data-defined output path.",[192,745,747],{"id":746},"prepare-before-iterating","Prepare before iterating",[14,749,750,751,754],{},"For a loop over more than a few hundred features, ",[18,752,753],{},"prepare()"," is worth the extra line. It resolves field names to indexes once against the context, so each evaluation skips a name lookup.",[14,756,757],{},[38,758,761,764,767,769,772,778,809,813,829],{"viewBox":759,"role":41,"ariaLabel":760,"xmlns":43},"0 0 760 216","Two loops compared: without prepare, each iteration re-resolves field names; with prepare, name resolution happens once before the loop and each iteration only reads values",[45,762,763],{},"What prepare() moves out of the loop",[49,765,766],{},"The upper bar shows a loop where every iteration performs both a name-resolution step and a value-read step. The lower bar shows the same loop after calling prepare, where a single resolution step happens once before the loop and each iteration performs only the value read, making the total noticeably shorter.",[53,768],{"x":55,"y":55,"width":56,"height":154,"fill":58},[77,770,771],{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"prepare() hoists name resolution out of the loop",[77,773,777],{"x":774,"y":90,"style":775,"fill":82,"textAnchor":776},"150","text-anchor:end;font-size:12px;font-weight:bold;font-family:sans-serif","end","without prepare()",[779,780,781,789,792,795,797,800,803,806],"g",{},[53,782],{"x":783,"y":784,"width":785,"height":80,"rx":786,"fill":165,"fillOpacity":787,"stroke":165,"style":788},"162","56","86","4",0.35,"stroke-width:1.8",[53,790],{"x":125,"y":784,"width":791,"height":80,"rx":786,"fill":93,"fillOpacity":787,"stroke":93,"style":788},"46",[53,793],{"x":794,"y":784,"width":785,"height":80,"rx":786,"fill":165,"fillOpacity":787,"stroke":165,"style":788},"294",[53,796],{"x":79,"y":784,"width":791,"height":80,"rx":786,"fill":93,"fillOpacity":787,"stroke":93,"style":788},[53,798],{"x":799,"y":784,"width":785,"height":80,"rx":786,"fill":165,"fillOpacity":787,"stroke":165,"style":788},"426",[53,801],{"x":802,"y":784,"width":791,"height":80,"rx":786,"fill":93,"fillOpacity":787,"stroke":93,"style":788},"512",[53,804],{"x":805,"y":784,"width":785,"height":80,"rx":786,"fill":165,"fillOpacity":787,"stroke":165,"style":788},"558",[53,807],{"x":808,"y":784,"width":791,"height":80,"rx":786,"fill":93,"fillOpacity":787,"stroke":93,"style":788},"644",[77,810,812],{"x":774,"y":811,"style":775,"fill":82,"textAnchor":776},"132","with prepare()",[779,814,815,819,821,823,826],{},[53,816],{"x":783,"y":817,"width":785,"height":80,"rx":786,"fill":155,"fillOpacity":818,"stroke":155,"style":788},"116",0.4,[53,820],{"x":125,"y":817,"width":791,"height":80,"rx":786,"fill":93,"fillOpacity":787,"stroke":93,"style":788},[53,822],{"x":794,"y":817,"width":791,"height":80,"rx":786,"fill":93,"fillOpacity":787,"stroke":93,"style":788},[53,824],{"x":825,"y":817,"width":791,"height":80,"rx":786,"fill":93,"fillOpacity":787,"stroke":93,"style":788},"340",[53,827],{"x":828,"y":817,"width":791,"height":80,"rx":786,"fill":93,"fillOpacity":787,"stroke":93,"style":788},"386",[779,830,831,835,840,843,847,850],{},[53,832],{"x":783,"y":127,"width":87,"height":833,"fill":165,"fillOpacity":787,"stroke":165,"style":834},"14","stroke-width:1.5",[77,836,839],{"x":837,"y":115,"style":838,"fill":75},"186","font-size:11.5px;font-family:sans-serif","resolve names",[53,841],{"x":842,"y":127,"width":87,"height":833,"fill":155,"fillOpacity":818,"stroke":155,"style":834},"292",[77,844,846],{"x":845,"y":115,"style":838,"fill":75},"316","resolve once",[53,848],{"x":849,"y":127,"width":87,"height":833,"fill":93,"fillOpacity":787,"stroke":93,"style":834},"416",[77,851,853],{"x":852,"y":115,"style":838,"fill":75},"440","read values and evaluate",[225,855,857],{"className":227,"code":856,"language":229,"meta":230,"style":230},"expression = QgsExpression('\"assessed_value\" \u002F nullif(\"lot_area\", 0)')\nif expression.hasParserError():\n    raise ValueError(expression.parserErrorString())\n\ncontext = QgsExpressionContext()\ncontext.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\nexpression.prepare(context)\n\nresults = {}\nfor feature in layer.getFeatures():\n    context.setFeature(feature)\n    value = expression.evaluate(context)\n    if expression.hasEvalError():\n        continue\n    results[feature.id()] = value\n\nprint(len(results), \"feature(s) evaluated cleanly\")\n",[18,858,859,872,878,887,891,899,903,908,912,922,934,939,948,955,960,970,974],{"__ignoreMap":230},[234,860,861,863,865,867,870],{"class":173,"line":236},[234,862,337],{"class":243},[234,864,296],{"class":239},[234,866,342],{"class":243},[234,868,869],{"class":302},"'\"assessed_value\" \u002F nullif(\"lot_area\", 0)'",[234,871,280],{"class":243},[234,873,874,876],{"class":173,"line":253},[234,875,483],{"class":239},[234,877,486],{"class":243},[234,879,880,882,884],{"class":173,"line":259},[234,881,491],{"class":239},[234,883,494],{"class":309},[234,885,886],{"class":243},"(expression.parserErrorString())\n",[234,888,889],{"class":173,"line":265},[234,890,287],{"emptyLinePlaceholder":286},[234,892,893,895,897],{"class":173,"line":271},[234,894,358],{"class":243},[234,896,296],{"class":239},[234,898,363],{"class":243},[234,900,901],{"class":173,"line":277},[234,902,369],{"class":243},[234,904,905],{"class":173,"line":283},[234,906,907],{"class":243},"expression.prepare(context)\n",[234,909,910],{"class":173,"line":290},[234,911,287],{"emptyLinePlaceholder":286},[234,913,914,917,919],{"class":173,"line":315},[234,915,916],{"class":243},"results ",[234,918,296],{"class":239},[234,920,921],{"class":243}," {}\n",[234,923,924,926,929,931],{"class":173,"line":329},[234,925,691],{"class":239},[234,927,928],{"class":243}," feature ",[234,930,697],{"class":239},[234,932,933],{"class":243}," layer.getFeatures():\n",[234,935,936],{"class":173,"line":334},[234,937,938],{"class":243},"    context.setFeature(feature)\n",[234,940,941,944,946],{"class":173,"line":350},[234,942,943],{"class":243},"    value ",[234,945,296],{"class":239},[234,947,531],{"class":243},[234,949,950,953],{"class":173,"line":355},[234,951,952],{"class":239},"    if",[234,954,542],{"class":243},[234,956,957],{"class":173,"line":366},[234,958,959],{"class":239},"        continue\n",[234,961,962,965,967],{"class":173,"line":372},[234,963,964],{"class":243},"    results[feature.id()] ",[234,966,296],{"class":239},[234,968,969],{"class":243}," value\n",[234,971,972],{"class":173,"line":378},[234,973,287],{"emptyLinePlaceholder":286},[234,975,976,978,980,983,986,989],{"class":173,"line":383},[234,977,386],{"class":309},[234,979,497],{"class":243},[234,981,982],{"class":309},"len",[234,984,985],{"class":243},"(results), ",[234,987,988],{"class":302},"\"feature(s) evaluated cleanly\"",[234,990,280],{"class":243},[14,992,993,395,995,997,998,1001,1002,1005,1006,1008,1009,1011,1012,435],{},[203,994,394],{},[18,996,753],{}," must come ",[605,999,1000],{},"after"," the scopes are appended, because it resolves against whatever the context currently knows. ",[18,1003,1004],{},"nullif(\"lot_area\", 0)"," converts a zero area to ",[18,1007,434],{}," so the division yields ",[18,1010,434],{}," rather than raising an evaluation error — the expression engine's idiom for a guarded divide, and usually preferable to catching the error afterwards. Building a dictionary keyed by feature ID makes the result easy to feed into a bulk attribute update, as described in ",[30,1013,1015],{"href":1014},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fupdate-attribute-values-pyqgis\u002F","Update Attribute Values in Bulk with PyQGIS",[192,1017,1019],{"id":1018},"inspect-what-the-expression-will-touch","Inspect what the expression will touch",[14,1021,1022],{},"Before running an expression you did not write yourself — one pulled from a project file, a config, or a user dialog — it is worth asking the engine what it references. Three introspection methods answer that without evaluating anything.",[225,1024,1026],{"className":227,"code":1025,"language":229,"meta":230,"style":230},"expression = QgsExpression('\"owner\" || \\' — \\' || format_number(\"assessed_value\", 0)')\n\nprint(expression.dump())               # canonical form, as the parser understood it\nprint(expression.referencedColumns())  # {'owner', 'assessed_value'}\nprint(expression.referencedVariables())\nprint(expression.needsGeometry())      # False — no geometry function used\n",[18,1027,1028,1052,1056,1067,1077,1084],{"__ignoreMap":230},[234,1029,1030,1032,1034,1036,1039,1042,1045,1047,1050],{"class":173,"line":236},[234,1031,337],{"class":243},[234,1033,296],{"class":239},[234,1035,342],{"class":243},[234,1037,1038],{"class":302},"'\"owner\" || ",[234,1040,1041],{"class":309},"\\'",[234,1043,1044],{"class":302}," — ",[234,1046,1041],{"class":309},[234,1048,1049],{"class":302}," || format_number(\"assessed_value\", 0)'",[234,1051,280],{"class":243},[234,1053,1054],{"class":173,"line":253},[234,1055,287],{"emptyLinePlaceholder":286},[234,1057,1058,1060,1063],{"class":173,"line":259},[234,1059,386],{"class":309},[234,1061,1062],{"class":243},"(expression.dump())               ",[234,1064,1066],{"class":1065},"sjoCn","# canonical form, as the parser understood it\n",[234,1068,1069,1071,1074],{"class":173,"line":265},[234,1070,386],{"class":309},[234,1072,1073],{"class":243},"(expression.referencedColumns())  ",[234,1075,1076],{"class":1065},"# {'owner', 'assessed_value'}\n",[234,1078,1079,1081],{"class":173,"line":271},[234,1080,386],{"class":309},[234,1082,1083],{"class":243},"(expression.referencedVariables())\n",[234,1085,1086,1088,1091],{"class":173,"line":277},[234,1087,386],{"class":309},[234,1089,1090],{"class":243},"(expression.needsGeometry())      ",[234,1092,1093],{"class":1065},"# False — no geometry function used\n",[14,1095,1096,395,1098,1101,1102,1105,1106,1109,1110,1113,1114,1117,1118,1121],{},[203,1097,394],{},[18,1099,1100],{},"dump()"," prints the expression as the parser reconstructed it from the syntax tree, which is the fastest way to spot a quoting mistake — a field reference that was actually parsed as a string literal comes back wrapped in single quotes. ",[18,1103,1104],{},"referencedColumns()"," returns the field names the expression will read; comparing it to ",[18,1107,1108],{},"layer.fields().names()"," catches typos before the loop starts. ",[18,1111,1112],{},"needsGeometry()"," is the one that pays for itself in performance work: when it returns ",[18,1115,1116],{},"False",", you can add the ",[18,1119,1120],{},"QgsFeatureRequest.NoGeometry"," flag to the iteration and skip decoding geometry entirely.",[14,1123,1124],{},"That last point turns into a real optimisation on polygon layers, where geometry decoding usually dominates iteration cost:",[225,1126,1128],{"className":227,"code":1127,"language":229,"meta":230,"style":230},"from qgis.core import QgsFeatureRequest\n\nrequest = QgsFeatureRequest()\nif not expression.needsGeometry():\n    request.setFlags(QgsFeatureRequest.NoGeometry)\nrequest.setSubsetOfAttributes(expression.referencedColumns(), layer.fields())\n\nfor feature in layer.getFeatures(request):\n    context.setFeature(feature)\n    print(expression.evaluate(context))\n",[18,1129,1130,1141,1145,1155,1165,1170,1175,1179,1190,1194],{"__ignoreMap":230},[234,1131,1132,1134,1136,1138],{"class":173,"line":236},[234,1133,240],{"class":239},[234,1135,244],{"class":243},[234,1137,247],{"class":239},[234,1139,1140],{"class":243}," QgsFeatureRequest\n",[234,1142,1143],{"class":173,"line":253},[234,1144,287],{"emptyLinePlaceholder":286},[234,1146,1147,1150,1152],{"class":173,"line":259},[234,1148,1149],{"class":243},"request ",[234,1151,296],{"class":239},[234,1153,1154],{"class":243}," QgsFeatureRequest()\n",[234,1156,1157,1159,1162],{"class":173,"line":265},[234,1158,483],{"class":239},[234,1160,1161],{"class":239}," not",[234,1163,1164],{"class":243}," expression.needsGeometry():\n",[234,1166,1167],{"class":173,"line":271},[234,1168,1169],{"class":243},"    request.setFlags(QgsFeatureRequest.NoGeometry)\n",[234,1171,1172],{"class":173,"line":277},[234,1173,1174],{"class":243},"request.setSubsetOfAttributes(expression.referencedColumns(), layer.fields())\n",[234,1176,1177],{"class":173,"line":283},[234,1178,287],{"emptyLinePlaceholder":286},[234,1180,1181,1183,1185,1187],{"class":173,"line":290},[234,1182,691],{"class":239},[234,1184,928],{"class":243},[234,1186,697],{"class":239},[234,1188,1189],{"class":243}," layer.getFeatures(request):\n",[234,1191,1192],{"class":173,"line":315},[234,1193,938],{"class":243},[234,1195,1196,1198],{"class":173,"line":329},[234,1197,547],{"class":309},[234,1199,389],{"class":243},[14,1201,1202,1204,1205,1207,1208,1211,1212,435],{},[203,1203,394],{}," The request is built ",[605,1206,240],{}," the expression's own introspection, so it stays correct if the expression changes. ",[18,1209,1210],{},"setSubsetOfAttributes()"," takes the referenced column names directly, fetching only the columns that will actually be read. Combined, the two flags can cut iteration time substantially on a wide table — the same technique used for attribute summaries in ",[30,1213,1215],{"href":1214},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fcount-features-by-attribute-pyqgis\u002F","Count and Summarise Features by Attribute in PyQGIS",[14,1217,1218],{},[38,1219,1222,1225,1228,1231,1238,1241,1245,1248,1252,1255,1258,1261,1263,1266,1269,1275,1279,1283,1286,1290,1295,1298,1302,1307,1310,1314],{"viewBox":1220,"role":41,"ariaLabel":1221,"xmlns":43},"0 0 760 230","Expression introspection feeding a feature request: referenced columns become the attribute subset and a false needsGeometry result adds the NoGeometry flag",[45,1223,1224],{},"Building a feature request from the expression itself",[49,1226,1227],{},"An expression is introspected three ways. Its referenced columns become the subset of attributes on a QgsFeatureRequest. Its needsGeometry result, when false, adds the NoGeometry flag. Its referenced variables confirm which scopes must be pushed. The resulting request fetches only what the expression will actually read.",[53,1229],{"x":55,"y":55,"width":56,"height":1230,"fill":58},"230",[60,1232,1233],{},[63,1234,1236],{"id":1235,"viewBox":66,"refX":67,"refY":68,"markerWidth":69,"markerHeight":69,"orient":70},"evalIntroArrow",[72,1237],{"d":74,"fill":93},[77,1239,1240],{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"Let the expression describe what the request should fetch",[53,1242],{"x":87,"y":1243,"width":1244,"height":88,"rx":67,"fill":111,"stroke":128,"style":94},"50","200",[77,1246,1104],{"x":817,"y":90,"style":1247,"fill":128,"textAnchor":83},"text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif",[77,1249,1251],{"x":817,"y":1250,"style":121,"fill":75,"textAnchor":83},"91","owner, assessed_value",[53,1253],{"x":87,"y":1254,"width":1244,"height":88,"rx":67,"fill":111,"stroke":165,"style":94},"114",[77,1256,1112],{"x":817,"y":1257,"style":1247,"fill":165,"textAnchor":83},"136",[77,1259,1116],{"x":817,"y":1260,"style":121,"fill":75,"textAnchor":83},"155",[53,1262],{"x":87,"y":115,"width":1244,"height":138,"rx":67,"fill":111,"stroke":139,"style":94},[77,1264,1265],{"x":817,"y":1244,"style":1247,"fill":82,"textAnchor":83},"referencedVariables()",[77,1267,1268],{"x":817,"y":154,"style":121,"fill":75,"textAnchor":83},"which scopes to push",[53,1270],{"x":1271,"y":1272,"width":1273,"height":1274,"rx":91,"fill":92,"stroke":93,"style":94},"300","82","212","96",[77,1276,1278],{"x":1277,"y":182,"style":99,"fill":100,"textAnchor":83},"406","QgsFeatureRequest",[77,1280,1282],{"x":1277,"y":1281,"style":105,"fill":100,"textAnchor":83},"134","setSubsetOfAttributes",[77,1284,1285],{"x":1277,"y":109,"style":105,"fill":100,"textAnchor":83},"NoGeometry",[53,1287],{"x":1288,"y":1272,"width":1289,"height":1274,"rx":91,"fill":111,"stroke":155,"style":94},"568","176",[77,1291,1294],{"x":1292,"y":1293,"style":99,"fill":155,"textAnchor":83},"656","112","minimal read",[77,1296,1297],{"x":1292,"y":1257,"style":105,"fill":75,"textAnchor":83},"two columns,",[77,1299,1301],{"x":1292,"y":1300,"style":105,"fill":75,"textAnchor":83},"154","no geometry decode",[173,1303],{"x1":154,"y1":1304,"x2":1305,"y2":182,"stroke":93,"style":1306},"76","296","stroke-width:2;marker-end:url(#evalIntroArrow)",[173,1308],{"x1":154,"y1":1309,"x2":1305,"y2":1281,"stroke":93,"style":1306},"140",[173,1311],{"x1":154,"y1":180,"x2":1305,"y2":1312,"stroke":93,"style":1313},"158","stroke-width:2;stroke-dasharray:5 4;marker-end:url(#evalIntroArrow)",[173,1315],{"x1":802,"y1":1316,"x2":1317,"y2":1316,"stroke":155,"style":1318},"130","564","stroke-width:2.5;marker-end:url(#evalIntroArrow)",[192,1320,1322],{"id":1321},"qgis-version-compatibility","QGIS version compatibility",[14,1324,1325,1326,1328],{},"The examples target ",[203,1327,205],{}," (Python 3.12).",[1330,1331,1332,1348],"table",{},[1333,1334,1335],"thead",{},[1336,1337,1338,1342,1345],"tr",{},[1339,1340,1341],"th",{},"QGIS version",[1339,1343,1344],{},"Python",[1339,1346,1347],{},"Notes",[1349,1350,1351,1367,1378],"tbody",{},[1336,1352,1353,1357,1360],{},[1354,1355,1356],"td",{},"3.28 LTR",[1354,1358,1359],{},"3.9",[1354,1361,1362,1363,1366],{},"Identical API. ",[18,1364,1365],{},"QgsExpressionContextUtils.globalProjectLayerScopes()"," available since 3.0.",[1336,1368,1369,1372,1375],{},[1354,1370,1371],{},"3.34 LTR",[1354,1373,1374],{},"3.12",[1354,1376,1377],{},"Baseline for this page.",[1336,1379,1380,1383,1385],{},[1354,1381,1382],{},"3.40 \u002F 3.44",[1354,1384,1374],{},[1354,1386,1387],{},"Same API; the expression function library gained entries but nothing here changed.",[14,1389,1390,706,1392,1394],{},[18,1391,20],{},[18,1393,24],{}," and the scope helpers have been stable for the whole 3.x series, so this code is portable without modification.",[192,1396,1398],{"id":1397},"troubleshooting","Troubleshooting",[197,1400,1401,1415,1428,1442,1454],{},[200,1402,1403,1408,1409,1411,1412,1414],{},[203,1404,1405,1406,435],{},"Everything evaluates to ",[18,1407,430],{}," The feature scope was never pushed. ",[18,1410,402],{}," must be called before ",[18,1413,410],{},", and the layer scope must be present for field names to resolve at all.",[200,1416,1417,1420,1421,1424,1425,1427],{},[203,1418,1419],{},"A field name reports as unknown."," Compare ",[18,1422,1423],{},"expression.referencedColumns()"," against ",[18,1426,1108],{},". A trailing space or a case difference is invisible in the console but fatal to the lookup.",[200,1429,1430,1433,1434,1437,1438,435],{},[203,1431,1432],{},"The result is a string when you expected a number."," The field is text in the source data. Wrap it with ",[18,1435,1436],{},"to_real()"," in the expression, or fix the field type — see ",[30,1439,1441],{"href":1440},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fadd-field-to-layer-pyqgis\u002F","Add a Field to a Layer in PyQGIS",[200,1443,1444,1449,1450,1453],{},[203,1445,1446,1448],{},[18,1447,117],{}," is false but nothing works."," The expression is syntactically valid but semantically wrong, most often single quotes used for a field reference. Print ",[18,1451,1452],{},"expression.dump()"," to see how the engine actually parsed it.",[200,1455,1456,395,1459,1461],{},[203,1457,1458],{},"Evaluation is slow over a large layer.",[18,1460,753],{}," was not called, or the expression was reconstructed inside the loop. Both are hoisting problems, not engine problems.",[192,1463,1465],{"id":1464},"conclusion","Conclusion",[14,1467,1468,1469,1471],{},"Evaluating an expression comes down to a fixed sequence: construct, check the parser, build a context with the right scopes, prepare, then set a feature and evaluate inside the loop while checking the evaluation error each time. Keeping the two error checks distinct in your head is what turns expression evaluation from an occasional source of mystery ",[18,1470,430],{}," values into a predictable tool.",[192,1473,1475],{"id":1474},"frequently-asked-questions","Frequently Asked Questions",[14,1477,1478,1481,1482,1484,1485,1487],{},[203,1479,1480],{},"Why does my expression return None for every feature?","\nThe feature scope is missing. ",[18,1483,24],{}," needs the layer scope for field names to exist and ",[18,1486,402],{}," for their values to be readable. Without both, every field reference resolves to nothing.",[14,1489,1490,1493,1495,1496,1498,1499,1501],{},[203,1491,1492],{},"What is the difference between hasParserError and hasEvalError?",[18,1494,117],{}," reports syntax problems and is meaningful once, right after construction. ",[18,1497,168],{}," reports data-dependent failures and is reset on every ",[18,1500,410],{}," call, so it has to be checked per feature.",[14,1503,1504,1507],{},[203,1505,1506],{},"Do I need to call prepare()?","\nNot for a single evaluation. For a loop it resolves field names once instead of per feature, which is a measurable saving on layers above a few hundred rows. Call it after appending scopes, never before.",[14,1509,1510,1513,1514,1517],{},[203,1511,1512],{},"Can I evaluate an expression without a layer?","\nYes, as long as the expression does not reference fields or geometry. Push only the global and project scopes and evaluate — this is how expressions like ",[18,1515,1516],{},"format_date(now(), 'yyyyMMdd')"," are used to build output paths.",[192,1519,1521],{"id":1520},"related","Related",[197,1523,1524,1529,1535,1541],{},[200,1525,1526,1528],{},[30,1527,33],{"href":32}," — the guide this recipe belongs to",[200,1530,1531],{},[30,1532,1534],{"href":1533},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fselect-features-by-expression-pyqgis\u002F","Select Features by Expression in PyQGIS",[200,1536,1537],{},[30,1538,1540],{"href":1539},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fregister-custom-expression-function-pyqgis\u002F","Register a Custom Expression Function in PyQGIS",[200,1542,1543],{},[30,1544,1546],{"href":1545},"\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002F","Debugging PyQGIS Scripts",[1548,1549,1550],"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 .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":230,"searchDepth":253,"depth":253,"links":1552},[1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563],{"id":194,"depth":253,"text":195},{"id":219,"depth":253,"text":220},{"id":452,"depth":253,"text":453},{"id":611,"depth":253,"text":612},{"id":746,"depth":253,"text":747},{"id":1018,"depth":253,"text":1019},{"id":1321,"depth":253,"text":1322},{"id":1397,"depth":253,"text":1398},{"id":1464,"depth":253,"text":1465},{"id":1474,"depth":253,"text":1475},{"id":1520,"depth":253,"text":1521},"Parse and evaluate a QGIS expression from Python with QgsExpression and QgsExpressionContext, handle parser and evaluation errors, and prepare it for fast iteration.","md",{"slug":1567,"type":1568,"breadcrumb":1569,"datePublished":1570,"dateModified":1570},"evaluate-qgis-expression-in-pyqgis","article","Evaluate an Expression","2026-08-01","\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fevaluate-qgis-expression-in-pyqgis",{"title":5,"description":1564},"pyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fevaluate-qgis-expression-in-pyqgis\u002Findex","DDVW1xE5sJ5SZa23m4khfBIVVOq3US66wqDBFlf1Bw0",1785585990473]