Showing posts with label class variable. Show all posts
Showing posts with label class variable. Show all posts

Forward references

Forward references are sometimes necessary and in interpreted languages like Python almost never a problem. In class definitions however, it is. In this article we examine a solution for this problem with the use of the built in globals() function.

Consider the following use case: we have two classes A and B and we want the __init__() method of both classes to check that any arguments passed to it are instances of the other class, i.e. the A() constructor will only accept B instances as argument while the B() constructor only accepts A instances.

This isn't a problem per se because Python is an interpreted language. Is this context that means that a reference to any class name in for example the __init__() method will be resolved at run time. The __init__() method of the first class to be defined can therefor use an expression like issubclass(argument.__class__,B) without a problem, even if B is not yet defined.

But what if we want to parameterize this approach? Say we want to store the class that the arguments have to check against in a class variable. Such an assignment is executed while the class is being defined and therefor a forward reference (a reference to a class that is defined later in the file) will raise an exception. The following snippet of code illustrates the problem.

class A:
    argumentclass = B

    def __init__(self,*args):
        for a in args:
            if not issubclass(a.__class__,self.argumentclass):
                raise TypeError('argument not a subclass of' ,
                   self.argumentclass,a)
        self.refs = args

The code in line 2 will raise a NameError exception. There is a solution however: if we store the name of the class we can easily check if an argument is a sub class of a class with that stored name if we have a means of retrieving a class object by name. Fortunately that is quite simple by way of the globals() function. This built in function will return a dictionary of all objects in the current globals scope. By the time the __init__() method is executed this will hold both the A and the B class. The adapted code may look something like this:

class A:
    argumentclass = 'B' 
    def __init__(self,*args):
        for a in args:
            if not issubclass(a.__class__,globals()[self.argumentclass]):
                raise TypeError('argument not a subclass of' ,self.argumentclass,a)
        self.refs = args

This approach even allows for easy subclassing:

class X:
    argumentclass = None 
    def __init__(self,*args):
        for a in args:
            if not issubclass(a.__class__,globals()[self.argumentclass]):
                raise TypeError('argument not a subclass of' ,self.argumentclass,a)
        self.refs = args

class A(X):
    argumentclass = 'B'

class B(X):
    argumentclass = 'A'