Need help with simple OOP Python question

Discussion in 'Python' started by Kristofer Tengström, Sep 5, 2011.

  1. Hi, I'm having trouble creating objects that in turn can have custom
    objects as variables. The code looks like this:

    ---------------------------------------------

    class A:
    sub = dict()
    def sub_add(self, cls):
    obj = cls()
    self.sub[obj.id] = obj

    class B(A):
    id = 'inst'

    base = A()
    base.sub_add(B)
    base.sub['inst'].sub_add(B)

    print # prints a blank line
    print base.sub['inst']
    print base.sub['inst'].sub['inst']

    ----------------------------------------------

    Now, what I get from this is the following:
    <__main__.B instance at 0x01FC20A8>
    <__main__.B instance at 0x01FC20A8>
    Why is this? What I want is for them to be two separate objects, but
    it seems like they are the same one. I've tried very hard to get this
    to work, but as I've been unsuccessful I would really appreciate some
    comments on this. I'm sure it's something really easy that I just
    haven't thought of.

    Python version is 2.6.5 (I'm using Panda3D to create a 2½D game).
     
    Kristofer Tengström, Sep 5, 2011
    #1
    1. Advertisements

  2. You are sharing this single "sub" dictionary with all instances of your
    A class.

    If you want to define instance-specific attributes, define them in the
    __init__ method, like so:

    class A:
    def __init__(self):
    self.sub = dict()

    def sub_add(self, cls):
    obj = cls()
    self.sub[obj.id] = obj

    --

    Stephen Hansen
    ... Also: Ixokai
    ... Mail: me+list/python (AT) ixokai (DOT) io
    ... Blog: http://meh.ixokai.io/


    -----BEGIN PGP SIGNATURE-----
    Version: GnuPG v2.0.10 (Darwin)

    iQEcBAEBAgAGBQJOZHVKAAoJEKcbwptVWx/l3+4H/3abOlp0rAR0HRYdxsDGw6HP
    uZN9+H6mkw/NqgVsD5yxTkE+l1axvq6w0GmFsHBOm9Xx9dbnOpzAMuAENEPgxg5o
    LXTwWVQePHnk2mr9xdwSkL5L/8cRqegzPaLVs085iMlnQUHkgO+l13grQD4Wnh6d
    rXnV/Ah1y2I2iGO1bM382imEmGDkIUpM8tPdpnUyCVz1rI7LjsmvLoBspKKbT1Ip
    qr8+1wvH9OINUff4YHBtIvST/wRhxQNYYOb7q6JcR88bGPhUXDNI73/eyVgIcGYf
    Buft1Ieyl+RsweoEuGsXgGh+GuJN+ISBtlrrEFglSDPfsvHyP8jfeLisxjPX+IQ=
    =tVRh
    -----END PGP SIGNATURE-----
     
    Stephen Hansen, Sep 5, 2011
    #2
    1. Advertisements

  3. Kristofer Tengström

    Peter Otten Guest

    Putting it into the class like this means sub is shared by all instances.
    Your class A needs an initialiser:

    class A:
    def __init__(self):
    self.sub = {} # one dict per instance
    # ...
     
    Peter Otten, Sep 5, 2011
    #3
  4. Thanks everyone, moving the declaration to the class's __init__ method
    did the trick. Now there's just one little problem left. I'm trying to
    create a list that holds the parents for each instance in the
    hierarchy. This is what my code looks like now:

    -----------------------------------------

    class A:
    def __init__(self, parents=None):
    self.sub = dict()
    if parents:
    self.parents = parents
    else:
    self.parents = []
    def sub_add(self, cls):
    hierarchy = self.parents
    hierarchy.append(self)
    obj = cls(hierarchy)
    self.sub[obj.id] = obj

    class B(A):
    id = 'inst'

    base = A()
    base.sub_add(B)
    base.sub['inst'].sub_add(B)

    print
    print vars(base)
    print
    print vars(base.sub['inst'])
    print
    print vars(base.sub['inst'].sub['inst'])

    ---------------------------------------------

    The output from this program is the following:

    {'parents': [<__main__.A instance at 0x02179468>, <__main__.B instance
    at 0x021794B8>], 'sub': {'inst': <__main__.B instance at 0x021794B8>}}

    {'parents': [<__main__.A instance at 0x02179468>, <__main__.B instance
    at 0x021794B8>], 'sub': {'inst': <__main__.B instance at 0x021794E0>}}

    {'parents': [<__main__.A instance at 0x02179468>, <__main__.B instance
    at 0x021794B8>], 'sub': {}}

    As you can see, the problem looks similar to the one before: All the
    instances have an identical parent list. However, I don't understand
    why as self.parents is declared in the __init__ method. Any ideas?
    What I want is for the first instance to have an empty list, the
    second to have one element in the list and the third to have two
    parent elements.
     
    Kristofer Tengström, Sep 5, 2011
    #4
  5. Kristofer Tengström

    Peter Otten Guest

    You should explicitly test for None here; otherwise in a call like

    ancestors = []
    a = A(anchestors)

    the list passed as an argument will not be used, which makes fore confusing
    behaviour.
    Here you are adding self to the parents (that should be called ancestors)
    and pass it on to cls(...). Then -- because it's non-empty -- it will be
    used by the child, too, and you end up with a single parents list.
    While the minimal fix is to pass a copy

    def sub_add(self, cls):
    obj = cls(self.parents + [self])
    self.sub[obj.id] = obj

    I suggest that you modify your node class to keep track only of the direct
    parent instead of all ancestors. That makes the implementation more robust
    when you move a node to another parent.
     
    Peter Otten, Sep 5, 2011
    #5
  6. Kristofer Tengström

    Jon Clements Guest

    I may not be understanding the OP correctly, but going by what you've
    put here, I might be tempted to take this kind of stuff out of the
    class's and using a graph library (such as networkx) - that way if
    traversal is necessary, it might be a lot easier. But once again, I
    must say I'm not 100% sure what the OP wants to achieve...

    Jon.
     
    Jon Clements, Sep 5, 2011
    #6
  7. Kristofer Tengström

    Peter Otten Guest

    Learn Python?

    ;)
     
    Peter Otten, Sep 5, 2011
    #7
  8. Kristofer Tengström

    Terry Reedy Guest

    Indexing objects by their internal id is usually useless. Considier
    whether you should be using sets rather than dicts.
     
    Terry Reedy, Sep 5, 2011
    #8
  9. obj.id is not the internal id.
     
    Piet van Oostrum, Sep 8, 2011
    #9
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.