14

I get the error:

NameError: name 'OrgUnit' is not defined
class OrgUnit(object):

    def __init__(self,
                 an_org_name: str,
                 its_parent_org_unit: OrgUnit= None
                 ):
        self.org_unit_name = an_org_name
        self.parent_org_unit = its_parent_org_unit

    def __str__(self):
        if self.parent_org_unit:
            parent_org_unit_name = self.parent_org_unit.__str__()
            return parent_org_unit_name + "->" + self.org_unit_name
        else:
            return self.org_unit_name


if __name__ == '__main__':
    ibm_worldwide = OrgUnit("IBM_Worldwide")
    ibm_usa = OrgUnit("IBM_USA", ibm_worldwide)
    ibm_asia = OrgUnit("IBM_Asia", ibm_worldwide)
    ibm_china = OrgUnit("IBM_China", ibm_asia)
    print(ibm_worldwide)
    print(ibm_usa)
    print(ibm_asia)
    print(ibm_china)

I am sure this is a known paradigm, as it seems like a pretty common hierarchical class usage issue (a self referencing class). I know I could change the type of its_parent_org_unit to be object and it works, but this seems like the wrong thing to do, primarily since it scuppers my ability to check types in my calls. With its_parent_org_unit changed to be a type object I get the correct results:

IBM_Worldwide
IBM_Worldwide->IBM_USA
IBM_Worldwide->IBM_Asia
IBM_Worldwide->IBM_Asia->IBM_China

I am open to thoughts and suggestions. What is the most "pythonic" way to do this sort of thing?

PS: What is the name for this sort of "self referencing class" paradigm/problem, which I could use to look up other suggestions?

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
R. G. Abbott
  • 143
  • 1
  • 5

1 Answers1

29

Your problem is that you want to use type hints but you want this class itself to be able to take arguments of its own type.

The type hints PEP (0484) explains that you can use the string version of the type's name as a forward reference. The example there is of a Tree data structure which sounds remarkably similar to this OrgUnit one.

For example, this works:

class OrgUnit(object):

    def __init__(self,
                 an_org_name: str,
                 its_parent_org_unit: 'OrgUnit' = None
                 ):

In Python 3.7, you will be able to activate postponed evaluation of annotations with from __future__ import annotations. This will automatically store annotations as strings instead of evaluating them, so you can do

from __future__ import annotations

class OrgUnit(object):
    def __init__(self,
                 an_org_name: str,
                 its_parent_org_unit: OrgUnit= None
                 ):
        ...

This is scheduled to become the default in Python 4.0.

user2357112
  • 260,549
  • 28
  • 431
  • 505
Brian Cain
  • 14,403
  • 3
  • 50
  • 88
  • This does work, however it does not raise any issue with an invalid type such as a string eg: failure_case = OrgUnit("Bad Unit", "a String")! There is no error in this case. – R. G. Abbott Feb 25 '16 at 04:09
  • "There is no error in this case"? During execution? Why would you expect an error? – Brian Cain Feb 25 '16 at 04:13
  • 1
    re. "there is no error". Python is **not** Java _on purpose_ :-) – miraculixx Aug 26 '16 at 14:37
  • @R. G. Abbott: Also see [**Class Members and Methods**](http://stackoverflow.com/documentation/python/1766/type-hints/10915/class-members-and-methods#t=201704140024478784181) topic in the Python Language **Type Hints** section of stackoverflow's Documentation project. – martineau Apr 14 '17 at 00:31