3

When I run following code in QGIS 2.18:

layer = iface.activeLayer()

expression = QgsExpression( u'"area" > 2e8 AND "area" < 3e8' )

feats = [ feat for feat in layer.getFeatures()
              if expression.evaluate( feat )]

epsg = layer.crs().postgisSrid()

uri = "Polygon?crs=epsg:" + str(epsg) + "&field=id:integer""&index=yes"

mem_layer = QgsVectorLayer(uri, 
                           'mem_layer', 
                           'memory')

prov = mem_layer.dataProvider()

for i, feat in enumerate(feats):
    feat.setAttributes([i])
    mem_layer.addFeature(feat)

prov.addFeatures(feats)

QgsMapLayerRegistry.instance().addMapLayer(mem_layer)

I got the result of following image:

enter image description here

However, equivalent code in QGIS 3.x doesn't work.

layer = iface.activeLayer()

expression = QgsExpression( u'"area" > 2e8 AND "area" < 3e8' )

feats = [ feat for feat in layer.getFeatures()
              if expression.evaluate( feat )]

epsg = layer.crs().postgisSrid()

uri = "Polygon?crs=epsg:" + str(epsg) + "&field=id:integer""&index=yes"

mem_layer = QgsVectorLayer(uri, 
                           'mem_layer', 
                           'memory')

prov = mem_layer.dataProvider()

for i, feat in enumerate(feats):
    feat.setAttributes([i])
    mem_layer.addFeature(feat)

prov.addFeatures(feats)

QgsProject.instance().addMapLayer(mem_layer)

Error message is:

Traceback (most recent call last):
  File "C:\PROGRA~1\QGIS3~1.0\apps\Python36\lib\code.py", line 91, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>
  File "<string>", line 5, in <module>
  File "<string>", line 6, in <listcomp>
TypeError: QgsExpression.evaluate(): arguments did not match any overloaded call:
  overload 1: too many arguments
  overload 2: argument 1 has unexpected type 'QgsFeature'

where it is said that overload 2: argument 1 has unexpected type 'QgsFeature'.

So, I did a 'help(QgsExpression.evaluate)' request at Python Console:

help(QgsExpression.evaluate)
Help on built-in function evaluate:

evaluate(...)
    Evaluate the feature and return the result.

    .. note::

       this method does not expect that prepare() has been called on this instance

    .. versionadded:: 2.12
    Evaluate the expression against the specified context and return the result.

    :param context: context for evaluating expression

    .. note::

       prepare() should be called before calling this method.

    .. versionadded:: 2.12 

where it can be observed, at first paragraph, that Evaluate the feature and return the result is completely factible and this method does not expect that prepare() has been called on this instance.

Searching other possible solutions, I found out this Joseph's answer where it is pointed out that "... you may need to use a combination of the QgsExpressionContext and QgsExpressionContextScope classes". When I use this approach, in a second run, QGIS 3.0 crashed indefectively.

So, Why a QgsExpression does not evaluate a feature as expected in PyQGIS 3? Is It possible to solve this issue without QgsExpressionContext and QgsExpressionContextScope classes?

Editing Note:

As I am a very big fan of Python List Comprehension, I modified Kadir's lines code to more concise form as:

project = QgsProject.instance()

layer = project.mapLayersByName('polygon8')

expression = QgsExpression( '"area" > 2e8 AND "area" < 3e8' )

context = QgsExpressionContext()
scope = QgsExpressionContextScope()
context.appendScope(scope)

eval = [ [scope.setFeature(feat), expression.evaluate(context)][1] for feat in layer[0].getFeatures() ]

feats = [ item[1] for item in enumerate(layer[0].getFeatures()) if eval[item[0]] == 1]

epsg = layer[0].crs().postgisSrid()

uri = "Polygon?crs=epsg:" + str(epsg) + "&field=id:integer""&index=yes"

mem_layer = QgsVectorLayer(uri, 
                           'mem_layer', 
                           'memory')

prov = mem_layer.dataProvider()

for i, feat in enumerate(feats):
    feat.setAttributes([i])
    mem_layer.addFeature(feat)

prov.addFeatures(feats)

QgsProject.instance().addMapLayer(mem_layer)

It works as expected.

1 Answer 1

3

By changing some lines, your code will work. In Joseph's answer, context.appendScope(scope) line have to moved outside the loop to solve crashing issue. @ndawson's answer explains why.

layer = iface.activeLayer()

expression = QgsExpression( u'"area" > 2e8 AND "area" < 3e8' )

# Added / changed lines ##########
context = QgsExpressionContext()
scope = QgsExpressionContextScope()
context.appendScope(scope)

feats=[]
for feat in layer.getFeatures():
    scope.setFeature(feat)
    result = expression.evaluate(context)
    if result:
        feats.append(feat)
################


epsg = layer.crs().postgisSrid()

uri = "Polygon?crs=epsg:" + str(epsg) + "&field=id:integer""&index=yes"

mem_layer = QgsVectorLayer(uri, 'mem_layer', 'memory')

prov = mem_layer.dataProvider()

for i, feat in enumerate(feats):
    feat.setAttributes([i])
    mem_layer.addFeature(feat)

prov.addFeatures(feats)

QgsProject.instance().addMapLayer(mem_layer)
3
  • But, I've no idea if it is possible to solve without QgsExpressionContext and QgsExpressionContextScope. Oct 14, 2018 at 18:49
  • Thanks for your answer. However, it changes a one line solution in pyqgis 2 for a 9 lines solution in pyqgis 3. I don't like it very much but it works.
    – xunilk
    Oct 14, 2018 at 20:27
  • I modified your proposal by using two python list comprehension. Please, see my Editing Note. Thanks again.
    – xunilk
    Oct 15, 2018 at 3:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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