Can Python pyreverse generate UML diagram for functions?

pyreverse is a great tool to generate UML diagram from Python codes. However, I found it cannot identify all the classes that has been used within a function. I take the following example to illustrate my point:

class ClassA(object):
   def __init__(self):
         pass
class ClassB(object):
   def __init__(self):
         pass
class ClassC(object):
   def __init__(self):
      self.object_b = ClassB() 
   def perform():
      object_a = ClassA()

If we use pyreverse to generate class diagram, it is clear that we can see ClassB is a component within ClassC. However, it cannot generate the relationship with ClassA, which is used in its function perform. Is there any way for pyreverse to retrieve the relationship between ClassC and ClassA?

Answer

The reason is the scope of object_a: it is a local variable of the function and not an instance variable. The relationship of C with A is therefore not structural. Hence, it is not an UML association (nor aggregation, nor composition).

At best, we could say there is a usage dependency from C to A. But this usage is specific to the implementation and not necessarily meant by design.

I’m not a Python expert, but if pyreverse is able to spot the right relation with object_b, and if you’d make object_a an instance variable with self.object_a in the assignment, you can hope to get the result that you expected.

Edit: Experimental verification

If the class C is corrected as explained:

class ClassC(object):
    def __init__(self):
        self.object_b = ClassB()
    def perform(self):
        self.object_a = ClassA()

pyreverse generates indeed the right result as expected:

enter image description here

For a human reader, it’s easy to miss a property. This is why pylint issues a warning on this code:

W0201: Attribute 'object_a' defined outside __init__ (attribute-defined-outside-init)

Note also that if you define a (static) class variable instead of an instance variable, pyreverse does not show it with an underlined name. The reason is probably, because it’s not uncommon to hide a class variable with an instance variable of the same name.