I load the following CSV data into QGIS 3 (specs below) as a table with no geometry using default options.


I open a python terminal to access the layer with table = QgsProject.instance().mapLayersByName("table")[0]

When I call getFeatures() I see that there are, correctly, 4 features

>>> list(table.getFeatures())
[<qgis._core.QgsFeature object at 0x00000215D6518948>, <qgis._core.QgsFeature object at 0x00000215D65189D8>, 
<qgis._core.QgsFeature object at 0x00000215D6518A68>, <qgis._core.QgsFeature object at 0x00000215D6518AF8>]

But when I call getFeature() I can access indices 0 through 5, suggesting that there are 6 features. The attributes of the first two features are unusual.

>>> table.getFeature(0).attributes()
[NULL, 'time', NULL]
>>> table.getFeature(1).attributes()
[NULL, 'time', NULL]
>>> table.getFeature(2).attributes()
[-49, '2021-12-28T00:12:17.725753Z', 13.5]
>>> table.getFeature(5).attributes()
[-52, '2021-12-28T00:09:18.078015Z', 14.2]

My assumption was that these two functions accessed the same data. But this suggests they don't and now I'm confused. What's going on, and how do I program defensively around it?

QGIS version
QGIS code revision
Compiled against Qt
Running against Qt
Compiled against GDAL/OGR
Running against GDAL/OGR
Compiled against GEOS
Running against GEOS
PostgreSQL Client Version
SpatiaLite Version
QWT Version
QScintilla2 Version
Compiled against PROJ
Running against PROJ
Rel. 5.2.0, September 15th, 2018
OS Version
Windows 10 (10.0)
  • 1
    When you loaded the CSV file (using the GUI presumably?) did you handle the header line? What did the preview table show? What does type(table.getFeature(0)) say? Jan 2 at 10:52
  • 1
    @bugmenot123 - the header line was handled (i.e. "first line contains headers") so the attribute table looks as it should. Calling type(table.getFeature(0)) returns <class 'qgis._core.QgsFeature'> Jan 2 at 18:14
  • 1
    It needs to be reviewed the source code. There may be a bug. Calling type(table.getFeature(10000)) returns a QgsFeature, too. Jan 2 at 21:21

You assume that there is a feature for ids 0 and 1, but table.getFeature(0 or 1).attributes() tells us that it is not true.

The following code prints existing feature ids.

ids = [f.id() for f in table.getFeatures()] # list comprehension
# OUT: [2, 3, 4, 5]

Then, continue using ids to be sure you use an existing ids.

for i in ids:
    attributes = table.getFeature(i).attributes()

If the table has millions of rows, use of the list comprehension may not be reasonable. Instead, use generator comprehension. Generator comprehensions use circular brackets () whereas list comprehensions use square brackets []. Generators don’t allocate memory for the whole list and are memory efficient.

ids = (f.id() for f in table.getFeatures()) # generator comprehension


  • if n header/skipped line, then ids start from header + skipped + 1. That is weird.

For example:

  • if no header and no skipped line, then ids start from 1
  • if 1 header and no skipped line, then ids start from 2
  • if 1 header and 2 skipped line, then ids start from 4

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.