[{"data":1,"prerenderedAt":2609},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions":3},{"id":4,"title":5,"body":6,"description":2598,"extension":2599,"meta":2600,"navigation":481,"path":2605,"seo":2606,"stem":2607,"__hash__":2608},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Findex.md","Working with QGIS Expressions in PyQGIS",{"type":7,"value":8,"toc":2582},"minimark",[9,13,22,41,210,215,253,263,267,296,323,412,419,530,544,548,551,796,822,869,874,891,972,981,985,992,1110,1125,1140,1144,1151,1280,1369,1389,1410,1414,1421,1530,1552,1581,1585,1591,1608,1617,1640,1661,1664,1919,1934,1938,1941,2046,2135,2143,2147,2150,2155,2278,2304,2315,2372,2385,2389,2462,2466,2485,2498,2511,2517,2526,2530,2578],[10,11,5],"h1",{"id":12},"working-with-qgis-expressions-in-pyqgis",[14,15,16,17,21],"p",{},"The QGIS expression engine is the small language that appears everywhere in the application: the field calculator, the feature filter box, rule-based renderers, labelling rules, layout item text, and every data-defined override. It is not a Python subset — it is its own evaluator, written in C++, with its own function library and its own idea of what ",[18,19,20],"code",{},"NULL"," means. Learning to drive it from PyQGIS is one of the highest-leverage skills in the whole API, because a single expression can replace a hand-written feature loop that would otherwise be twenty lines slower and less correct.",[14,23,24,25,30,31,35,36,40],{},"This guide sits inside ",[26,27,29],"a",{"href":28},"\u002Fpyqgis-fundamentals-environment-setup\u002F","PyQGIS Fundamentals & Environment Setup"," because expressions are infrastructure: once you understand the evaluation model, everything from ",[26,32,34],{"href":33},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002F","programmatic layer styling"," to ",[26,37,39],{"href":38},"\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fadd-rule-based-labels-pyqgis\u002F","rule-based labelling"," becomes a matter of writing the right expression string rather than reinventing the logic in Python. It covers how the engine resolves names, how to evaluate an expression against a feature, how to filter and select with one, how expressions drive styling through data-defined properties, and how to extend the function library with your own Python functions.",[14,42,43],{},[44,45,50,54,58,65,82,91,101,108,113,117,122,126,129,132,137,141,145,150,155,159,163,168,173,177,180,184,188,194,198,201,206],"svg",{"viewBox":46,"role":47,"ariaLabel":48,"xmlns":49},"0 0 760 250","img","The QGIS expression evaluation pipeline: an expression string is parsed into a syntax tree, combined with an evaluation context, and evaluated against a feature to produce a value","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[51,52,53],"title",{},"How an expression string becomes a value",[55,56,57],"desc",{},"An expression string enters QgsExpression, which parses it into a syntax tree. A QgsExpressionContext supplies variables, functions and the current feature. The evaluator combines both and returns a QVariant result, or reports a parser or evaluation error.",[59,60],"rect",{"x":61,"y":61,"width":62,"height":63,"fill":64},"0","760","250","#f6f3ea",[66,67,68],"defs",{},[69,70,77],"marker",{"id":71,"viewBox":72,"refX":73,"refY":74,"markerWidth":75,"markerHeight":75,"orient":76},"exprPipeArrow","0 0 10 10","8","5","7","auto-start-reverse",[78,79],"path",{"d":80,"fill":81},"M0 0 L10 5 L0 10 z","#2f3b35",[83,84,90],"text",{"x":85,"y":86,"style":87,"fill":88,"textAnchor":89},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","One expression string, two inputs, one value",[59,92],{"x":93,"y":94,"width":95,"height":96,"rx":97,"fill":98,"stroke":99,"style":100},"20","52","168","76","10","#26322d","#0f766e","stroke-width:2.5",[83,102,107],{"x":103,"y":104,"style":105,"fill":106,"textAnchor":89},"104","80","text-anchor:middle;font-size:12.5px;font-weight:bold;font-family:sans-serif","#d9f99d","expression string",[83,109,112],{"x":103,"y":110,"style":111,"fill":106,"textAnchor":89},"102","text-anchor:middle;font-size:11.5px;font-family:sans-serif","\"pop\" \u002F area($geometry)",[83,114,116],{"x":103,"y":115,"style":111,"fill":106,"textAnchor":89},"119","plain text, not Python",[59,118],{"x":119,"y":94,"width":95,"height":96,"rx":97,"fill":120,"stroke":121,"style":100},"232","#fffdf7","#2563eb",[83,123,125],{"x":124,"y":104,"style":105,"fill":88,"textAnchor":89},"316","QgsExpression",[83,127,128],{"x":124,"y":110,"style":111,"fill":81,"textAnchor":89},"parses to a syntax tree",[83,130,131],{"x":124,"y":115,"style":111,"fill":121,"textAnchor":89},"hasParserError()",[59,133],{"x":119,"y":134,"width":95,"height":135,"rx":97,"fill":120,"stroke":136,"style":100},"158","66","#b45309",[83,138,140],{"x":124,"y":139,"style":105,"fill":88,"textAnchor":89},"184","QgsExpressionContext",[83,142,144],{"x":124,"y":143,"style":111,"fill":81,"textAnchor":89},"205","variables · fields · feature",[59,146],{"x":147,"y":148,"width":149,"height":96,"rx":97,"fill":120,"stroke":99,"style":100},"444","90","150",[83,151,154],{"x":152,"y":153,"style":105,"fill":88,"textAnchor":89},"519","118","evaluate()",[83,156,158],{"x":152,"y":157,"style":111,"fill":81,"textAnchor":89},"140","resolves names,",[83,160,162],{"x":152,"y":161,"style":111,"fill":81,"textAnchor":89},"156","calls functions",[59,164],{"x":165,"y":94,"width":166,"height":135,"rx":97,"fill":120,"stroke":167,"style":100},"632","112","#15803d",[83,169,172],{"x":170,"y":171,"style":105,"fill":167,"textAnchor":89},"688","78","value",[83,174,176],{"x":170,"y":175,"style":111,"fill":81,"textAnchor":89},"99","int · str · NULL",[59,178],{"x":165,"y":157,"width":166,"height":135,"rx":97,"fill":120,"stroke":179,"style":100},"#b91c1c",[83,181,183],{"x":170,"y":182,"style":105,"fill":179,"textAnchor":89},"166","error",[83,185,187],{"x":170,"y":186,"style":111,"fill":81,"textAnchor":89},"187","evalErrorString()",[189,190],"line",{"x1":191,"y1":148,"x2":192,"y2":148,"stroke":81,"style":193},"188","228","stroke-width:2.5;marker-end:url(#exprPipeArrow)",[189,195],{"x1":196,"y1":148,"x2":197,"y2":166,"stroke":81,"style":193},"400","440",[189,199],{"x1":196,"y1":191,"x2":197,"y2":200,"stroke":136,"style":193},"152",[189,202],{"x1":203,"y1":166,"x2":204,"y2":205,"stroke":167,"style":193},"594","628","92",[189,207],{"x1":203,"y1":208,"x2":204,"y2":182,"stroke":179,"style":209},"146","stroke-width:2.5;stroke-dasharray:5 4;marker-end:url(#exprPipeArrow)",[211,212,214],"h2",{"id":213},"prerequisites","Prerequisites",[216,217,218,226,234,237],"ul",{},[219,220,221,225],"li",{},[222,223,224],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer. The expression classes have been stable across the whole 3.x line; version-specific notes are called out inline.",[219,227,228,229,233],{},"Access to PyQGIS through the Python Console, a standalone script, or a plugin — see ",[26,230,232],{"href":231},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002F","QGIS Python Console Basics"," if you have not run PyQGIS before.",[219,235,236],{},"A loaded vector layer with at least one numeric and one text field to experiment against.",[219,238,239,240,244,245,248,249,252],{},"Familiarity with the ",[26,241,243],{"href":242},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002F","QGIS API architecture",", in particular the split between ",[18,246,247],{},"qgis.core"," and ",[18,250,251],{},"qgis.gui",".",[14,254,255,256,258,259,252],{},"Everything in this guide lives in ",[18,257,247],{},", so it works identically in a headless script started outside QGIS Desktop — see ",[26,260,262],{"href":261},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Frunning-python-scripts-outside-qgis-desktop\u002F","Running Python Scripts Outside QGIS Desktop",[211,264,266],{"id":265},"how-the-expression-engine-resolves-names","How the expression engine resolves names",[14,268,269,270,273,274,277,278,281,282,285,286,290,291,295],{},"An expression string on its own means nothing. When you write ",[18,271,272],{},"\"population\" \u002F $area > 100",", the engine has to decide what ",[18,275,276],{},"\"population\""," refers to, what ",[18,279,280],{},"$area"," means, and whether a function called ",[18,283,284],{},"round"," exists. It answers those questions from a ",[222,287,288],{},[18,289,140],{},", which is a stack of ",[292,293,294],"em",{},"scopes",". Each scope contributes variables and functions, and later scopes shadow earlier ones — exactly like nested Python namespaces.",[14,297,298,299,302,303,306,307,310,311,314,315,318,319,322],{},"The conventional stack, from lowest to highest priority, is: global scope (QGIS-wide variables such as ",[18,300,301],{},"@qgis_version","), project scope (",[18,304,305],{},"@project_title"," and anything set in Project Properties), layer scope (",[18,308,309],{},"@layer_name",", ",[18,312,313],{},"@layer_id","), and finally the feature scope, which contributes the current feature's field values and ",[18,316,317],{},"$geometry",". Because the feature scope sits on top, a field named ",[18,320,321],{},"layer_name"," would shadow the layer variable — a subtle collision worth knowing about when you inherit someone else's schema.",[14,324,325],{},[44,326,329,332,335,338,345,348,351,357,362,365,369,373,375,379,383,387,390,394,399,404,408],{"viewBox":327,"role":47,"ariaLabel":328,"xmlns":49},"0 0 760 300","The expression context scope stack, from the global scope at the bottom through project and layer scopes to the feature scope at the top, with later scopes shadowing earlier ones",[51,330,331],{},"The QgsExpressionContext scope stack",[55,333,334],{},"Four stacked scopes. The global scope supplies QGIS-wide variables, the project scope supplies project variables, the layer scope supplies layer name and identifier, and the feature scope on top supplies field values and the geometry. Lookup runs from the top down, so the feature scope shadows the ones beneath it.",[59,336],{"x":61,"y":61,"width":62,"height":337,"fill":64},"300",[66,339,340],{},[69,341,343],{"id":342,"viewBox":72,"refX":73,"refY":74,"markerWidth":75,"markerHeight":75,"orient":76},"exprScopeArrow",[78,344],{"d":80,"fill":99},[83,346,347],{"x":85,"y":86,"style":87,"fill":88,"textAnchor":89},"Name lookup runs top-down: the last scope pushed wins",[59,349],{"x":149,"y":350,"width":196,"height":94,"rx":73,"fill":120,"stroke":99,"style":100},"46",[83,352,356],{"x":353,"y":354,"style":355,"fill":88},"172","70","font-size:12.5px;font-weight:bold;font-family:sans-serif","4 · Feature scope",[83,358,361],{"x":353,"y":359,"style":360,"fill":81},"88","font-size:11.5px;font-family:sans-serif","field values · $geometry · $id · @row_number",[59,363],{"x":149,"y":364,"width":196,"height":94,"rx":73,"fill":120,"stroke":121,"style":100},"106",[83,366,368],{"x":353,"y":367,"style":355,"fill":88},"130","3 · Layer scope",[83,370,372],{"x":353,"y":371,"style":360,"fill":81},"148","@layer_name · @layer_id · layer fields",[59,374],{"x":149,"y":182,"width":196,"height":94,"rx":73,"fill":120,"stroke":136,"style":100},[83,376,378],{"x":353,"y":377,"style":355,"fill":88},"190","2 · Project scope",[83,380,382],{"x":353,"y":381,"style":360,"fill":81},"208","@project_title · @project_crs · custom variables",[59,384],{"x":149,"y":385,"width":196,"height":94,"rx":73,"fill":120,"stroke":386,"style":100},"226","#59645f",[83,388,389],{"x":353,"y":63,"style":355,"fill":88},"1 · Global scope",[83,391,393],{"x":353,"y":392,"style":360,"fill":81},"268","@qgis_version · @user_full_name",[189,395],{"x1":396,"y1":397,"x2":396,"y2":135,"stroke":99,"style":398},"120","270","stroke-width:2.5;marker-end:url(#exprScopeArrow)",[83,400,403],{"x":166,"y":401,"style":111,"fill":99,"transform":402,"textAnchor":89},"180","rotate(-90 112 180)","pushed in this order",[83,405,407],{"x":406,"y":354,"style":360,"fill":99},"576","highest priority",[83,409,411],{"x":406,"y":410,"style":360,"fill":386},"252","lowest priority",[14,413,414,415,418],{},"Building that stack by hand is three lines, and ",[18,416,417],{},"QgsExpressionContextUtils"," provides the standard scopes so you never have to remember what belongs in each:",[420,421,426],"pre",{"className":422,"code":423,"language":424,"meta":425,"style":425},"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]\n\ncontext = QgsExpressionContext()\ncontext.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\n","python","",[18,427,428,446,452,458,464,470,476,483,508,513,524],{"__ignoreMap":425},[429,430,432,436,440,443],"span",{"class":189,"line":431},1,[429,433,435],{"class":434},"snl16","from",[429,437,439],{"class":438},"s95oV"," qgis.core ",[429,441,442],{"class":434},"import",[429,444,445],{"class":438}," (\n",[429,447,449],{"class":189,"line":448},2,[429,450,451],{"class":438},"    QgsExpression,\n",[429,453,455],{"class":189,"line":454},3,[429,456,457],{"class":438},"    QgsExpressionContext,\n",[429,459,461],{"class":189,"line":460},4,[429,462,463],{"class":438},"    QgsExpressionContextUtils,\n",[429,465,467],{"class":189,"line":466},5,[429,468,469],{"class":438},"    QgsProject,\n",[429,471,473],{"class":189,"line":472},6,[429,474,475],{"class":438},")\n",[429,477,479],{"class":189,"line":478},7,[429,480,482],{"emptyLinePlaceholder":481},true,"\n",[429,484,486,489,492,495,499,502,505],{"class":189,"line":485},8,[429,487,488],{"class":438},"layer ",[429,490,491],{"class":434},"=",[429,493,494],{"class":438}," QgsProject.instance().mapLayersByName(",[429,496,498],{"class":497},"sU2Wk","\"parcels\"",[429,500,501],{"class":438},")[",[429,503,61],{"class":504},"sDLfK",[429,506,507],{"class":438},"]\n",[429,509,511],{"class":189,"line":510},9,[429,512,482],{"emptyLinePlaceholder":481},[429,514,516,519,521],{"class":189,"line":515},10,[429,517,518],{"class":438},"context ",[429,520,491],{"class":434},[429,522,523],{"class":438}," QgsExpressionContext()\n",[429,525,527],{"class":189,"line":526},11,[429,528,529],{"class":438},"context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\n",[14,531,532,535,536,539,540,543],{},[222,533,534],{},"Breakdown:"," ",[18,537,538],{},"globalProjectLayerScopes()"," returns the global, project, and layer scopes already populated and in the right order. ",[18,541,542],{},"appendScopes()"," pushes them onto the stack. What is deliberately missing is the feature scope — you add that per feature, because it changes on every iteration.",[211,545,547],{"id":546},"evaluating-an-expression-against-a-feature","Evaluating an expression against a feature",[14,549,550],{},"With a context in hand, evaluation is a two-step dance: parse once, then set the feature and evaluate repeatedly. Parsing is the expensive half, so hoisting it out of the loop matters on large layers.",[420,552,554],{"className":422,"code":553,"language":424,"meta":425,"style":425},"from qgis.core import (\n    QgsExpression,\n    QgsExpressionContext,\n    QgsExpressionContextUtils,\n    QgsProject,\n)\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\n\nexpression = QgsExpression('\"assessed_value\" \u002F area($geometry)')\nif expression.hasParserError():\n    raise ValueError(expression.parserErrorString())\n\ncontext = QgsExpressionContext()\ncontext.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\n\nexpression.prepare(context)\n\nfor feature in layer.getFeatures():\n    context.setFeature(feature)\n    value = expression.evaluate(context)\n    if expression.hasEvalError():\n        print(f\"feature {feature.id()}: {expression.evalErrorString()}\")\n        continue\n    print(f\"feature {feature.id()}: {value}\")\n",[18,555,556,566,570,574,578,582,586,590,606,610,625,633,645,650,659,664,669,675,680,695,701,712,721,760,766],{"__ignoreMap":425},[429,557,558,560,562,564],{"class":189,"line":431},[429,559,435],{"class":434},[429,561,439],{"class":438},[429,563,442],{"class":434},[429,565,445],{"class":438},[429,567,568],{"class":189,"line":448},[429,569,451],{"class":438},[429,571,572],{"class":189,"line":454},[429,573,457],{"class":438},[429,575,576],{"class":189,"line":460},[429,577,463],{"class":438},[429,579,580],{"class":189,"line":466},[429,581,469],{"class":438},[429,583,584],{"class":189,"line":472},[429,585,475],{"class":438},[429,587,588],{"class":189,"line":478},[429,589,482],{"emptyLinePlaceholder":481},[429,591,592,594,596,598,600,602,604],{"class":189,"line":485},[429,593,488],{"class":438},[429,595,491],{"class":434},[429,597,494],{"class":438},[429,599,498],{"class":497},[429,601,501],{"class":438},[429,603,61],{"class":504},[429,605,507],{"class":438},[429,607,608],{"class":189,"line":510},[429,609,482],{"emptyLinePlaceholder":481},[429,611,612,615,617,620,623],{"class":189,"line":515},[429,613,614],{"class":438},"expression ",[429,616,491],{"class":434},[429,618,619],{"class":438}," QgsExpression(",[429,621,622],{"class":497},"'\"assessed_value\" \u002F area($geometry)'",[429,624,475],{"class":438},[429,626,627,630],{"class":189,"line":526},[429,628,629],{"class":434},"if",[429,631,632],{"class":438}," expression.hasParserError():\n",[429,634,636,639,642],{"class":189,"line":635},12,[429,637,638],{"class":434},"    raise",[429,640,641],{"class":504}," ValueError",[429,643,644],{"class":438},"(expression.parserErrorString())\n",[429,646,648],{"class":189,"line":647},13,[429,649,482],{"emptyLinePlaceholder":481},[429,651,653,655,657],{"class":189,"line":652},14,[429,654,518],{"class":438},[429,656,491],{"class":434},[429,658,523],{"class":438},[429,660,662],{"class":189,"line":661},15,[429,663,529],{"class":438},[429,665,667],{"class":189,"line":666},16,[429,668,482],{"emptyLinePlaceholder":481},[429,670,672],{"class":189,"line":671},17,[429,673,674],{"class":438},"expression.prepare(context)\n",[429,676,678],{"class":189,"line":677},18,[429,679,482],{"emptyLinePlaceholder":481},[429,681,683,686,689,692],{"class":189,"line":682},19,[429,684,685],{"class":434},"for",[429,687,688],{"class":438}," feature ",[429,690,691],{"class":434},"in",[429,693,694],{"class":438}," layer.getFeatures():\n",[429,696,698],{"class":189,"line":697},20,[429,699,700],{"class":438},"    context.setFeature(feature)\n",[429,702,704,707,709],{"class":189,"line":703},21,[429,705,706],{"class":438},"    value ",[429,708,491],{"class":434},[429,710,711],{"class":438}," expression.evaluate(context)\n",[429,713,715,718],{"class":189,"line":714},22,[429,716,717],{"class":434},"    if",[429,719,720],{"class":438}," expression.hasEvalError():\n",[429,722,724,727,730,733,736,739,742,745,748,750,753,755,758],{"class":189,"line":723},23,[429,725,726],{"class":504},"        print",[429,728,729],{"class":438},"(",[429,731,732],{"class":434},"f",[429,734,735],{"class":497},"\"feature ",[429,737,738],{"class":504},"{",[429,740,741],{"class":438},"feature.id()",[429,743,744],{"class":504},"}",[429,746,747],{"class":497},": ",[429,749,738],{"class":504},[429,751,752],{"class":438},"expression.evalErrorString()",[429,754,744],{"class":504},[429,756,757],{"class":497},"\"",[429,759,475],{"class":438},[429,761,763],{"class":189,"line":762},24,[429,764,765],{"class":434},"        continue\n",[429,767,769,772,774,776,778,780,782,784,786,788,790,792,794],{"class":189,"line":768},25,[429,770,771],{"class":504},"    print",[429,773,729],{"class":438},[429,775,732],{"class":434},[429,777,735],{"class":497},[429,779,738],{"class":504},[429,781,741],{"class":438},[429,783,744],{"class":504},[429,785,747],{"class":497},[429,787,738],{"class":504},[429,789,172],{"class":438},[429,791,744],{"class":504},[429,793,757],{"class":497},[429,795,475],{"class":438},[14,797,798,535,800,802,803,806,807,810,811,814,815,818,819,821],{},[222,799,534],{},[18,801,131],{}," catches syntax problems ",[292,804,805],{},"before"," the loop — a misspelled function name or an unbalanced quote fails here, not per feature. ",[18,808,809],{},"prepare()"," lets the engine resolve field indexes once against the context, which measurably speeds up long iterations. ",[18,812,813],{},"setFeature()"," swaps the feature scope in place. ",[18,816,817],{},"hasEvalError()"," is separate from the parser error and must be checked after each ",[18,820,154],{},", because a valid expression can still fail on a particular row — dividing by a zero area, for example.",[14,823,824,825,828,829,832,833,836,837,840,841,844,845,848,849,852,853,856,857,859,860,863,864,868],{},"Two details trip people up. Field references use ",[222,826,827],{},"double quotes"," (",[18,830,831],{},"\"assessed_value\"","); single quotes denote a ",[292,834,835],{},"string literal",", so ",[18,838,839],{},"'assessed_value'"," evaluates to the literal text rather than the column. And the return value is a Python object converted from a ",[18,842,843],{},"QVariant",": numbers arrive as ",[18,846,847],{},"int"," or ",[18,850,851],{},"float",", text as ",[18,854,855],{},"str",", and SQL ",[18,858,20],{}," as ",[18,861,862],{},"None",". The dedicated recipe ",[26,865,867],{"href":866},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fevaluate-qgis-expression-in-pyqgis\u002F","Evaluate a QGIS Expression in PyQGIS"," works through the error-handling patterns in more depth.",[870,871,873],"h3",{"id":872},"evaluating-without-a-feature","Evaluating without a feature",[14,875,876,877,310,879,310,882,885,886,890],{},"Not every expression needs a row. Expressions that only reference variables or constants — ",[18,878,301],{},[18,880,881],{},"now()",[18,883,884],{},"1 + 1"," — evaluate against a bare context, which makes them handy for building dynamic output paths in ",[26,887,889],{"href":888},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002F","batch processing pipelines",":",[420,892,894],{"className":422,"code":893,"language":424,"meta":425,"style":425},"from qgis.core import QgsExpression, QgsExpressionContext, QgsExpressionContextUtils\n\ncontext = QgsExpressionContext()\ncontext.appendScopes([QgsExpressionContextUtils.globalScope(),\n                      QgsExpressionContextUtils.projectScope(QgsProject.instance())])\n\nstamp = QgsExpression(\"format_date(now(), 'yyyyMMdd')\").evaluate(context)\nprint(f\"\u002Fdata\u002Fexports\u002Frun_{stamp}.gpkg\")\n",[18,895,896,907,911,919,924,929,933,948],{"__ignoreMap":425},[429,897,898,900,902,904],{"class":189,"line":431},[429,899,435],{"class":434},[429,901,439],{"class":438},[429,903,442],{"class":434},[429,905,906],{"class":438}," QgsExpression, QgsExpressionContext, QgsExpressionContextUtils\n",[429,908,909],{"class":189,"line":448},[429,910,482],{"emptyLinePlaceholder":481},[429,912,913,915,917],{"class":189,"line":454},[429,914,518],{"class":438},[429,916,491],{"class":434},[429,918,523],{"class":438},[429,920,921],{"class":189,"line":460},[429,922,923],{"class":438},"context.appendScopes([QgsExpressionContextUtils.globalScope(),\n",[429,925,926],{"class":189,"line":466},[429,927,928],{"class":438},"                      QgsExpressionContextUtils.projectScope(QgsProject.instance())])\n",[429,930,931],{"class":189,"line":472},[429,932,482],{"emptyLinePlaceholder":481},[429,934,935,938,940,942,945],{"class":189,"line":478},[429,936,937],{"class":438},"stamp ",[429,939,491],{"class":434},[429,941,619],{"class":438},[429,943,944],{"class":497},"\"format_date(now(), 'yyyyMMdd')\"",[429,946,947],{"class":438},").evaluate(context)\n",[429,949,950,953,955,957,960,962,965,967,970],{"class":189,"line":485},[429,951,952],{"class":504},"print",[429,954,729],{"class":438},[429,956,732],{"class":434},[429,958,959],{"class":497},"\"\u002Fdata\u002Fexports\u002Frun_",[429,961,738],{"class":504},[429,963,964],{"class":438},"stamp",[429,966,744],{"class":504},[429,968,969],{"class":497},".gpkg\"",[429,971,475],{"class":438},[14,973,974,976,977,980],{},[222,975,534],{}," Only the global and project scopes are pushed, because there is no layer or feature involved. ",[18,978,979],{},"format_date()"," is an expression-engine function, not a Python one — the whole point is that the same string would work in the layout item that stamps the map.",[211,982,984],{"id":983},"filtering-and-selecting-features-with-an-expression","Filtering and selecting features with an expression",[14,986,987,988,991],{},"The single biggest performance win from expressions is pushing the filter down to the data provider instead of testing every feature in Python. ",[18,989,990],{},"QgsFeatureRequest.setFilterExpression()"," does exactly that: for a PostGIS or GeoPackage layer the predicate is translated into SQL and executed by the database, so only matching rows ever cross into Python.",[420,993,995],{"className":422,"code":994,"language":424,"meta":425,"style":425},"from qgis.core import QgsFeatureRequest, QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\n\nrequest = QgsFeatureRequest()\nrequest.setFilterExpression('\"zoning\" = \\'residential\\' AND \"assessed_value\" > 250000')\nrequest.setSubsetOfAttributes([\"parcel_id\", \"assessed_value\"], layer.fields())\n\nfor feature in layer.getFeatures(request):\n    print(feature[\"parcel_id\"], feature[\"assessed_value\"])\n",[18,996,997,1008,1012,1028,1032,1042,1063,1078,1082,1093],{"__ignoreMap":425},[429,998,999,1001,1003,1005],{"class":189,"line":431},[429,1000,435],{"class":434},[429,1002,439],{"class":438},[429,1004,442],{"class":434},[429,1006,1007],{"class":438}," QgsFeatureRequest, QgsProject\n",[429,1009,1010],{"class":189,"line":448},[429,1011,482],{"emptyLinePlaceholder":481},[429,1013,1014,1016,1018,1020,1022,1024,1026],{"class":189,"line":454},[429,1015,488],{"class":438},[429,1017,491],{"class":434},[429,1019,494],{"class":438},[429,1021,498],{"class":497},[429,1023,501],{"class":438},[429,1025,61],{"class":504},[429,1027,507],{"class":438},[429,1029,1030],{"class":189,"line":460},[429,1031,482],{"emptyLinePlaceholder":481},[429,1033,1034,1037,1039],{"class":189,"line":466},[429,1035,1036],{"class":438},"request ",[429,1038,491],{"class":434},[429,1040,1041],{"class":438}," QgsFeatureRequest()\n",[429,1043,1044,1047,1050,1053,1056,1058,1061],{"class":189,"line":472},[429,1045,1046],{"class":438},"request.setFilterExpression(",[429,1048,1049],{"class":497},"'\"zoning\" = ",[429,1051,1052],{"class":504},"\\'",[429,1054,1055],{"class":497},"residential",[429,1057,1052],{"class":504},[429,1059,1060],{"class":497}," AND \"assessed_value\" > 250000'",[429,1062,475],{"class":438},[429,1064,1065,1068,1071,1073,1075],{"class":189,"line":478},[429,1066,1067],{"class":438},"request.setSubsetOfAttributes([",[429,1069,1070],{"class":497},"\"parcel_id\"",[429,1072,310],{"class":438},[429,1074,831],{"class":497},[429,1076,1077],{"class":438},"], layer.fields())\n",[429,1079,1080],{"class":189,"line":485},[429,1081,482],{"emptyLinePlaceholder":481},[429,1083,1084,1086,1088,1090],{"class":189,"line":510},[429,1085,685],{"class":434},[429,1087,688],{"class":438},[429,1089,691],{"class":434},[429,1091,1092],{"class":438}," layer.getFeatures(request):\n",[429,1094,1095,1097,1100,1102,1105,1107],{"class":189,"line":515},[429,1096,771],{"class":504},[429,1098,1099],{"class":438},"(feature[",[429,1101,1070],{"class":497},[429,1103,1104],{"class":438},"], feature[",[429,1106,831],{"class":497},[429,1108,1109],{"class":438},"])\n",[14,1111,1112,535,1114,1117,1118,1121,1122,1124],{},[222,1113,534],{},[18,1115,1116],{},"setFilterExpression()"," takes the same string the GUI filter box accepts. ",[18,1119,1120],{},"setSubsetOfAttributes()"," narrows the columns fetched, which is a second, independent saving — combined, the two turn a full-table scan into a targeted query. Note the escaping: the whole expression is a Python string, so the single quotes around ",[18,1123,1055],{}," are backslash-escaped.",[14,1126,1127,1128,1131,1132,1136,1137,1139],{},"To highlight the matches on the map rather than iterate them, ",[18,1129,1130],{},"selectByExpression()"," is the one-liner equivalent, and it is the basis of ",[26,1133,1135],{"href":1134},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fselect-features-by-expression-pyqgis\u002F","Select Features by Expression in PyQGIS",". Both approaches beat the naive alternative — reading every feature and testing attributes in a Python ",[18,1138,629],{}," — by a wide margin on anything larger than a few thousand rows.",[211,1141,1143],{"id":1142},"expressions-that-drive-styling","Expressions that drive styling",[14,1145,1146,1147,1150],{},"Data-defined properties are where expressions stop being a query language and become a design tool. Any symbol property that accepts an override — size, colour, rotation, opacity, offset — can be bound to a ",[18,1148,1149],{},"QgsProperty"," built from an expression, and QGIS re-evaluates it per feature at render time. Nothing is stored on the layer; the styling simply becomes a function of the data.",[14,1152,1153],{},[44,1154,1157,1160,1163,1165,1172,1175,1178,1182,1186,1190,1196,1200,1203,1207,1210,1215,1220,1225,1229,1233,1237,1241,1245,1252,1255,1258,1261,1265,1269,1272,1276],{"viewBox":1155,"role":47,"ariaLabel":1156,"xmlns":49},"0 0 760 268","A data-defined property binds an expression to a symbol property so each feature is rendered with a value computed from its own attributes",[51,1158,1159],{},"How a data-defined property renders a feature",[55,1161,1162],{},"Three feature rows with different population values pass through a QgsProperty holding a scale_linear expression. Each row is rendered as a circle whose radius is computed from its population, producing three proportionally sized symbols on the map.",[59,1164],{"x":61,"y":61,"width":62,"height":392,"fill":64},[66,1166,1167],{},[69,1168,1170],{"id":1169,"viewBox":72,"refX":73,"refY":74,"markerWidth":75,"markerHeight":75,"orient":76},"exprDdArrow",[78,1171],{"d":80,"fill":81},[83,1173,1174],{"x":85,"y":86,"style":87,"fill":88,"textAnchor":89},"One expression, a different symbol size per feature",[59,1176],{"x":93,"y":94,"width":1177,"height":401,"rx":97,"fill":120,"stroke":121,"style":100},"176",[83,1179,1181],{"x":1180,"y":96,"style":105,"fill":88,"textAnchor":89},"108","attribute table",[189,1183],{"x1":1184,"y1":359,"x2":401,"y2":359,"stroke":121,"style":1185},"36","stroke-width:1.5",[83,1187,1189],{"x":350,"y":1188,"style":360,"fill":81},"110","Ashford",[83,1191,1195],{"x":1192,"y":1188,"style":1193,"fill":121,"textAnchor":1194},"170","text-anchor:end;font-size:11.5px;font-family:sans-serif","end","4 200",[83,1197,1199],{"x":350,"y":1198,"style":360,"fill":81},"142","Brant",[83,1201,1202],{"x":1192,"y":1198,"style":1193,"fill":121,"textAnchor":1194},"28 500",[83,1204,1206],{"x":350,"y":1205,"style":360,"fill":81},"174","Corran",[83,1208,1209],{"x":1192,"y":1205,"style":1193,"fill":121,"textAnchor":1194},"96 000",[83,1211,1214],{"x":1180,"y":1212,"style":1213,"fill":386,"textAnchor":89},"212","text-anchor:middle;font-size:11px;font-family:sans-serif","field: \"population\"",[59,1216],{"x":1217,"y":205,"width":1218,"height":1219,"rx":97,"fill":98,"stroke":99,"style":100},"240","220","100",[83,1221,1224],{"x":1222,"y":153,"style":1223,"fill":106,"textAnchor":89},"350","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","QgsProperty.fromExpression",[83,1226,1228],{"x":1222,"y":1227,"style":111,"fill":106,"textAnchor":89},"144","scale_linear(\"population\",",[83,1230,1232],{"x":1222,"y":1231,"style":111,"fill":106,"textAnchor":89},"161","0, 100000, 2, 12)",[83,1234,1236],{"x":1222,"y":1235,"style":1213,"fill":106,"textAnchor":89},"182","re-evaluated per feature",[59,1238],{"x":1239,"y":94,"width":1240,"height":401,"rx":97,"fill":120,"stroke":167,"style":100},"504","236",[83,1242,1244],{"x":1243,"y":96,"style":105,"fill":88,"textAnchor":89},"622","rendered symbols",[1246,1247],"circle",{"cx":1248,"cy":157,"r":1249,"fill":167,"fillOpacity":1250,"stroke":167,"style":1251},"560","9",0.45,"stroke-width:2",[83,1253,1254],{"x":1248,"y":353,"style":1213,"fill":81,"textAnchor":89},"2.5 mm",[1246,1256],{"cx":1243,"cy":157,"r":1257,"fill":167,"fillOpacity":1250,"stroke":167,"style":1251},"18",[83,1259,1260],{"x":1243,"y":353,"style":1213,"fill":81,"textAnchor":89},"5.4 mm",[1246,1262],{"cx":1263,"cy":157,"r":1264,"fill":167,"fillOpacity":1250,"stroke":167,"style":1251},"694","31",[83,1266,1268],{"x":1263,"y":1267,"style":1213,"fill":81,"textAnchor":89},"186","11.6 mm",[83,1270,1271],{"x":1243,"y":1212,"style":1213,"fill":386,"textAnchor":89},"nothing written to the layer",[189,1273],{"x1":1274,"y1":1198,"x2":1240,"y2":1198,"stroke":81,"style":1275},"196","stroke-width:2.5;marker-end:url(#exprDdArrow)",[189,1277],{"x1":1278,"y1":1198,"x2":1279,"y2":1198,"stroke":81,"style":1275},"460","500",[420,1281,1283],{"className":422,"code":1282,"language":424,"meta":425,"style":425},"from qgis.core import QgsProperty, QgsProject, QgsSymbolLayer\n\nlayer = QgsProject.instance().mapLayersByName(\"towns\")[0]\nsymbol_layer = layer.renderer().symbol().symbolLayer(0)\n\nsymbol_layer.setDataDefinedProperty(\n    QgsSymbolLayer.PropertySize,\n    QgsProperty.fromExpression('scale_linear(\"population\", 0, 100000, 2, 12)'),\n)\n\nlayer.triggerRepaint()\n",[18,1284,1285,1296,1300,1317,1331,1335,1340,1345,1356,1360,1364],{"__ignoreMap":425},[429,1286,1287,1289,1291,1293],{"class":189,"line":431},[429,1288,435],{"class":434},[429,1290,439],{"class":438},[429,1292,442],{"class":434},[429,1294,1295],{"class":438}," QgsProperty, QgsProject, QgsSymbolLayer\n",[429,1297,1298],{"class":189,"line":448},[429,1299,482],{"emptyLinePlaceholder":481},[429,1301,1302,1304,1306,1308,1311,1313,1315],{"class":189,"line":454},[429,1303,488],{"class":438},[429,1305,491],{"class":434},[429,1307,494],{"class":438},[429,1309,1310],{"class":497},"\"towns\"",[429,1312,501],{"class":438},[429,1314,61],{"class":504},[429,1316,507],{"class":438},[429,1318,1319,1322,1324,1327,1329],{"class":189,"line":460},[429,1320,1321],{"class":438},"symbol_layer ",[429,1323,491],{"class":434},[429,1325,1326],{"class":438}," layer.renderer().symbol().symbolLayer(",[429,1328,61],{"class":504},[429,1330,475],{"class":438},[429,1332,1333],{"class":189,"line":466},[429,1334,482],{"emptyLinePlaceholder":481},[429,1336,1337],{"class":189,"line":472},[429,1338,1339],{"class":438},"symbol_layer.setDataDefinedProperty(\n",[429,1341,1342],{"class":189,"line":478},[429,1343,1344],{"class":438},"    QgsSymbolLayer.PropertySize,\n",[429,1346,1347,1350,1353],{"class":189,"line":485},[429,1348,1349],{"class":438},"    QgsProperty.fromExpression(",[429,1351,1352],{"class":497},"'scale_linear(\"population\", 0, 100000, 2, 12)'",[429,1354,1355],{"class":438},"),\n",[429,1357,1358],{"class":189,"line":510},[429,1359,475],{"class":438},[429,1361,1362],{"class":189,"line":515},[429,1363,482],{"emptyLinePlaceholder":481},[429,1365,1366],{"class":189,"line":526},[429,1367,1368],{"class":438},"layer.triggerRepaint()\n",[14,1370,1371,535,1373,1376,1377,1380,1381,1384,1385,1388],{},[222,1372,534],{},[18,1374,1375],{},"QgsSymbolLayer.PropertySize"," names the property being overridden; the enum has a member for every overridable attribute. ",[18,1378,1379],{},"QgsProperty.fromExpression()"," wraps the expression string. ",[18,1382,1383],{},"scale_linear()"," maps an input range onto an output range, which is the idiomatic way to size symbols proportionally without writing a classification. ",[18,1386,1387],{},"triggerRepaint()"," is what makes the canvas pick the change up — without it the layer keeps drawing from its cached render.",[14,1390,1391,1392,1395,1396,1399,1400,1404,1405,1409],{},"The same mechanism drives colour (",[18,1393,1394],{},"PropertyFillColor"," with a ",[18,1397,1398],{},"ramp_color()"," expression), rotation, and label placement. ",[26,1401,1403],{"href":1402},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fset-vector-layer-symbol-color-pyqgis\u002F","Set a Vector Layer's Symbol Colour in PyQGIS"," covers the static case; ",[26,1406,1408],{"href":1407},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fdata-defined-symbol-size-expression-pyqgis\u002F","Data-Defined Symbol Size with an Expression"," works through the dynamic one, including how to keep the legend readable.",[211,1411,1413],{"id":1412},"registering-your-own-expression-functions","Registering your own expression functions",[14,1415,1416,1417,1420],{},"When an expression gets long enough to be unreadable, or needs logic the built-in library cannot express, you can add a function to the engine in Python. The ",[18,1418,1419],{},"@qgsfunction"," decorator registers it globally, and from that moment it appears in the expression builder dialog for users as well as in your scripts.",[420,1422,1424],{"className":422,"code":1423,"language":424,"meta":425,"style":425},"from qgis.core import qgsfunction\n\n\n@qgsfunction(args=\"auto\", group=\"Custom\", referenced_columns=[])\ndef utm_zone(longitude, feature, parent):\n    \"\"\"Return the UTM zone number containing the given longitude.\"\"\"\n    return int((longitude + 180) \u002F 6) + 1\n",[18,1425,1426,1437,1441,1445,1481,1492,1497],{"__ignoreMap":425},[429,1427,1428,1430,1432,1434],{"class":189,"line":431},[429,1429,435],{"class":434},[429,1431,439],{"class":438},[429,1433,442],{"class":434},[429,1435,1436],{"class":438}," qgsfunction\n",[429,1438,1439],{"class":189,"line":448},[429,1440,482],{"emptyLinePlaceholder":481},[429,1442,1443],{"class":189,"line":454},[429,1444,482],{"emptyLinePlaceholder":481},[429,1446,1447,1450,1452,1456,1458,1461,1463,1466,1468,1471,1473,1476,1478],{"class":189,"line":460},[429,1448,1419],{"class":1449},"svObZ",[429,1451,729],{"class":438},[429,1453,1455],{"class":1454},"s9osk","args",[429,1457,491],{"class":434},[429,1459,1460],{"class":497},"\"auto\"",[429,1462,310],{"class":438},[429,1464,1465],{"class":1454},"group",[429,1467,491],{"class":434},[429,1469,1470],{"class":497},"\"Custom\"",[429,1472,310],{"class":438},[429,1474,1475],{"class":1454},"referenced_columns",[429,1477,491],{"class":434},[429,1479,1480],{"class":438},"[])\n",[429,1482,1483,1486,1489],{"class":189,"line":466},[429,1484,1485],{"class":434},"def",[429,1487,1488],{"class":1449}," utm_zone",[429,1490,1491],{"class":438},"(longitude, feature, parent):\n",[429,1493,1494],{"class":189,"line":472},[429,1495,1496],{"class":497},"    \"\"\"Return the UTM zone number containing the given longitude.\"\"\"\n",[429,1498,1499,1502,1505,1508,1511,1514,1517,1520,1523,1525,1527],{"class":189,"line":478},[429,1500,1501],{"class":434},"    return",[429,1503,1504],{"class":504}," int",[429,1506,1507],{"class":438},"((longitude ",[429,1509,1510],{"class":434},"+",[429,1512,1513],{"class":504}," 180",[429,1515,1516],{"class":438},") ",[429,1518,1519],{"class":434},"\u002F",[429,1521,1522],{"class":504}," 6",[429,1524,1516],{"class":438},[429,1526,1510],{"class":434},[429,1528,1529],{"class":504}," 1\n",[14,1531,1532,535,1534,1537,1538,248,1541,1544,1545,1547,1548,1551],{},[222,1533,534],{},[18,1535,1536],{},"args=\"auto\""," tells QGIS to infer the argument count from the signature — everything before the mandatory ",[18,1539,1540],{},"feature",[18,1542,1543],{},"parent"," parameters. ",[18,1546,1465],{}," decides which folder the function appears under in the expression builder. ",[18,1549,1550],{},"referenced_columns=[]"," declares that the function reads no fields directly, which lets the provider keep optimising attribute fetching; omit it and QGIS conservatively loads every column. The docstring becomes the help text shown in the dialog.",[14,1553,1554,1555,1558,1559,1562,1563,1566,1567,1570,1571,1575,1576,1580],{},"Once registered, ",[18,1556,1557],{},"utm_zone(x_min($geometry))"," works anywhere an expression works — field calculator, filters, labels. The lifecycle detail that catches people out is unregistration: a plugin that registers functions in ",[18,1560,1561],{},"initGui()"," must call ",[18,1564,1565],{},"QgsExpression.unregisterFunction()"," in ",[18,1568,1569],{},"unload()",", or reloading the plugin raises a duplicate-name error. ",[26,1572,1574],{"href":1573},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fregister-custom-expression-function-pyqgis\u002F","Register a Custom Expression Function in PyQGIS"," covers that lifecycle in full, and ",[26,1577,1579],{"href":1578},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Freload-qgis-plugin-without-restart\u002F","Reload a QGIS Plugin Without Restarting"," explains why it bites during development.",[211,1582,1584],{"id":1583},"debugging-expressions-that-return-null","Debugging expressions that return NULL",[14,1586,1587,1588,1590],{},"A silent ",[18,1589,862],{}," is the most common expression complaint, and it almost always has one of four causes. Working through them in order resolves nearly every case.",[14,1592,1593,535,1596,1599,1600,1603,1604,1607],{},[222,1594,1595],{},"A field name is wrong or unquoted.",[18,1597,1598],{},"\"popluation\""," is a typo the parser cannot catch, because an unknown name is only detected at evaluation time against a specific feature. Compare ",[18,1601,1602],{},"expression.referencedColumns()"," against ",[18,1605,1606],{},"layer.fields().names()"," to catch it programmatically.",[14,1609,1610,535,1613,1616],{},[222,1611,1612],{},"A single quote was used where a double quote was meant.",[18,1614,1615],{},"'zoning' = 'residential'"," compares two literals and is always false. The parser accepts it happily.",[14,1618,1619,1622,1623,1625,1626,1628,1629,1632,1633,1635,1636,1639],{},[222,1620,1621],{},"NULL propagates."," In the expression engine, any arithmetic involving ",[18,1624,20],{}," yields ",[18,1627,20],{}," — ",[18,1630,1631],{},"\"a\" + \"b\""," is ",[18,1634,20],{}," if either is empty. Wrap the operands with ",[18,1637,1638],{},"coalesce(\"a\", 0)"," when an empty cell should behave as a zero.",[14,1641,1642,535,1645,310,1647,1650,1651,1654,1655,1657,1658,252],{},[222,1643,1644],{},"The geometry is missing.",[18,1646,280],{},[18,1648,1649],{},"$length",", and ",[18,1652,1653],{},"area($geometry)"," return ",[18,1656,20],{}," for a feature with no geometry, which is common after a failed join or an import from CSV. Guard with ",[18,1659,1660],{},"is_empty_or_null($geometry)",[14,1662,1663],{},"A compact diagnostic that reports all four at once:",[420,1665,1667],{"className":422,"code":1666,"language":424,"meta":425,"style":425},"from qgis.core import QgsExpression, QgsExpressionContext, QgsExpressionContextUtils\n\n\ndef diagnose(layer, expression_text):\n    expression = QgsExpression(expression_text)\n    if expression.hasParserError():\n        return f\"parser error: {expression.parserErrorString()}\"\n\n    unknown = set(expression.referencedColumns()) - set(layer.fields().names())\n    unknown.discard(QgsExpression.ALL_ATTRIBUTES)\n    if unknown:\n        return f\"unknown field(s): {', '.join(sorted(unknown))}\"\n\n    context = QgsExpressionContext()\n    context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\n    feature = next(layer.getFeatures(), None)\n    if feature is None:\n        return \"layer has no features to test against\"\n\n    context.setFeature(feature)\n    value = expression.evaluate(context)\n    if expression.hasEvalError():\n        return f\"eval error: {expression.evalErrorString()}\"\n    return f\"first feature evaluates to: {value!r}\"\n",[18,1668,1669,1679,1683,1687,1697,1707,1713,1734,1738,1759,1769,1776,1803,1807,1816,1821,1838,1853,1860,1864,1868,1876,1882,1899],{"__ignoreMap":425},[429,1670,1671,1673,1675,1677],{"class":189,"line":431},[429,1672,435],{"class":434},[429,1674,439],{"class":438},[429,1676,442],{"class":434},[429,1678,906],{"class":438},[429,1680,1681],{"class":189,"line":448},[429,1682,482],{"emptyLinePlaceholder":481},[429,1684,1685],{"class":189,"line":454},[429,1686,482],{"emptyLinePlaceholder":481},[429,1688,1689,1691,1694],{"class":189,"line":460},[429,1690,1485],{"class":434},[429,1692,1693],{"class":1449}," diagnose",[429,1695,1696],{"class":438},"(layer, expression_text):\n",[429,1698,1699,1702,1704],{"class":189,"line":466},[429,1700,1701],{"class":438},"    expression ",[429,1703,491],{"class":434},[429,1705,1706],{"class":438}," QgsExpression(expression_text)\n",[429,1708,1709,1711],{"class":189,"line":472},[429,1710,717],{"class":434},[429,1712,632],{"class":438},[429,1714,1715,1718,1721,1724,1726,1729,1731],{"class":189,"line":478},[429,1716,1717],{"class":434},"        return",[429,1719,1720],{"class":434}," f",[429,1722,1723],{"class":497},"\"parser error: ",[429,1725,738],{"class":504},[429,1727,1728],{"class":438},"expression.parserErrorString()",[429,1730,744],{"class":504},[429,1732,1733],{"class":497},"\"\n",[429,1735,1736],{"class":189,"line":485},[429,1737,482],{"emptyLinePlaceholder":481},[429,1739,1740,1743,1745,1748,1751,1754,1756],{"class":189,"line":510},[429,1741,1742],{"class":438},"    unknown ",[429,1744,491],{"class":434},[429,1746,1747],{"class":504}," set",[429,1749,1750],{"class":438},"(expression.referencedColumns()) ",[429,1752,1753],{"class":434},"-",[429,1755,1747],{"class":504},[429,1757,1758],{"class":438},"(layer.fields().names())\n",[429,1760,1761,1764,1767],{"class":189,"line":515},[429,1762,1763],{"class":438},"    unknown.discard(QgsExpression.",[429,1765,1766],{"class":504},"ALL_ATTRIBUTES",[429,1768,475],{"class":438},[429,1770,1771,1773],{"class":189,"line":526},[429,1772,717],{"class":434},[429,1774,1775],{"class":438}," unknown:\n",[429,1777,1778,1780,1782,1785,1787,1790,1793,1796,1799,1801],{"class":189,"line":635},[429,1779,1717],{"class":434},[429,1781,1720],{"class":434},[429,1783,1784],{"class":497},"\"unknown field(s): ",[429,1786,738],{"class":504},[429,1788,1789],{"class":497},"', '",[429,1791,1792],{"class":438},".join(",[429,1794,1795],{"class":504},"sorted",[429,1797,1798],{"class":438},"(unknown))",[429,1800,744],{"class":504},[429,1802,1733],{"class":497},[429,1804,1805],{"class":189,"line":647},[429,1806,482],{"emptyLinePlaceholder":481},[429,1808,1809,1812,1814],{"class":189,"line":652},[429,1810,1811],{"class":438},"    context ",[429,1813,491],{"class":434},[429,1815,523],{"class":438},[429,1817,1818],{"class":189,"line":661},[429,1819,1820],{"class":438},"    context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))\n",[429,1822,1823,1826,1828,1831,1834,1836],{"class":189,"line":666},[429,1824,1825],{"class":438},"    feature ",[429,1827,491],{"class":434},[429,1829,1830],{"class":504}," next",[429,1832,1833],{"class":438},"(layer.getFeatures(), ",[429,1835,862],{"class":504},[429,1837,475],{"class":438},[429,1839,1840,1842,1844,1847,1850],{"class":189,"line":671},[429,1841,717],{"class":434},[429,1843,688],{"class":438},[429,1845,1846],{"class":434},"is",[429,1848,1849],{"class":504}," None",[429,1851,1852],{"class":438},":\n",[429,1854,1855,1857],{"class":189,"line":677},[429,1856,1717],{"class":434},[429,1858,1859],{"class":497}," \"layer has no features to test against\"\n",[429,1861,1862],{"class":189,"line":682},[429,1863,482],{"emptyLinePlaceholder":481},[429,1865,1866],{"class":189,"line":697},[429,1867,700],{"class":438},[429,1869,1870,1872,1874],{"class":189,"line":703},[429,1871,706],{"class":438},[429,1873,491],{"class":434},[429,1875,711],{"class":438},[429,1877,1878,1880],{"class":189,"line":714},[429,1879,717],{"class":434},[429,1881,720],{"class":438},[429,1883,1884,1886,1888,1891,1893,1895,1897],{"class":189,"line":723},[429,1885,1717],{"class":434},[429,1887,1720],{"class":434},[429,1889,1890],{"class":497},"\"eval error: ",[429,1892,738],{"class":504},[429,1894,752],{"class":438},[429,1896,744],{"class":504},[429,1898,1733],{"class":497},[429,1900,1901,1903,1905,1908,1910,1912,1915,1917],{"class":189,"line":762},[429,1902,1501],{"class":434},[429,1904,1720],{"class":434},[429,1906,1907],{"class":497},"\"first feature evaluates to: ",[429,1909,738],{"class":504},[429,1911,172],{"class":438},[429,1913,1914],{"class":434},"!r",[429,1916,744],{"class":504},[429,1918,1733],{"class":497},[14,1920,1921,535,1923,1926,1927,1929,1930,252],{},[222,1922,534],{},[18,1924,1925],{},"referencedColumns()"," returns the field names the expression mentions, so a set difference against the layer's real fields catches typos instantly. ",[18,1928,1766],{}," is a sentinel the engine returns when an expression could touch anything (for example when it calls a function with unknown column references) — discarding it prevents a false positive. Evaluating against the first feature turns an abstract \"returns NULL\" into a concrete, reproducible value. For the broader toolkit, see ",[26,1931,1933],{"href":1932},"\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002F","Debugging PyQGIS Scripts",[211,1935,1937],{"id":1936},"choosing-between-an-expression-and-a-python-loop","Choosing between an expression and a Python loop",[14,1939,1940],{},"Expressions are not always the right answer, and reaching for one reflexively can make a script harder to read than the loop it replaced. The decision comes down to where the work has to happen and how much state it needs.",[14,1942,1943],{},[44,1944,1946,1949,1952,1954,1961,1964,1967,1971,1977,1981,1984,1987,1990,1993,1996,2000,2003,2005,2009,2012,2014,2017,2020,2022,2024,2027,2032,2034,2038,2042,2044],{"viewBox":327,"role":47,"ariaLabel":1945,"xmlns":49},"Decision tree for choosing between a QGIS expression and a Python loop, branching on whether the logic runs inside QGIS, needs per-feature state, or calls external libraries",[51,1947,1948],{},"Expression or Python loop?",[55,1950,1951],{},"A decision tree starting from the task. If the logic must run inside a QGIS component such as a renderer, label or filter, use an expression. If it needs state carried across features or calls an external Python library, use a loop. If it is a per-feature calculation with no external dependency, an expression pushed to the provider is faster.",[59,1953],{"x":61,"y":61,"width":62,"height":337,"fill":64},[66,1955,1956],{},[69,1957,1959],{"id":1958,"viewBox":72,"refX":73,"refY":74,"markerWidth":75,"markerHeight":75,"orient":76},"exprPickArrow",[78,1960],{"d":80,"fill":81},[83,1962,1963],{"x":85,"y":86,"style":87,"fill":88,"textAnchor":89},"Where does the logic have to live?",[59,1965],{"x":1966,"y":350,"width":401,"height":350,"rx":73,"fill":120,"stroke":88,"style":100},"290",[83,1968,1970],{"x":85,"y":1969,"style":105,"fill":88,"textAnchor":89},"74","per-feature task",[59,1972],{"x":1973,"y":1974,"width":1975,"height":1976,"rx":73,"fill":120,"stroke":121,"style":100},"40","132","200","60",[83,1978,1980],{"x":157,"y":161,"style":1979,"fill":81,"textAnchor":89},"text-anchor:middle;font-size:12px;font-family:sans-serif","renders, labels or filters",[83,1982,1983],{"x":157,"y":1177,"style":1979,"fill":81,"textAnchor":89},"inside a QGIS component?",[59,1985],{"x":1986,"y":1974,"width":1975,"height":1976,"rx":73,"fill":120,"stroke":136,"style":100},"280",[83,1988,1989],{"x":85,"y":161,"style":1979,"fill":81,"textAnchor":89},"needs running totals or",[83,1991,1992],{"x":85,"y":1177,"style":1979,"fill":81,"textAnchor":89},"cross-feature state?",[59,1994],{"x":1995,"y":1974,"width":1975,"height":1976,"rx":73,"fill":120,"stroke":99,"style":100},"520",[83,1997,1999],{"x":1998,"y":161,"style":1979,"fill":81,"textAnchor":89},"620","calls numpy, requests or",[83,2001,2002],{"x":1998,"y":1177,"style":1979,"fill":81,"textAnchor":89},"another Python library?",[59,2004],{"x":1973,"y":385,"width":1975,"height":94,"rx":73,"fill":120,"stroke":167,"style":100},[83,2006,2008],{"x":157,"y":2007,"style":105,"fill":167,"textAnchor":89},"248","expression — required",[83,2010,2011],{"x":157,"y":392,"style":111,"fill":81,"textAnchor":89},"nothing else runs there",[59,2013],{"x":1986,"y":385,"width":1975,"height":94,"rx":73,"fill":120,"stroke":136,"style":100},[83,2015,2016],{"x":85,"y":2007,"style":105,"fill":136,"textAnchor":89},"Python loop",[83,2018,2019],{"x":85,"y":392,"style":111,"fill":81,"textAnchor":89},"the engine is stateless",[59,2021],{"x":1995,"y":385,"width":1975,"height":94,"rx":73,"fill":120,"stroke":136,"style":100},[83,2023,2016],{"x":1998,"y":2007,"style":105,"fill":136,"textAnchor":89},[83,2025,2026],{"x":1998,"y":392,"style":111,"fill":81,"textAnchor":89},"or a custom function",[189,2028],{"x1":2029,"y1":205,"x2":149,"y2":2030,"stroke":81,"style":2031},"340","128","stroke-width:2;marker-end:url(#exprPickArrow)",[189,2033],{"x1":85,"y1":205,"x2":85,"y2":2030,"stroke":81,"style":2031},[189,2035],{"x1":2036,"y1":205,"x2":2037,"y2":2030,"stroke":81,"style":2031},"420","610",[189,2039],{"x1":157,"y1":2040,"x2":157,"y2":2041,"stroke":167,"style":2031},"192","222",[189,2043],{"x1":85,"y1":2040,"x2":85,"y2":2041,"stroke":136,"style":2031},[189,2045],{"x1":1998,"y1":2040,"x2":1998,"y2":2041,"stroke":136,"style":2031},[2047,2048,2049,2065],"table",{},[2050,2051,2052],"thead",{},[2053,2054,2055,2059,2062],"tr",{},[2056,2057,2058],"th",{},"Situation",[2056,2060,2061],{},"Reach for",[2056,2063,2064],{},"Why",[2066,2067,2068,2080,2094,2110,2124],"tbody",{},[2053,2069,2070,2074,2077],{},[2071,2072,2073],"td",{},"Rule-based renderer, label rule, filter box",[2071,2075,2076],{},"Expression",[2071,2078,2079],{},"These components accept nothing else — the logic has to be a string",[2053,2081,2082,2085,2091],{},[2071,2083,2084],{},"Filtering a large layer before iterating",[2071,2086,2087,2088],{},"Expression via ",[18,2089,2090],{},"QgsFeatureRequest",[2071,2092,2093],{},"The predicate is translated to SQL and executed by the provider",[2053,2095,2096,2105,2107],{},[2071,2097,2098,2099,35,2102],{},"Running totals, ranking, comparing feature ",[292,2100,2101],{},"n",[292,2103,2104],{},"n-1",[2071,2106,2016],{},[2071,2108,2109],{},"The engine evaluates each feature independently and keeps no state",[2053,2111,2112,2115,2118],{},[2071,2113,2114],{},"Calling an external library or web service",[2071,2116,2117],{},"Python loop, or a custom function",[2071,2119,2120,2121,2123],{},"The expression library has no equivalent, though ",[18,2122,1419],{}," can bridge one in",[2053,2125,2126,2129,2132],{},[2071,2127,2128],{},"Simple derived column written back to the layer",[2071,2130,2131],{},"Either",[2071,2133,2134],{},"An expression in a field calculator call is shorter; a loop is easier to unit test",[14,2136,2137,2138,2142],{},"The middle row is the one worth internalising: the expression engine has no memory between features. Anything that needs to compare a feature to the one before it, accumulate a total, or assign a rank belongs in Python — see ",[26,2139,2141],{"href":2140},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fupdate-attribute-values-pyqgis\u002F","Update Attribute Values in Bulk with PyQGIS"," for the editing patterns that go with that.",[211,2144,2146],{"id":2145},"building-expression-strings-safely","Building expression strings safely",[14,2148,2149],{},"Most real scripts assemble expressions from values that came from somewhere else — a dialog field, a config file, a list of category names. Concatenating those into a string with an f-string works right up until a value contains an apostrophe, at which point the expression stops parsing and, in a plugin exposed to user input, becomes an injection vector into whatever the expression touches.",[14,2151,2152,2154],{},[18,2153,125],{}," provides quoting helpers for exactly this, and using them is both shorter and correct:",[420,2156,2158],{"className":422,"code":2157,"language":424,"meta":425,"style":425},"from qgis.core import QgsExpression\n\nfield = \"owner_name\"\nwanted = [\"O'Brien Holdings\", \"Ash & Co\"]\n\nclause = \" OR \".join(\n    f\"{QgsExpression.quotedColumnRef(field)} = {QgsExpression.quotedValue(name)}\"\n    for name in wanted\n)\nprint(clause)\n# \"owner_name\" = 'O''Brien Holdings' OR \"owner_name\" = 'Ash & Co'\n",[18,2159,2160,2171,2175,2185,2205,2209,2222,2248,2261,2265,2272],{"__ignoreMap":425},[429,2161,2162,2164,2166,2168],{"class":189,"line":431},[429,2163,435],{"class":434},[429,2165,439],{"class":438},[429,2167,442],{"class":434},[429,2169,2170],{"class":438}," QgsExpression\n",[429,2172,2173],{"class":189,"line":448},[429,2174,482],{"emptyLinePlaceholder":481},[429,2176,2177,2180,2182],{"class":189,"line":454},[429,2178,2179],{"class":438},"field ",[429,2181,491],{"class":434},[429,2183,2184],{"class":497}," \"owner_name\"\n",[429,2186,2187,2190,2192,2195,2198,2200,2203],{"class":189,"line":460},[429,2188,2189],{"class":438},"wanted ",[429,2191,491],{"class":434},[429,2193,2194],{"class":438}," [",[429,2196,2197],{"class":497},"\"O'Brien Holdings\"",[429,2199,310],{"class":438},[429,2201,2202],{"class":497},"\"Ash & Co\"",[429,2204,507],{"class":438},[429,2206,2207],{"class":189,"line":466},[429,2208,482],{"emptyLinePlaceholder":481},[429,2210,2211,2214,2216,2219],{"class":189,"line":472},[429,2212,2213],{"class":438},"clause ",[429,2215,491],{"class":434},[429,2217,2218],{"class":497}," \" OR \"",[429,2220,2221],{"class":438},".join(\n",[429,2223,2224,2227,2229,2231,2234,2236,2239,2241,2244,2246],{"class":189,"line":478},[429,2225,2226],{"class":434},"    f",[429,2228,757],{"class":497},[429,2230,738],{"class":504},[429,2232,2233],{"class":438},"QgsExpression.quotedColumnRef(field)",[429,2235,744],{"class":504},[429,2237,2238],{"class":497}," = ",[429,2240,738],{"class":504},[429,2242,2243],{"class":438},"QgsExpression.quotedValue(name)",[429,2245,744],{"class":504},[429,2247,1733],{"class":497},[429,2249,2250,2253,2256,2258],{"class":189,"line":485},[429,2251,2252],{"class":434},"    for",[429,2254,2255],{"class":438}," name ",[429,2257,691],{"class":434},[429,2259,2260],{"class":438}," wanted\n",[429,2262,2263],{"class":189,"line":510},[429,2264,475],{"class":438},[429,2266,2267,2269],{"class":189,"line":515},[429,2268,952],{"class":504},[429,2270,2271],{"class":438},"(clause)\n",[429,2273,2274],{"class":189,"line":526},[429,2275,2277],{"class":2276},"sjoCn","# \"owner_name\" = 'O''Brien Holdings' OR \"owner_name\" = 'Ash & Co'\n",[14,2279,2280,535,2282,2285,2286,2289,2290,2293,2294,2296,2297,2299,2300,2303],{},[222,2281,534],{},[18,2283,2284],{},"quotedColumnRef()"," wraps a name in double quotes and escapes any embedded ones, producing a valid field reference even for a column called ",[18,2287,2288],{},"my \"odd\" name",". ",[18,2291,2292],{},"quotedValue()"," does the same for a literal, doubling internal apostrophes the way the expression grammar requires and rendering ",[18,2295,862],{}," as the bare keyword ",[18,2298,20],{},". Building the clause with a generator and ",[18,2301,2302],{},"join()"," keeps it readable regardless of how many values arrive.",[14,2305,2306,2307,2310,2311,2314],{},"For an ",[18,2308,2309],{},"IN"," list the same helper applies, and the resulting expression is usually faster than a chain of ",[18,2312,2313],{},"OR","s because the provider can translate it into a single SQL predicate:",[420,2316,2318],{"className":422,"code":2317,"language":424,"meta":425,"style":425},"values = \", \".join(QgsExpression.quotedValue(name) for name in wanted)\nexpression = f'{QgsExpression.quotedColumnRef(field)} IN ({values})'\n",[18,2319,2320,2342],{"__ignoreMap":425},[429,2321,2322,2325,2327,2330,2333,2335,2337,2339],{"class":189,"line":431},[429,2323,2324],{"class":438},"values ",[429,2326,491],{"class":434},[429,2328,2329],{"class":497}," \", \"",[429,2331,2332],{"class":438},".join(QgsExpression.quotedValue(name) ",[429,2334,685],{"class":434},[429,2336,2255],{"class":438},[429,2338,691],{"class":434},[429,2340,2341],{"class":438}," wanted)\n",[429,2343,2344,2346,2348,2350,2353,2355,2357,2359,2362,2364,2367,2369],{"class":189,"line":448},[429,2345,614],{"class":438},[429,2347,491],{"class":434},[429,2349,1720],{"class":434},[429,2351,2352],{"class":497},"'",[429,2354,738],{"class":504},[429,2356,2233],{"class":438},[429,2358,744],{"class":504},[429,2360,2361],{"class":497}," IN (",[429,2363,738],{"class":504},[429,2365,2366],{"class":438},"values",[429,2368,744],{"class":504},[429,2370,2371],{"class":497},")'\n",[14,2373,2374,2376,2377,2379,2380,2382,2383,252],{},[222,2375,534],{}," One ",[18,2378,2309],{}," clause replaces ",[292,2381,2101],{}," comparisons, and PostGIS in particular optimises it well. The rule of thumb is simple: never format a user-supplied value into an expression yourself — always route it through ",[18,2384,2292],{},[211,2386,2388],{"id":2387},"key-takeaways","Key takeaways",[216,2390,2391,2400,2412,2427,2438,2450],{},[219,2392,2393,2396,2397,2399],{},[222,2394,2395],{},"Expressions are their own language, not Python."," Double quotes reference fields, single quotes are string literals, and ",[18,2398,20],{}," propagates through arithmetic rather than raising.",[219,2401,2402,535,2405,2408,2409,2411],{},[222,2403,2404],{},"A context is a stack of scopes.",[18,2406,2407],{},"QgsExpressionContextUtils.globalProjectLayerScopes()"," builds the standard three; add the feature scope yourself with ",[18,2410,813],{}," inside the loop.",[219,2413,2414,2417,2418,2420,2421,2423,2424,2426],{},[222,2415,2416],{},"Parse once, evaluate many."," Construct ",[18,2419,125],{}," and call ",[18,2422,809],{}," outside the loop, then check ",[18,2425,817],{}," after each evaluation — parser errors and evaluation errors are separate conditions.",[219,2428,2429,535,2432,2434,2435,2437],{},[222,2430,2431],{},"Push filters into the provider.",[18,2433,990],{}," lets a database do the work; combining it with ",[18,2436,1120],{}," avoids fetching columns you will not read.",[219,2439,2440,535,2443,2445,2446,2449],{},[222,2441,2442],{},"Data-defined properties turn styling into a function of the data.",[18,2444,1379],{}," bound to a ",[18,2447,2448],{},"QgsSymbolLayer"," property re-evaluates per feature at render time and stores nothing on the layer.",[219,2451,2452,2455,2456,2458,2459,2461],{},[222,2453,2454],{},"Custom functions must be unregistered."," A plugin that registers with ",[18,2457,1419],{}," has to call ",[18,2460,1565],{}," on unload or a reload will fail.",[211,2463,2465],{"id":2464},"frequently-asked-questions","Frequently Asked Questions",[14,2467,2468,2471,2472,2474,2475,2477,2478,2480,2481,2484],{},[222,2469,2470],{},"Why does my expression return NULL instead of a number?","\nThe four usual causes are a misspelled field name, single quotes where double quotes were meant, ",[18,2473,20],{}," propagating through arithmetic, and a missing geometry. Compare ",[18,2476,1602],{}," with ",[18,2479,1606],{}," to catch the first two, and wrap operands in ",[18,2482,2483],{},"coalesce()"," to neutralise the third.",[14,2486,2487,2490,2491,2493,2494,2497],{},[222,2488,2489],{},"What is the difference between single and double quotes in a QGIS expression?","\nDouble quotes reference a field — ",[18,2492,276],{}," reads that column. Single quotes create a string literal — ",[18,2495,2496],{},"'population'"," is the eleven-character word. Because both are syntactically valid, mixing them up produces a comparison that is silently always false rather than an error.",[14,2499,2500,2503,2504,2507,2508,2510],{},[222,2501,2502],{},"Do I need a QgsExpressionContext to evaluate an expression?","\nOnly if the expression references fields, variables, or the geometry. A self-contained expression such as ",[18,2505,2506],{},"2 + 2"," evaluates without one, but anything touching ",[18,2509,305],{}," or a column needs the matching scope pushed onto the context first.",[14,2512,2513,2516],{},[222,2514,2515],{},"Is filtering with setFilterExpression faster than looping in Python?","\nSubstantially, on any provider that can translate the predicate. For PostGIS and GeoPackage layers the filter becomes SQL executed by the database, so only matching rows are transferred. A Python loop always fetches every feature first.",[14,2518,2519,2522,2523,2525],{},[222,2520,2521],{},"Can I use a custom Python function inside a data-defined property?","\nYes. Once registered with ",[18,2524,1419],{},", the function is part of the expression engine and available anywhere an expression is accepted, including data-defined overrides and layout item text. Make sure the plugin registering it also unregisters it on unload.",[211,2527,2529],{"id":2528},"related","Related",[216,2531,2532,2537,2542,2546,2550,2554,2558,2563,2567,2573],{},[219,2533,2534,2536],{},[26,2535,29],{"href":28}," — the guide this page belongs to",[219,2538,2539],{},[26,2540,2541],{"href":242},"QGIS API Architecture",[219,2543,2544],{},[26,2545,232],{"href":231},[219,2547,2548],{},[26,2549,1933],{"href":1932},[219,2551,2552],{},[26,2553,867],{"href":866},[219,2555,2556],{},[26,2557,1135],{"href":1134},[219,2559,2560],{},[26,2561,2562],{"href":1407},"Data-Defined Symbol Size with an Expression in PyQGIS",[219,2564,2565],{},[26,2566,1574],{"href":1573},[219,2568,2569],{},[26,2570,2572],{"href":2571},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002F","Attribute Tables and Field Management in PyQGIS",[219,2574,2575],{},[26,2576,2577],{"href":33},"Programmatic Layer Styling in PyQGIS",[2579,2580,2581],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":425,"searchDepth":448,"depth":448,"links":2583},[2584,2585,2586,2589,2590,2591,2592,2593,2594,2595,2596,2597],{"id":213,"depth":448,"text":214},{"id":265,"depth":448,"text":266},{"id":546,"depth":448,"text":547,"children":2587},[2588],{"id":872,"depth":454,"text":873},{"id":983,"depth":448,"text":984},{"id":1142,"depth":448,"text":1143},{"id":1412,"depth":448,"text":1413},{"id":1583,"depth":448,"text":1584},{"id":1936,"depth":448,"text":1937},{"id":2145,"depth":448,"text":2146},{"id":2387,"depth":448,"text":2388},{"id":2464,"depth":448,"text":2465},{"id":2528,"depth":448,"text":2529},"Evaluate, filter and debug QGIS expressions from Python. Build a QgsExpressionContext, drive data-defined properties, and register custom expression functions.","md",{"slug":2601,"type":2602,"breadcrumb":2603,"datePublished":2604,"dateModified":2604},"working-with-qgis-expressions","guide","QGIS Expressions","2026-08-01","\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions",{"title":5,"description":2598},"pyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Findex","5do7TSi43QH_N_vhnbkQr6ogjsy_S552pyNQ-B7Brpo",1785585990473]