1

I am trying to use an object attribute in a numexpr expression. The most obvious way of doing this:

import numpy as np
import numexpr as ne

class MyClass:
    def __init__(self):
        self.a = np.zeros(10)

o = MyClass()

o.a

b = ne.evaluate("o.a+1")

Results in the following error

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-22-dc90c81859f1> in <module>()
     10 o.a
     11 
---> 12 b = ne.evaluate("o.a+1")

~/.local/lib/python3.5/site-packages/numexpr/necompiler.py in evaluate(ex, local_dict, global_dict, out, order, casting, **kwargs)
    799     expr_key = (ex, tuple(sorted(context.items())))
    800     if expr_key not in _names_cache:
--> 801         _names_cache[expr_key] = getExprNames(ex, context)
    802     names, ex_uses_vml = _names_cache[expr_key]
    803     arguments = getArguments(names, local_dict, global_dict)

~/.local/lib/python3.5/site-packages/numexpr/necompiler.py in getExprNames(text, context)
    706 
    707 def getExprNames(text, context):
--> 708     ex = stringToExpression(text, {}, context)
    709     ast = expressionToAST(ex)
    710     input_order = getInputOrder(ast, None)

~/.local/lib/python3.5/site-packages/numexpr/necompiler.py in stringToExpression(s, types, context)
    296         names.update(expressions.functions)
    297         # now build the expression
--> 298         ex = eval(c, names)
    299         if expressions.isConstant(ex):
    300             ex = expressions.ConstantNode(ex, expressions.getKind(ex))

<expr> in <module>()

AttributeError: 'VariableNode' object has no attribute 'a'

Consulting another question, I was able to get a less than satisfactory solution by using numexpr's global_dict:

import numpy as np
import numexpr as ne

class MyClass:
    def __init__(self):
        self.a = np.zeros(10)

o = MyClass()

o.a

b = ne.evaluate("a+1", global_dict={'a':o.a})

That is going to get pretty messy once MyClass has a dozen attributes and there are a few such calls to ne.evaluate.

Is there a simple, clean way of doing this?

Nathan Musoke
  • 166
  • 1
  • 12

2 Answers2

2

Your main concern seems to be the scalability/maintainability of the evaluate call if your object starts having a lot of attributes. You can automate this part by passing vars(o):

import numpy as np
import numexpr as ne

class MyClass:
    def __init__(self):
        self.a = np.arange(10000)
        self.b = 2*self.a

o = MyClass()

c = ne.evaluate("a+b", local_dict=vars(o))

Note that I used local_dict because it might be marginally faster to put these names into the local namespace. If there's any chance of the instance attributes clashing with local names in the script (which largely depends on how you name your attributes and what the class does), it's probably safer to pass the vars as the global_dict just like in the question (and for the same reason as noted in a comment).

You'll still have to keep track of the correspondence between instance attributes and their names in numexpr expressions, but the bulk of the work can be skipped with the above.

  • Sometimes vars won't work when the object has no __dict__ method. Although admittedly this is unlikely, it's still probably more complete to have a solution for that case as well –  Oct 29 '18 at 22:55
  • 1
    @NateBronman for custom made classes that can mostly happen with `__slots__`, right? And otherwise `vars(o)` should return the same thing as the dunder `o.__dict__` like in the other answer, right? – Andras Deak -- Слава Україні Oct 29 '18 at 22:56
  • 2
    yes mainly and a few other cases. Considering the question, I repeat that this is pretty unlikely that his object is one of those cases. But the question is not only for the OP, so you never know what kind of object someone else may have in mind. –  Oct 29 '18 at 23:00
  • 1
    @NateBronman you'd hope though that if you're using numexpr you'd expect some kind of stability or precontract than truly broadscale abstractions that just want to do anything... :) – Jon Clements Oct 29 '18 at 23:18
  • 1
    I did originally use `local_dict`, but that doesn't work as nicely when the expression also contains variables from the local namespace. – Nathan Musoke Oct 29 '18 at 23:24
  • @NathanMusoke good point. I guess what I had in mind was a specialized container for a given task, where I'd expect clashes with local names to be less frequent. But I'll emphasize this point, thank you. – Andras Deak -- Слава Україні Oct 29 '18 at 23:26
  • 2
    @NathanMusoke is there any reason you have to do the evaluation outside the instance and not have an instance method *evaluate*? – Jon Clements Oct 29 '18 at 23:27
  • @JonClements That may be a better idea. What I am currently doing made the most sense with the code I started refactoring but I may rethink it. – Nathan Musoke Oct 29 '18 at 23:32
  • @JonClements actually, won't the instance method have a similar problem? In the given example it would need to use `self.a`. – Nathan Musoke Oct 29 '18 at 23:36
2

You can use an object's __dict__ attribute to do this. This will return a dictionary where the key is the name of the attribute (as a string) and the value is the actual value of that attribute itself.

So, as an example, the code in your question would look like:

import numpy as np
import numexpr as ne

class MyClass:
    def __init__(self):
        self.a = np.zeros(10)

o = MyClass()

o.a

b = ne.evaluate("a+1", global_dict=o.__dict__)  # Notice the .__dict__

However, some object may not have a __dict__ attribute. So, instead, I made a small function that would do the same thing:

def asdict(obj):
    objDict = {}
    for attr in dir(g):
        objDict[attr] = getattr(g, attr)

    return objDict

Please note that this function will also include methods and certain hidden attributes such as __module__ and __main__