mitchute
mitchute

Reputation: 431

Symbolic Links to Variables

I'm working on a program where I would like to report the status of specific variables from time to time, however, I don't necessarily know which variables I'll need apriori. Other programs I have worked on (in C++) use a registration system where the variables of interest are registered with a central output processor once, then when program status needs to be reported, a central module gathers the data from the output variables and writes to an output file. So, the program flow would be something like this.

Example MyModule.py

from OutputProcessor import OutputProcessor


class MyClass():

    def __init__(self):
        op = OutputProcessor()

        # variable I want to report
        self.foo = None

        # register with output processor
        op.register_output_variable(self.foo, "Foo")

    def calc_foo(self, bar, rand):
        self.foo = bar ** rand

Example OutputProcessor.py

class OutputProcessor(object):
    # list or dict object containing the name and link information to variables of interest
    output_vars_data = []

    def __init__(self):
        pass

    def register_output_variable(self, var, name):
        OutputProcessor.output_vars_data.append({"link address": id(var), "name": name})

    def report_output(self):
        for data in OutputProcessor.output_vars_data:
            self.write_to_output_file(data)

    def write_to_output_file(self, data):
        # write all of the data to the output file here
        pass

Example Main.py

import random

from MyClass import MyClass
from OutputProcessor import OutputProcessor as OP

if __name__ == "__main__":

    cls_instance = MyClass()

    for i in range(100):
        cls_instance.calc_foo(i, random.randint(1, 10))
        OP().report_output()

My first question: is this even possible, or practicable? Second, if so, how would I go about doing this? There are going to be some nuances to deal with, like how to handle multiple instances of the same class reporting the same variable, but that seems like a small issue. Is there a way to implement this so that I'm always getting the current value of the variable, and not just an old copy of it?

Any thoughts, or suggestions?

Upvotes: 2

Views: 690

Answers (1)

Mad Physicist
Mad Physicist

Reputation: 114460

Aside from the scoping issues in your code, there is really one issue to keep in mind. Python does everything by binding a name to a reference. You are interested in getting the value assigned to a name, not to the reference it may have been bound to at registration time. This is sort of like a pointer-to-a-pointer in C and C++.

To keep track of arbitrary names, you need to keep track of their enclosing namespace as well. That way you can use the universal function getattr to fetch the latest value of your name. A namespace can be any instance (modules and classes are instances too).

Your registration method needs to keep track of the object containing the name of interest, the name itself, and the key you want to display it as:

op.register_output_variable(self, 'foo', "Foo")

The manager class can store the values in a dictionary. Instead of a nested dictionary, I'd recommend using a tuple or collections.namedtuple to hold the "pointer":

def register_output_variable(self, inst, attr, key):
    self.output_vars_data[key] = (inst, attr)

When you output the variables, do something like

for key, spec in self.output_vars_data.items():
    print(key, '=', getattr(*spec))

What this does is provide you with a way to reference object attributes by name instead of reference. If you set x.a = 1, register x.a, then do x.a += 1, you'd want to get 2 as your value. The important thing to remember is that x.a will be bound to a different reference because integers are immutable in Python. You completely bypass this by registering x as the namespace, and 'a' as the name.

You can generalize further by adding a method to register indexable elements, like the elements of a list and values of a dictionary. You would register the instance and index/key instead of instance and attribute name. Take a look at operator.attrgetter and operator.itemgetter for more ideas.

This is just a broad suggestion. It does not address the inconsistent mess of static, class and instance namespaces you are using.

Upvotes: 2

Related Questions