0

Suppose I have a Python class called Person in file person.py as follows:

class Person(object):
    static_id = 0
    instance_id = None

    @staticmethod
    def new_id():
        Person.static_id += 1
        return Person.static_id

    def __init__(self):
        self.instance_id = Person.new_id()

    def __repr__(self):
        return "<Person #%s>" % (self.instance_id)

Instantiating Person works:

>>> a = Person()
>>> a
<Person #1>
>>> b = Person()
>>> b
<Person #2>

And I have another class called Thing in file thing.py as follows:

from person import Person

class Thing(object):
    static_id = 0
    instance_id = None
    instance_creator = None

    @staticmethod
    def new_id():
        Thing.static_id += 1
        return Thing.static_id

    def __init__(self, person):
        self.instance_id = Thing.new_id()
        self.instance_creator = person

    def __repr__(self):
        return "<Thing #%s created by %s>" % (self.instance_id, self.instance_creator)

Now, when I instantiate instances of Thing I see the following:

>>> Thing(person=b)
<Thing #1 created by <Person #2>>

>>> Thing(person=b)
<Thing #2 created by <Person #2>>

>>> Thing(person=a)
<Thing #3 created by <Person #1>>

How can I create a (non-static) method Person.created_things() that will return me all Things created by that instance of Person? I want:

>>> b.created_things()
[<Thing #1 created by <Person #2>>, <Thing #2 created by <Person #2>>]

But I cannot figure out how to do this without importing thing.Thing into person.py. Doing so would create a circular import reference. So how can I do it? In this case these are not Django classes.

Saqib Ali
  • 11,931
  • 41
  • 133
  • 272
  • This is not directly related to the question, but it's worth noting that those instance counters are not thread safe. – sapi Jan 24 '15 at 08:02

2 Answers2

1

Just add a couple of methods to Person (and initialize self.things in __init__):

def __init__(self):
    self.things = []
    self.instance_id = Person.new_id()

def addThing(self, thing):
    self.things.append(thing)

def created_things(self):
    return self.things

And when you initialize Thing, just add it to Person.

def __init__(self, person):
    self.instance_id = Thing.new_id()
    self.instance_creator = person
    self.instance_creator.addThing(self)
Eithos
  • 2,421
  • 13
  • 13
1

You can do this without modifying the Person definition in person.py by dynamically modifying the person instance passed to Thing:

from person import Person

class Thing(object):
    static_id = 0
    instance_id = None
    instance_creator = None

    @staticmethod
    def new_id():
        Thing.static_id += 1
        return Thing.static_id

    def __init__(self, person):
        self.instance_id = Thing.new_id()
        self.instance_creator = person
        try:
            person.created_things.append(self)
        except AttributeError:
            person.created_things = [self]

    def __repr__(self):
        return "<Thing #%s created by %s>" % (self.instance_id, self.instance_creator)


a = Person()
print a

b = Person()
print b

t1 = Thing(person=b)
print t1

t2 = Thing(person=b)
print t2

t3 = Thing(person=a)
print t3

print a.created_things
print b.created_things

output

<Person #1>
<Person #2>
<Thing #1 created by <Person #2>>
<Thing #2 created by <Person #2>>
<Thing #3 created by <Person #1>>
[<Thing #3 created by <Person #1>>]
[<Thing #1 created by <Person #2>>, <Thing #2 created by <Person #2>>]
PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • This works... but setting another object's fields directly violates encapsulation. If a `Person` instance is going to hold that data, it should be responsible for it. The way you have it, `Person` is unaware it holds it. I don't really see that not **'modifying the `Person` definition in person.py'** to be an advantage. If you were to do this though, at the very least I'd initialize created_things inside of `Person` to avoid such a superfluous use of `Try/Except`. – Eithos Jan 24 '15 at 07:37
  • @Eithos: Fair point: my code does violate encapsulation. OTOH, that may be preferable to modifying the definition in `person.py` if the `Person` class is used by other code apart from `thing.py`. But I guess a cleaner approach would be to create a new class in `thing.py` that inherits from `Person`. – PM 2Ring Jan 24 '15 at 07:47
  • Also note that `try:...except` is a _very_ efficient operation in Python: code that uses it can often run faster than equivalent `if:...else`-based code. – PM 2Ring Jan 24 '15 at 07:52