I honestly have no idea why you feel that OOP would be necessary in order to keep track of variable assignments in a language interpreter. People wrote both interpreters and compilers long before object-oriented programming was even invented.
It might help to start by going back to what a "variable" in a computer program actually is in the first place.
A variable name is a symbolic name for a memory location, typically associated with a data size and some kind of semantic about the type of data stored at that memory location, or semantic about how what is stored at that memory location should be interpreted by the program.
Some languages, particularly older ones such as BCPL, have only a single data type, and thus don't need to keep track of the size or type of the data. In these cases, a variable name becomes simply a symbolic name for a memory location.
Either way, by consequence, a variable is simply a memory location.
It's possible to combine the "data size" and "type of data" into a single representation; for example, if you keep track of the fact that a variable name refers to a memory location holding a "16-bit signed integer value", then there is no need to separately track the fact that it's "16 bits" and that it's a "signed integer". There are pros and cons to tracking size and type together or separately; which way you do it is a design decision to be made for your interpreter implementation, and done right, the decision should have no impact on the behavior of a program that is being interpreted.
Since you are mainly interested in integers, you can take the BCPL approach; define a standard integer type for your language and leave it at that. For example, you could declare that every variable is a signed 64-bit integer; in C parlace, an
int64_t. (Apparently, in Python,
int type integer variables are of unlimited magnitude.)
That only leaves mapping from a name to its value within the program being interpreted.
As hkotsubo suggested in a comment, an obvious way to do that within a modern programming language and runtime such as Python is by using a dictionary to map from one to the other. The key (for the dictionary entry) becomes the variable name, or some transformation of the variable name that somehow includes its scope, and the value (of the dictionary entry) becomes the value held by that variable within the program being interpreted.
Once you want to support multiple types, simply replace the dictionary entry value with something like (in Python) a
dict instance that includes both type and value. Other languages have different names for much the same concept; for example, C-derived languages like C, C++, Java and C# have
struct (some also have
class), and Pascal and derivatives such as Ada have
record. The concept remains the same: the value that your interpreter is keeping track of itself contains information on both the type and the value of the variable within the scope of the program being interpreted.