[{"data":1,"prerenderedAt":1389},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Fuse-memory-layers-between-algorithms-pyqgis":3},{"id":4,"title":5,"body":6,"description":1378,"extension":1379,"meta":1380,"navigation":249,"path":1385,"seo":1386,"stem":1387,"__hash__":1388},"docs\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Fuse-memory-layers-between-algorithms-pyqgis\u002Findex.md","Use Memory Layers Between Algorithms in PyQGIS",{"type":7,"value":8,"toc":1365},"minimark",[9,13,17,26,187,192,219,223,367,385,389,557,562,566,569,740,761,838,842,845,851,857,863,870,960,964,967,1105,1130,1134,1201,1211,1215,1264,1268,1277,1281,1287,1296,1306,1312,1318,1328,1332,1361],[10,11,5],"h1",{"id":12},"use-memory-layers-between-algorithms-in-pyqgis",[14,15,16],"p",{},"A chain of four algorithms that writes four shapefiles produces three files nobody wants, in a folder somebody has to clean up, at the cost of three full round trips through the disk. Processing has a better answer built in: an output that lives in memory, is passed straight to the next step, and disappears when the script ends.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002F","Chaining Processing Algorithms",". It covers temporary outputs, passing results between steps, when memory is the wrong choice, keeping intermediate layers alive long enough to use them, and constructing a memory layer directly when no algorithm produced it.",[14,27,28],{},[29,30,35,39,43,50,67,76,84,92,98,101,105,108,112,118,122,129,132,136,141,146,151,155,157,160,162,165,168,171,174,176,178,182],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 268","img","Comparison of a chain writing every intermediate result to disk against one keeping intermediates in memory and writing only the final output","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Four steps, one file worth keeping",[40,41,42],"desc",{},"Writing every step to disk produces three intermediate files that must be named, stored and cleaned up, and each step pays the cost of writing and re-reading. Keeping intermediates in memory passes each result directly to the next algorithm, so only the final output reaches the disk and nothing needs cleaning up.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","268","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"memArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Only the last result is worth a file name",[68,77,83],{"x":78,"y":79,"style":80,"fill":81,"textAnchor":82},"60","80","text-anchor:end;font-size:11px;font-weight:bold;font-family:sans-serif","#b45309","end","to disk",[44,85],{"x":86,"y":78,"width":87,"height":88,"rx":89,"fill":90,"stroke":81,"style":91},"76","140","42","6","#fdf2e2","stroke-width:2",[68,93,97],{"x":94,"y":95,"style":96,"fill":66,"textAnchor":74},"146","86","text-anchor:middle;font-size:11px;font-family:sans-serif","buffer.shp",[44,99],{"x":100,"y":78,"width":87,"height":88,"rx":89,"fill":90,"stroke":81,"style":91},"240",[68,102,104],{"x":103,"y":95,"style":96,"fill":66,"textAnchor":74},"310","clipped.shp",[44,106],{"x":107,"y":78,"width":87,"height":88,"rx":89,"fill":90,"stroke":81,"style":91},"404",[68,109,111],{"x":110,"y":95,"style":96,"fill":66,"textAnchor":74},"474","dissolved.shp",[44,113],{"x":114,"y":78,"width":115,"height":88,"rx":89,"fill":116,"stroke":117,"style":91},"568","164","#edf8e9","#15803d",[68,119,121],{"x":120,"y":95,"style":96,"fill":66,"textAnchor":74},"650","result.gpkg",[123,124],"line",{"x1":125,"y1":126,"x2":127,"y2":126,"stroke":66,"style":128},"216","81","234","stroke-width:2;marker-end:url(#memArrow)",[123,130],{"x1":70,"y1":126,"x2":131,"y2":126,"stroke":66,"style":128},"398",[123,133],{"x1":134,"y1":126,"x2":135,"y2":126,"stroke":66,"style":128},"544","562",[68,137,140],{"x":70,"y":138,"style":139,"fill":81,"textAnchor":74},"124","text-anchor:middle;font-size:10px;font-family:sans-serif","three files to name, store and delete — and six disk traversals",[68,142,145],{"x":78,"y":143,"style":80,"fill":144,"textAnchor":82},"180","#0f766e","in memory",[44,147],{"x":86,"y":148,"width":87,"height":88,"rx":89,"fill":149,"stroke":144,"style":150},"160","#eef7f4","stroke-width:2;stroke-dasharray:5 3",[68,152,154],{"x":94,"y":153,"style":96,"fill":66,"textAnchor":74},"186","buffered",[44,156],{"x":100,"y":148,"width":87,"height":88,"rx":89,"fill":149,"stroke":144,"style":150},[68,158,159],{"x":103,"y":153,"style":96,"fill":66,"textAnchor":74},"clipped",[44,161],{"x":107,"y":148,"width":87,"height":88,"rx":89,"fill":149,"stroke":144,"style":150},[68,163,164],{"x":110,"y":153,"style":96,"fill":66,"textAnchor":74},"dissolved",[44,166],{"x":114,"y":148,"width":115,"height":88,"rx":89,"fill":116,"stroke":117,"style":167},"stroke-width:2.5",[68,169,121],{"x":120,"y":153,"style":170,"fill":117,"textAnchor":74},"text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif",[123,172],{"x1":125,"y1":173,"x2":127,"y2":173,"stroke":144,"style":128},"181",[123,175],{"x1":70,"y1":173,"x2":131,"y2":173,"stroke":144,"style":128},[123,177],{"x1":134,"y1":173,"x2":135,"y2":173,"stroke":144,"style":128},[68,179,181],{"x":70,"y":180,"style":139,"fill":144,"textAnchor":74},"224","nothing to clean up, and the intermediates never touch the disk",[68,183,186],{"x":70,"y":184,"style":96,"fill":185,"textAnchor":74},"254","#59645f","Until an intermediate no longer fits in memory — then the first row is right",[188,189,191],"h2",{"id":190},"prerequisites","Prerequisites",[193,194,195,203,211],"ul",{},[196,197,198,202],"li",{},[199,200,201],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[196,204,205,206,210],{},"The ",[207,208,209],"code",{},"processing"," module available — in the console it already is; in a standalone script it needs initialising.",[196,212,213,214,218],{},"A chain worth chaining; the single-algorithm case is in ",[21,215,217],{"href":216},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-processing-algorithm-from-script\u002F","Run a Processing Algorithm from a Script",".",[188,220,222],{"id":221},"ask-for-a-temporary-output","Ask for a temporary output",[224,225,230],"pre",{"className":226,"code":227,"language":228,"meta":229,"style":229},"language-python shiki shiki-themes github-dark","import processing\n\nbuffered = processing.run(\"native:buffer\", {\n    \"INPUT\": \"\u002Fdata\u002Fcity.gpkg|layername=roads\",\n    \"DISTANCE\": 25,\n    \"SEGMENTS\": 8,\n    \"DISSOLVE\": False,\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nprint(buffered)          # a layer id or a memory URI, depending on the algorithm\n","python","",[207,231,232,244,251,270,285,299,311,324,337,349,354],{"__ignoreMap":229},[233,234,236,240],"span",{"class":123,"line":235},1,[233,237,239],{"class":238},"snl16","import",[233,241,243],{"class":242},"s95oV"," processing\n",[233,245,247],{"class":123,"line":246},2,[233,248,250],{"emptyLinePlaceholder":249},true,"\n",[233,252,254,257,260,263,267],{"class":123,"line":253},3,[233,255,256],{"class":242},"buffered ",[233,258,259],{"class":238},"=",[233,261,262],{"class":242}," processing.run(",[233,264,266],{"class":265},"sU2Wk","\"native:buffer\"",[233,268,269],{"class":242},", {\n",[233,271,273,276,279,282],{"class":123,"line":272},4,[233,274,275],{"class":265},"    \"INPUT\"",[233,277,278],{"class":242},": ",[233,280,281],{"class":265},"\"\u002Fdata\u002Fcity.gpkg|layername=roads\"",[233,283,284],{"class":242},",\n",[233,286,288,291,293,297],{"class":123,"line":287},5,[233,289,290],{"class":265},"    \"DISTANCE\"",[233,292,278],{"class":242},[233,294,296],{"class":295},"sDLfK","25",[233,298,284],{"class":242},[233,300,302,305,307,309],{"class":123,"line":301},6,[233,303,304],{"class":265},"    \"SEGMENTS\"",[233,306,278],{"class":242},[233,308,58],{"class":295},[233,310,284],{"class":242},[233,312,314,317,319,322],{"class":123,"line":313},7,[233,315,316],{"class":265},"    \"DISSOLVE\"",[233,318,278],{"class":242},[233,320,321],{"class":295},"False",[233,323,284],{"class":242},[233,325,327,330,332,335],{"class":123,"line":326},8,[233,328,329],{"class":265},"    \"OUTPUT\"",[233,331,278],{"class":242},[233,333,334],{"class":265},"\"TEMPORARY_OUTPUT\"",[233,336,284],{"class":242},[233,338,340,343,346],{"class":123,"line":339},9,[233,341,342],{"class":242},"})[",[233,344,345],{"class":265},"\"OUTPUT\"",[233,347,348],{"class":242},"]\n",[233,350,352],{"class":123,"line":351},10,[233,353,250],{"emptyLinePlaceholder":249},[233,355,357,360,363],{"class":123,"line":356},11,[233,358,359],{"class":295},"print",[233,361,362],{"class":242},"(buffered)          ",[233,364,366],{"class":365},"sjoCn","# a layer id or a memory URI, depending on the algorithm\n",[14,368,369,372,373,376,377,380,381,384],{},[199,370,371],{},"Breakdown:"," ",[207,374,375],{},"TEMPORARY_OUTPUT"," tells the sink to create a temporary destination rather than a file, and for most vector algorithms that means a memory layer. The returned dictionary is keyed by the algorithm's output names, so ",[207,378,379],{},"[\"OUTPUT\"]"," extracts the one you want; what it contains is either a layer object or a string identifying one, and the useful property is that it can be passed straight back into the next algorithm either way. Omitting ",[207,382,383],{},"OUTPUT"," entirely has the same effect for many algorithms, but naming it explicitly documents the intent and avoids surprises with algorithms whose default is a file.",[188,386,388],{"id":387},"chain-the-steps","Chain the steps",[224,390,392],{"className":226,"code":391,"language":228,"meta":229,"style":229},"clipped = processing.run(\"native:clip\", {\n    \"INPUT\": buffered,\n    \"OVERLAY\": \"\u002Fdata\u002Fcity.gpkg|layername=boundary\",\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\ndissolved = processing.run(\"native:dissolve\", {\n    \"INPUT\": clipped,\n    \"FIELD\": [\"road_class\"],\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nprocessing.run(\"native:reprojectlayer\", {\n    \"INPUT\": dissolved,\n    \"TARGET_CRS\": \"EPSG:4326\",\n    \"OUTPUT\": \"\u002Fdata\u002Foutputs\u002Froad_buffers.gpkg\",\n})\n",[207,393,394,408,415,427,437,445,449,463,470,484,494,502,507,518,526,539,551],{"__ignoreMap":229},[233,395,396,399,401,403,406],{"class":123,"line":235},[233,397,398],{"class":242},"clipped ",[233,400,259],{"class":238},[233,402,262],{"class":242},[233,404,405],{"class":265},"\"native:clip\"",[233,407,269],{"class":242},[233,409,410,412],{"class":123,"line":246},[233,411,275],{"class":265},[233,413,414],{"class":242},": buffered,\n",[233,416,417,420,422,425],{"class":123,"line":253},[233,418,419],{"class":265},"    \"OVERLAY\"",[233,421,278],{"class":242},[233,423,424],{"class":265},"\"\u002Fdata\u002Fcity.gpkg|layername=boundary\"",[233,426,284],{"class":242},[233,428,429,431,433,435],{"class":123,"line":272},[233,430,329],{"class":265},[233,432,278],{"class":242},[233,434,334],{"class":265},[233,436,284],{"class":242},[233,438,439,441,443],{"class":123,"line":287},[233,440,342],{"class":242},[233,442,345],{"class":265},[233,444,348],{"class":242},[233,446,447],{"class":123,"line":301},[233,448,250],{"emptyLinePlaceholder":249},[233,450,451,454,456,458,461],{"class":123,"line":313},[233,452,453],{"class":242},"dissolved ",[233,455,259],{"class":238},[233,457,262],{"class":242},[233,459,460],{"class":265},"\"native:dissolve\"",[233,462,269],{"class":242},[233,464,465,467],{"class":123,"line":326},[233,466,275],{"class":265},[233,468,469],{"class":242},": clipped,\n",[233,471,472,475,478,481],{"class":123,"line":339},[233,473,474],{"class":265},"    \"FIELD\"",[233,476,477],{"class":242},": [",[233,479,480],{"class":265},"\"road_class\"",[233,482,483],{"class":242},"],\n",[233,485,486,488,490,492],{"class":123,"line":351},[233,487,329],{"class":265},[233,489,278],{"class":242},[233,491,334],{"class":265},[233,493,284],{"class":242},[233,495,496,498,500],{"class":123,"line":356},[233,497,342],{"class":242},[233,499,345],{"class":265},[233,501,348],{"class":242},[233,503,505],{"class":123,"line":504},12,[233,506,250],{"emptyLinePlaceholder":249},[233,508,510,513,516],{"class":123,"line":509},13,[233,511,512],{"class":242},"processing.run(",[233,514,515],{"class":265},"\"native:reprojectlayer\"",[233,517,269],{"class":242},[233,519,521,523],{"class":123,"line":520},14,[233,522,275],{"class":265},[233,524,525],{"class":242},": dissolved,\n",[233,527,529,532,534,537],{"class":123,"line":528},15,[233,530,531],{"class":265},"    \"TARGET_CRS\"",[233,533,278],{"class":242},[233,535,536],{"class":265},"\"EPSG:4326\"",[233,538,284],{"class":242},[233,540,542,544,546,549],{"class":123,"line":541},16,[233,543,329],{"class":265},[233,545,278],{"class":242},[233,547,548],{"class":265},"\"\u002Fdata\u002Foutputs\u002Froad_buffers.gpkg\"",[233,550,284],{"class":242},[233,552,554],{"class":123,"line":553},17,[233,555,556],{"class":242},"})\n",[14,558,559,561],{},[199,560,371],{}," Each step takes the previous result as its input, and only the last one names a file. The chain reads top to bottom as the sequence of operations, which is most of its value — a script that writes and re-reads three shapefiles hides the same logic under file handling. Note the field list in the dissolve: several algorithms take lists where the interface shows a single choice, and passing a bare string there is a common cause of an unexpected result rather than an error.",[188,563,565],{"id":564},"keep-the-layers-alive","Keep the layers alive",[14,567,568],{},"The one real trap: a temporary layer can be garbage-collected while your script still intends to use it.",[224,570,572],{"className":226,"code":571,"language":228,"meta":229,"style":229},"from qgis.core import QgsProcessingContext, QgsProcessingFeedback\n\ncontext = QgsProcessingContext()\nfeedback = QgsProcessingFeedback()\n\nbuffered = processing.run(\"native:buffer\", {...},\n                          context=context, feedback=feedback, is_child_algorithm=True)[\"OUTPUT\"]\n\nclipped = processing.run(\"native:clip\", {\"INPUT\": buffered, ...},\n                         context=context, feedback=feedback, is_child_algorithm=True)[\"OUTPUT\"]\n\n# the context owns the temporary layers until it goes out of scope\nfinal = context.getMapLayer(clipped)\n",[207,573,574,587,591,601,611,615,634,668,672,694,721,725,730],{"__ignoreMap":229},[233,575,576,579,582,584],{"class":123,"line":235},[233,577,578],{"class":238},"from",[233,580,581],{"class":242}," qgis.core ",[233,583,239],{"class":238},[233,585,586],{"class":242}," QgsProcessingContext, QgsProcessingFeedback\n",[233,588,589],{"class":123,"line":246},[233,590,250],{"emptyLinePlaceholder":249},[233,592,593,596,598],{"class":123,"line":253},[233,594,595],{"class":242},"context ",[233,597,259],{"class":238},[233,599,600],{"class":242}," QgsProcessingContext()\n",[233,602,603,606,608],{"class":123,"line":272},[233,604,605],{"class":242},"feedback ",[233,607,259],{"class":238},[233,609,610],{"class":242}," QgsProcessingFeedback()\n",[233,612,613],{"class":123,"line":287},[233,614,250],{"emptyLinePlaceholder":249},[233,616,617,619,621,623,625,628,631],{"class":123,"line":301},[233,618,256],{"class":242},[233,620,259],{"class":238},[233,622,262],{"class":242},[233,624,266],{"class":265},[233,626,627],{"class":242},", {",[233,629,630],{"class":295},"...",[233,632,633],{"class":242},"},\n",[233,635,636,640,642,645,648,650,653,656,658,661,664,666],{"class":123,"line":313},[233,637,639],{"class":638},"s9osk","                          context",[233,641,259],{"class":238},[233,643,644],{"class":242},"context, ",[233,646,647],{"class":638},"feedback",[233,649,259],{"class":238},[233,651,652],{"class":242},"feedback, ",[233,654,655],{"class":638},"is_child_algorithm",[233,657,259],{"class":238},[233,659,660],{"class":295},"True",[233,662,663],{"class":242},")[",[233,665,345],{"class":265},[233,667,348],{"class":242},[233,669,670],{"class":123,"line":326},[233,671,250],{"emptyLinePlaceholder":249},[233,673,674,676,678,680,682,684,687,690,692],{"class":123,"line":339},[233,675,398],{"class":242},[233,677,259],{"class":238},[233,679,262],{"class":242},[233,681,405],{"class":265},[233,683,627],{"class":242},[233,685,686],{"class":265},"\"INPUT\"",[233,688,689],{"class":242},": buffered, ",[233,691,630],{"class":295},[233,693,633],{"class":242},[233,695,696,699,701,703,705,707,709,711,713,715,717,719],{"class":123,"line":351},[233,697,698],{"class":638},"                         context",[233,700,259],{"class":238},[233,702,644],{"class":242},[233,704,647],{"class":638},[233,706,259],{"class":238},[233,708,652],{"class":242},[233,710,655],{"class":638},[233,712,259],{"class":238},[233,714,660],{"class":295},[233,716,663],{"class":242},[233,718,345],{"class":265},[233,720,348],{"class":242},[233,722,723],{"class":123,"line":356},[233,724,250],{"emptyLinePlaceholder":249},[233,726,727],{"class":123,"line":504},[233,728,729],{"class":365},"# the context owns the temporary layers until it goes out of scope\n",[233,731,732,735,737],{"class":123,"line":509},[233,733,734],{"class":242},"final ",[233,736,259],{"class":238},[233,738,739],{"class":242}," context.getMapLayer(clipped)\n",[14,741,742,744,745,748,749,752,753,756,757,218],{},[199,743,371],{}," A shared ",[207,746,747],{},"QgsProcessingContext"," owns the temporary layers produced during the chain, which is what keeps them alive between calls — without one, a temporary layer can be released as soon as the local reference to it goes away, and the next algorithm reports that its input is invalid. ",[207,750,751],{},"is_child_algorithm=True"," says these are intermediate steps, so their outputs are not added to the project. ",[207,754,755],{},"context.getMapLayer()"," converts a returned layer id back into a real layer object at the end. In a plugin or a Processing algorithm you already have a context and should pass it down rather than creating one, which is the pattern in ",[21,758,760],{"href":759},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Freport-progress-and-cancel-processing-algorithm\u002F","Report Progress and Cancellation in a Processing Algorithm",[14,762,763],{},[29,764,767,770,773,775,782,785,792,797,801,806,809,813,816,818,821,824,827,830,834,836],{"viewBox":765,"role":32,"ariaLabel":766,"xmlns":34},"0 0 760 254","Diagram showing a shared processing context owning three temporary layers so each remains valid until the chain completes",[36,768,769],{},"What the context is for",[40,771,772],{},"Without a shared context, each temporary layer is owned only by the local variable holding it, and can be released before the next algorithm reads it. With a shared context passed to every call, the context owns each temporary layer for the whole chain, so every step's input remains valid until the context itself goes out of scope.",[44,774],{"x":46,"y":46,"width":47,"height":184,"fill":49},[51,776,777],{},[54,778,780],{"id":779,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"ctxArrow",[63,781],{"d":65,"fill":144},[68,783,784],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Somebody has to own an intermediate layer",[44,786],{"x":787,"y":788,"width":789,"height":790,"rx":791,"fill":149,"stroke":144,"style":167},"40","52","680","72","10",[68,793,796],{"x":70,"y":794,"style":795,"fill":144,"textAnchor":74},"78","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","one QgsProcessingContext, passed to every call",[68,798,800],{"x":70,"y":799,"style":96,"fill":66,"textAnchor":74},"102","holds every temporary layer the chain produces",[44,802],{"x":78,"y":803,"width":143,"height":804,"rx":58,"fill":805,"stroke":185,"style":91},"150","66","#fffdf7",[68,807,154],{"x":803,"y":808,"style":170,"fill":73,"textAnchor":74},"178",[68,810,812],{"x":803,"y":811,"style":96,"fill":66,"textAnchor":74},"198","valid through step 2",[44,814],{"x":815,"y":803,"width":143,"height":804,"rx":58,"fill":805,"stroke":185,"style":91},"290",[68,817,159],{"x":70,"y":808,"style":170,"fill":73,"textAnchor":74},[68,819,820],{"x":70,"y":811,"style":96,"fill":66,"textAnchor":74},"valid through step 3",[44,822],{"x":823,"y":803,"width":143,"height":804,"rx":58,"fill":805,"stroke":185,"style":91},"520",[68,825,164],{"x":826,"y":808,"style":170,"fill":73,"textAnchor":74},"610",[68,828,829],{"x":826,"y":811,"style":96,"fill":66,"textAnchor":74},"valid to the end",[123,831],{"x1":803,"y1":138,"x2":803,"y2":832,"stroke":144,"style":833},"144","stroke-width:2;marker-end:url(#ctxArrow)",[123,835],{"x1":70,"y1":138,"x2":70,"y2":832,"stroke":144,"style":833},[123,837],{"x1":826,"y1":138,"x2":826,"y2":832,"stroke":144,"style":833},[188,839,841],{"id":840},"when-memory-is-the-wrong-choice","When memory is the wrong choice",[14,843,844],{},"Temporary outputs are the default for a reason, and there are three situations where they are not what you want.",[14,846,847,850],{},[199,848,849],{},"The intermediate does not fit."," A memory layer holds every feature and geometry in RAM. A dissolve producing one enormous multipolygon, or a buffer over ten million points, will exhaust memory and take the process with it. Write those to a GeoPackage and let the operating system page it.",[14,852,853,856],{},[199,854,855],{},"You need to inspect the intermediate."," When a chain produces the wrong answer, the fastest diagnosis is looking at step two. Temporarily naming each output as a file — and loading them — turns a mystery into an obvious step.",[14,858,859,862],{},[199,860,861],{},"The step is expensive and repeated."," A costly intermediate that several later runs need should be computed once and written, not rebuilt on every run. That is a cache, and it belongs on disk with a note about when it was made.",[14,864,865,866,869],{},"A pragmatic compromise for development is a flag: run with temporary outputs normally, and with named files when a ",[207,867,868],{},"DEBUG"," switch is set. It costs a few lines and makes the chain inspectable exactly when you need it.",[14,871,872],{},[29,873,876,879,882,885,892,895,901,904,908,913,917,920,923,926,930,933,935,939,943,949,952,956],{"viewBox":874,"role":32,"ariaLabel":875,"xmlns":34},"0 0 760 246","Decision tree choosing between a temporary output and a file output based on size, whether the step needs inspecting, and whether it is reused",[36,877,878],{},"Temporary or file, decided per step",[40,880,881],{},"A step whose result fits comfortably in memory, is not being inspected, and is not reused elsewhere should be a temporary output. A result too large for memory, one you are currently debugging, or one that several later runs need should be written to a file instead, with the last case treated explicitly as a cache.",[44,883],{"x":46,"y":46,"width":47,"height":884,"fill":49},"246",[51,886,887],{},[54,888,890],{"id":889,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"memTreeArrow",[63,891],{"d":65,"fill":185},[68,893,894],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"Three questions, asked per step",[44,896],{"x":897,"y":898,"width":899,"height":900,"rx":58,"fill":805,"stroke":73,"style":167},"284","48","192","44",[68,902,903],{"x":70,"y":86,"style":170,"fill":73,"textAnchor":74},"does it fit in memory?",[44,905],{"x":906,"y":907,"width":125,"height":788,"rx":58,"fill":90,"stroke":81,"style":91},"20","120",[68,909,912],{"x":910,"y":911,"style":170,"fill":81,"textAnchor":74},"128","142","no — write a file",[68,914,916],{"x":910,"y":915,"style":96,"fill":66,"textAnchor":74},"162","let the disk page it",[44,918],{"x":897,"y":919,"width":899,"height":900,"rx":58,"fill":805,"stroke":73,"style":167},"112",[68,921,922],{"x":70,"y":87,"style":170,"fill":73,"textAnchor":74},"are you inspecting it?",[44,924],{"x":925,"y":907,"width":125,"height":788,"rx":58,"fill":90,"stroke":81,"style":91},"524",[68,927,929],{"x":928,"y":911,"style":170,"fill":81,"textAnchor":74},"632","yes — name it",[68,931,932],{"x":928,"y":915,"style":96,"fill":66,"textAnchor":74},"and load it to look",[44,934],{"x":897,"y":143,"width":899,"height":788,"rx":58,"fill":149,"stroke":144,"style":167},[68,936,938],{"x":70,"y":937,"style":170,"fill":144,"textAnchor":74},"202","TEMPORARY OUTPUT",[68,940,942],{"x":70,"y":941,"style":96,"fill":66,"textAnchor":74},"222","nothing to clean up",[123,944],{"x1":945,"y1":946,"x2":143,"y2":947,"stroke":185,"style":948},"300","92","114","stroke-width:2;marker-end:url(#memTreeArrow)",[123,950],{"x1":70,"y1":946,"x2":70,"y2":951,"stroke":185,"style":948},"106",[123,953],{"x1":954,"y1":87,"x2":955,"y2":87,"stroke":185,"style":948},"460","518",[123,957],{"x1":70,"y1":958,"x2":70,"y2":959,"stroke":185,"style":948},"156","174",[188,961,963],{"id":962},"build-a-memory-layer-by-hand","Build a memory layer by hand",[14,965,966],{},"Where no algorithm produced the data — features computed in Python, rows read from an API — construct the layer directly.",[224,968,970],{"className":226,"code":969,"language":228,"meta":229,"style":229},"from qgis.core import QgsVectorLayer, QgsFeature, QgsGeometry, QgsPointXY, QgsField\nfrom qgis.PyQt.QtCore import QVariant\n\nlayer = QgsVectorLayer(\"Point?crs=EPSG:27700&field=ref:string&field=value:double\",\n                       \"computed\", \"memory\")\nprovider = layer.dataProvider()\n\nfeatures = []\nfor ref, x, y, value in readings:\n    feature = QgsFeature(layer.fields())\n    feature.setAttributes([ref, value])\n    feature.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))\n    features.append(feature)\n\nprovider.addFeatures(features)\nlayer.updateExtents()\n",[207,971,972,983,995,999,1014,1028,1038,1042,1052,1066,1076,1081,1086,1091,1095,1100],{"__ignoreMap":229},[233,973,974,976,978,980],{"class":123,"line":235},[233,975,578],{"class":238},[233,977,581],{"class":242},[233,979,239],{"class":238},[233,981,982],{"class":242}," QgsVectorLayer, QgsFeature, QgsGeometry, QgsPointXY, QgsField\n",[233,984,985,987,990,992],{"class":123,"line":246},[233,986,578],{"class":238},[233,988,989],{"class":242}," qgis.PyQt.QtCore ",[233,991,239],{"class":238},[233,993,994],{"class":242}," QVariant\n",[233,996,997],{"class":123,"line":253},[233,998,250],{"emptyLinePlaceholder":249},[233,1000,1001,1004,1006,1009,1012],{"class":123,"line":272},[233,1002,1003],{"class":242},"layer ",[233,1005,259],{"class":238},[233,1007,1008],{"class":242}," QgsVectorLayer(",[233,1010,1011],{"class":265},"\"Point?crs=EPSG:27700&field=ref:string&field=value:double\"",[233,1013,284],{"class":242},[233,1015,1016,1019,1022,1025],{"class":123,"line":287},[233,1017,1018],{"class":265},"                       \"computed\"",[233,1020,1021],{"class":242},", ",[233,1023,1024],{"class":265},"\"memory\"",[233,1026,1027],{"class":242},")\n",[233,1029,1030,1033,1035],{"class":123,"line":301},[233,1031,1032],{"class":242},"provider ",[233,1034,259],{"class":238},[233,1036,1037],{"class":242}," layer.dataProvider()\n",[233,1039,1040],{"class":123,"line":313},[233,1041,250],{"emptyLinePlaceholder":249},[233,1043,1044,1047,1049],{"class":123,"line":326},[233,1045,1046],{"class":242},"features ",[233,1048,259],{"class":238},[233,1050,1051],{"class":242}," []\n",[233,1053,1054,1057,1060,1063],{"class":123,"line":339},[233,1055,1056],{"class":238},"for",[233,1058,1059],{"class":242}," ref, x, y, value ",[233,1061,1062],{"class":238},"in",[233,1064,1065],{"class":242}," readings:\n",[233,1067,1068,1071,1073],{"class":123,"line":351},[233,1069,1070],{"class":242},"    feature ",[233,1072,259],{"class":238},[233,1074,1075],{"class":242}," QgsFeature(layer.fields())\n",[233,1077,1078],{"class":123,"line":356},[233,1079,1080],{"class":242},"    feature.setAttributes([ref, value])\n",[233,1082,1083],{"class":123,"line":504},[233,1084,1085],{"class":242},"    feature.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))\n",[233,1087,1088],{"class":123,"line":509},[233,1089,1090],{"class":242},"    features.append(feature)\n",[233,1092,1093],{"class":123,"line":520},[233,1094,250],{"emptyLinePlaceholder":249},[233,1096,1097],{"class":123,"line":528},[233,1098,1099],{"class":242},"provider.addFeatures(features)\n",[233,1101,1102],{"class":123,"line":541},[233,1103,1104],{"class":242},"layer.updateExtents()\n",[14,1106,1107,1109,1110,1113,1114,1117,1118,1121,1122,1125,1126,1129],{},[199,1108,371],{}," The memory provider's URI declares geometry type, coordinate system and fields in one string, so no separate schema step is needed — and the CRS must be declared here, because a memory layer with no CRS silently participates in transformations as if it were in the project's. Constructing each feature from ",[207,1111,1112],{},"layer.fields()"," gives it the right attribute count; a bare ",[207,1115,1116],{},"QgsFeature()"," accepts attributes and drops them. Adding features in one ",[207,1119,1120],{},"addFeatures()"," call rather than one at a time is substantially faster on large sets. ",[207,1123,1124],{},"updateExtents()"," is needed before anything asks the layer for its extent, including most algorithms. This layer can now be passed to ",[207,1127,1128],{},"processing.run()"," exactly like any other input.",[188,1131,1133],{"id":1132},"qgis-version-compatibility","QGIS version compatibility",[1135,1136,1137,1153],"table",{},[1138,1139,1140],"thead",{},[1141,1142,1143,1147,1150],"tr",{},[1144,1145,1146],"th",{},"QGIS version",[1144,1148,1149],{},"Python",[1144,1151,1152],{},"Notes",[1154,1155,1156,1170,1180,1191],"tbody",{},[1141,1157,1158,1162,1165],{},[1159,1160,1161],"td",{},"3.22 LTR",[1159,1163,1164],{},"3.9",[1159,1166,1167,1169],{},[207,1168,375],{},", contexts and the memory provider all as described.",[1141,1171,1172,1175,1177],{},[1159,1173,1174],{},"3.28 LTR",[1159,1176,1164],{},[1159,1178,1179],{},"Identical.",[1141,1181,1182,1185,1188],{},[1159,1183,1184],{},"3.34 LTR",[1159,1186,1187],{},"3.12",[1159,1189,1190],{},"Baseline for this page.",[1141,1192,1193,1196,1198],{},[1159,1194,1195],{},"3.40 \u002F 3.44",[1159,1197,1187],{},[1159,1199,1200],{},"Identical; some algorithms now default to temporary outputs where they previously required a destination.",[14,1202,1203,1204,1207,1208,1210],{},"Older examples use ",[207,1205,1206],{},"\"memory:\""," as the output value, which still works and is equivalent to ",[207,1209,375],{}," for vector sinks. The named constant is clearer and applies to raster outputs too, where it produces a temporary file rather than a memory layer.",[188,1212,1214],{"id":1213},"troubleshooting","Troubleshooting",[193,1216,1217,1226,1232,1238,1244,1252],{},[196,1218,1219,1222,1223,1225],{},[199,1220,1221],{},"\"Input layer is not valid\" on the second step."," The intermediate was released. Share a ",[207,1224,747],{}," across the calls.",[196,1227,1228,1231],{},[199,1229,1230],{},"Memory use grows until the process dies."," An intermediate is too large for RAM. Write that step to a GeoPackage.",[196,1233,1234,1237],{},[199,1235,1236],{},"The result is empty and no error appears."," An earlier step produced nothing — often a clip with no overlap. Inspect the intermediates.",[196,1239,1240,1243],{},[199,1241,1242],{},"A field list parameter is ignored."," It was passed as a string instead of a list.",[196,1245,1246,372,1249,1251],{},[199,1247,1248],{},"Layers pile up in the project.",[207,1250,655],{}," was not set, so intermediate outputs were added. Set it for every step but the last.",[196,1253,1254,1257,1258,1260,1261,218],{},[199,1255,1256],{},"A memory layer's features have no attributes."," They were built with ",[207,1259,1116],{}," rather than ",[207,1262,1263],{},"QgsFeature(layer.fields())",[188,1265,1267],{"id":1266},"conclusion","Conclusion",[14,1269,1270,1271,1273,1274,1276],{},"Pass ",[207,1272,375],{}," for every step whose result you do not want to keep, feed each result straight into the next algorithm, and name a real file only for the final output. Share one ",[207,1275,747],{}," across the chain so intermediates stay alive, mark intermediate calls as child algorithms so they do not clutter the project, and fall back to files when a step is too large for memory or when you need to look at it.",[188,1278,1280],{"id":1279},"frequently-asked-questions","Frequently Asked Questions",[14,1282,1283,1286],{},[199,1284,1285],{},"Is a temporary output always a memory layer?","\nFor vector sinks, usually yes. Raster outputs go to a temporary file, since raster data in memory is rarely practical.",[14,1288,1289,1292,1293,1295],{},[199,1290,1291],{},"Do temporary layers appear in the project?","\nOnly if you add them, or if you run without ",[207,1294,655],{},". In the toolbox they appear as scratch layers marked as temporary.",[14,1297,1298,1301,1302,1305],{},[199,1299,1300],{},"Can I save a memory layer later?","\nYes — run it through an algorithm with a file output, or write it with ",[207,1303,1304],{},"QgsVectorFileWriter",". Nothing about a memory layer prevents it being saved.",[14,1307,1308,1311],{},[199,1309,1310],{},"How much memory does a memory layer use?","\nRoughly the size of the data plus overhead per feature. A few hundred thousand simple features is comfortable; complex geometry costs far more.",[14,1313,1314,1317],{},[199,1315,1316],{},"Are memory layers thread-safe?","\nTreat them as not. Build them on one thread and hand the finished layer over, rather than writing from a background task.",[14,1319,1320,1323,1324,218],{},[199,1321,1322],{},"Why does my chain work in the console and fail in a standalone script?","\nProcessing was not initialised, or nothing owns the temporary layers. Both are addressed in ",[21,1325,1327],{"href":1326},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Frunning-python-scripts-outside-qgis-desktop\u002F","Running Python Scripts Outside QGIS Desktop",[188,1329,1331],{"id":1330},"related","Related",[193,1333,1334,1339,1345,1349,1355],{},[196,1335,1336,1338],{},[21,1337,24],{"href":23}," — the guide this recipe belongs to",[196,1340,1341],{},[21,1342,1344],{"href":1343},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Fchain-buffer-and-clip-pyqgis\u002F","Chain Buffer and Clip in PyQGIS",[196,1346,1347],{},[21,1348,217],{"href":216},[196,1350,1351],{},[21,1352,1354],{"href":1353},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-algorithm-over-folder-of-files-pyqgis\u002F","Run an Algorithm Over a Folder of Files in PyQGIS",[196,1356,1357],{},[21,1358,1360],{"href":1359},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fwrite-vector-layer-to-geopackage-pyqgis\u002F","Write a Vector Layer to GeoPackage in PyQGIS",[1362,1363,1364],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":229,"searchDepth":246,"depth":246,"links":1366},[1367,1368,1369,1370,1371,1372,1373,1374,1375,1376,1377],{"id":190,"depth":246,"text":191},{"id":221,"depth":246,"text":222},{"id":387,"depth":246,"text":388},{"id":564,"depth":246,"text":565},{"id":840,"depth":246,"text":841},{"id":962,"depth":246,"text":963},{"id":1132,"depth":246,"text":1133},{"id":1213,"depth":246,"text":1214},{"id":1266,"depth":246,"text":1267},{"id":1279,"depth":246,"text":1280},{"id":1330,"depth":246,"text":1331},"Keep intermediate results in memory instead of on disk — the TEMPORARY_OUTPUT sink, passing outputs straight into the next algorithm, when memory is the wrong choice, and building a memory layer by hand.","md",{"slug":1381,"type":1382,"breadcrumb":1383,"datePublished":1384,"dateModified":1384},"use-memory-layers-between-algorithms-pyqgis","article","Memory Layers","2026-08-15","\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Fuse-memory-layers-between-algorithms-pyqgis",{"title":5,"description":1378},"spatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Fuse-memory-layers-between-algorithms-pyqgis\u002Findex","SELPR3LOps3uHkeRiqJq7tAc1TYcsYTvhQvXtf_OSeQ",1786789586031]