How to detect variable symbol without assignment(NameError) before a piece of code run?

I wrote a tool to help converting Einstein notation assignment directly to numpy.einsum. It looks like:

import numpy as np
from easy_ast.tensor_contract import TensorContract

b = np.array([1, 2])
c = np.array([1, 2])


@TensorContract().exec
def _(i, j):
    a[i, j] = -b[i] * c[j]
    print(a)

Where TensorContract().exec get the source of function and do ast transformer and exec code immediately.

However, I pass dummy index manaully to my transformer currently. If I could get the information about dummy index directly from symbol without assginment before(symbol i and j in this example), my interface could be better, since declaring dummy index is annoying.

Not entirely sure I follow; can you show what you would like to do and how it’s not working?

A function object includes a lot of information about the symbols it uses. That should be useful.

In the example:

@TensorContract().exec
def _(i, j):
    a[i, j] = -b[i] * c[j]
    print(a)

code in the function will be transformed to something like

a = -np.einsum("i,j->ij", b, c)
print(a)

And then execute it immediately.

Inside transformer, TensorContract().exec read arg of function: i and j, and treat them as dummy index.

However, I want to simplify the code to

@TensorContract().exec
def _():
    a[i, j] = -b[i] * c[j]
    print(a)

Which could recognize i and j are not valid variables(will raise NameError if I run the code directly) and treat it as dummy index automatically.

Hmm. As far as Python is concerned, names like b and c are just as external as i and j - and even print. How do you know the difference, if they’re not arguments?

I don’t know, that’s why I come here for suggestion.

It seems I can search all getitem arguments in AST tree, and collect all AST Node with type ast.Name. But I cannot detect whether a Name is assigned.

Define “assigned”. You mean when it’s used as a subscript on the left of the equals sign? There’s a distinction between the assignment in a = 1 (which assigns to the name a) and what you have with a[i, j] = ... (which does a subscript assignment to the object a). You could search the bytecode for the STORE_SUBSCR operation, but it might not be easy.

A Name is assigned, if and only if, when code containing this Name running, it will not raise NameError saying this Name not defined.

Here is an example about why it is important whether a Name is assigned.

  • Name i is assigned, I do not want to apply any transformer on it, it is just a[1] = b[1]
i = 1
a[i] = b[i]
  • Name i is not assigned, I want to treat it as a dummy index, and AST transformer result would be like: a=np.einsum("i->i", b).
try:
   del i
except:
   pass
a[i] = b[i]

Sorry for confusing, I am talking about whether i is assigned, not the assignment for a in a[i] = b[i].

Okay, but in your example function, unassigned names are a, b, c, i, j, and print. How do you know which ones you’re using?

In my example, i and j are unassigned names, but b and c were assigned in b = np.array([1, 2]) and c = np.array([1, 2]), print can be found in global namespace. a is the Name with ctx=ast.Store() but not ast.Load(), so skip to check it.

Ahh, I see. So the module globals and the builtins are relevant, but anything that isn’t in one of those needs to become a local. That makes sense.

(By the way, since _ is a really awkward name, so I’m going to refer to it as func hereunder. Feel free to suggest a better name.)

So here’s what I’m thinking.

import builtins
class Magic(dict):
    def __missing__(self, key):
        if key in func.__globals__: return func.__globals__[key]
        if hasattr(builtins, key): return getattr(builtins, key)
        print("It's a local!")

exec("func()", func.__globals__, Magic())

I haven’t tested that but it’s worth a try.

1 Like

Lint programs can detect assignments that are never used. Can they not detect uses that are never assigned?

1 Like