Newbie: question regarding references and class relationships

R

Rui Maciel

Let:
- class Point be a data type which is used to define points in space
- class Line be a data type which possesses an aggregate relationship with
objects of type Point
- class Model be a container class which stores collections of Point and
Line objects


Essentially, a Model object stores lists of Point objects and Line objects,
and Line objects include references to Point objects which represent the
starting and ending point of a line.

To reflect this class relationship, I've defined the following classes:

<code>
class Point:
position = []

def __init__(self, x, y, z = 0):
self.position = [x, y, z]

class Line:
points = ()
def __init__(self, p_i, p_f):
self.points = (p_i, p_f)

class Model:
points = []
lines = []


</code>

It would be nice if, whenever a Point object was updated, the Line objects
which are associated with it could reflect those updates directly in
Line.p_i and Line.p_f.

What's the Python way of achieving the same effect?


Thanks in advance,
Rui Maciel
 
R

Roy Smith

Rui Maciel said:
Essentially, a Model object stores lists of Point objects and Line objects,
and Line objects include references to Point objects which represent the
starting and ending point of a line.

class Point:
position = []

def __init__(self, x, y, z = 0):
self.position = [x, y, z]

class Line:
points = ()
def __init__(self, p_i, p_f):
self.points = (p_i, p_f)

class Model:
points = []
lines = []


It would be nice if, whenever a Point object was updated, the Line objects
which are associated with it could reflect those updates directly in
Line.p_i and Line.p_f.

Have you tried running the code you wrote? It does that already! When
you do something like:

my_list = [obj1, obj2]

in Python, the objects are stored by reference (not just lists, all
assignments are by reference).
 
P

Peter Otten

Rui said:
Let:
- class Point be a data type which is used to define points in space
- class Line be a data type which possesses an aggregate relationship with
objects of type Point
- class Model be a container class which stores collections of Point and
Line objects


Essentially, a Model object stores lists of Point objects and Line
objects, and Line objects include references to Point objects which
represent the starting and ending point of a line.

To reflect this class relationship, I've defined the following classes:

<code>
class Point:

Don't add
position = []

to your code. That's not a declaration, but a class attribute and in the
long run it will cause nothing but trouble.
def __init__(self, x, y, z = 0):
self.position = [x, y, z]

class Line:
points = ()
def __init__(self, p_i, p_f):
self.points = (p_i, p_f)

class Model:
points = []
lines = []


</code>

It would be nice if, whenever a Point object was updated, the Line objects
which are associated with it could reflect those updates directly in
Line.p_i and Line.p_f.

What's the Python way of achieving the same effect?

Your code already does that. Here's a minimally cleaned up version:

$ cat tmp_points.py
class Point:
def __init__(self, x, y, z=0):
self.position = (x, y, z)
def __str__(self):
return "%s" % (self.position,)

class Line:
def __init__(self, p_i, p_f):
self.points = (p_i, p_f)
def __str__(self):
return "%s-->%s" % self.points

a = Point(1, 2, 3)
b = Point(4, 5, 6)
ab = Line(a, b)
print(ab)
a.position = (10, 20, 30)
print(ab)

$ python3 tmp_points.py
(1, 2, 3)-->(4, 5, 6)
(10, 20, 30)-->(4, 5, 6)
 
R

Rui Maciel

Roy said:
Have you tried running the code you wrote? It does that already! When
you do something like:

my_list = [obj1, obj2]

in Python, the objects are stored by reference (not just lists, all
assignments are by reference).

I've tested the following:

<code>
model = Model()
model.points.append(Point(1,2))
model.points.append(Point(1,4))

line = Line( model.points[0], model.points[1])

# Case A: this works
model.points[0].position = [2,3,4]
line.points

# Case B: this doesn't work
test.model.points[0] = test.Point(5,4,7)
line.points
</code>


Is there a Python way of getting the same effect with Case B?


Thanks in advance,
Rui Maciel
 
R

Rui Maciel

Rui said:
# Case B: this doesn't work
test.model.points[0] = test.Point(5,4,7)

Disregard the "test." bit. I was testing the code by importing the
definitions as a module.


Rui Maciel
 
P

Peter Otten

Rui said:
Peter said:
Don't add
position = []

to your code. That's not a declaration, but a class attribute and in the
long run it will cause nothing but trouble.

Why's that?

Especially with mutable attributes it's hard to keep track whether you are
operating on the instance or the class:
.... position = []
.... def __init__(self, x, y, z):
.... self.position = [x, y, z]
....
a = Point(1, 2, 3)
b = Point(10, 20, 30)
a.position [1, 2, 3]
del a.position
a.position [] # did you expect that?
del b.position
b.position.extend(["did you expect that?"])
a.position ['did you expect that?']
del a.position
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: position
 
R

Rui Maciel

Peter said:
Rui said:
Peter said:
Don't add

position = []

to your code. That's not a declaration, but a class attribute and in the
long run it will cause nothing but trouble.

Why's that?

Especially with mutable attributes it's hard to keep track whether you are
operating on the instance or the class:
... position = []
... def __init__(self, x, y, z):
... self.position = [x, y, z]
...
a = Point(1, 2, 3)
b = Point(10, 20, 30)
a.position [1, 2, 3]
del a.position
a.position [] # did you expect that?
del b.position
b.position.extend(["did you expect that?"])
a.position ['did you expect that?']
del a.position
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: position


How do you guarantee that any object of a class has a specific set of
attributes?


Rui Maciel
 
P

Peter Otten

Rui said:
What's your point regarding attribute assignments in class declarations,
then?

I don't understand the question. My original point was that you should omit
class attributes that don't fulfill a technical purpose.
 
R

Rui Maciel

Peter said:
I don't understand the question. My original point was that you should
omit class attributes that don't fulfill a technical purpose.

You've said the following:

class Point:

Don't add
position = []

to your code. That's not a declaration, but a class attribute and in the
long run it will cause nothing but trouble.
</quote>


We've established that you don't like attribute declarations, at least those
you describe as not fulfill a technical purpose. What I don't understand is
why you claim that that would "cause nothing but trouble".


Rui Maciel
 
P

Peter Otten

Rui said:
Peter said:
I don't understand the question. My original point was that you should
omit class attributes that don't fulfill a technical purpose.

You've said the following:

class Point:

Don't add
position = []

to your code. That's not a declaration, but a class attribute and in the
long run it will cause nothing but trouble.
</quote>


We've established that you don't like attribute declarations, at least
those
you describe as not fulfill a technical purpose. What I don't understand
is why you claim that that would "cause nothing but trouble".

Have you read the code in the interpreter session I posted?

If you do not agree that the demonstrated behaviour is puzzling I'll have to
drop my claim...
Likewise if you can show a benefit of the

line.
 
R

Rui Maciel

Peter said:
Have you read the code in the interpreter session I posted?

If you do not agree that the demonstrated behaviour is puzzling I'll have
to drop my claim...

I don't see how it should be puzzling. You've deleted the attribute, so it
ceassed to exist.

Likewise if you can show a benefit of the

line.

I wrote the code that way to declare intent and help document the code. In
this case that the class Point is expected to have an attribute named
position which will point to a list.


Rui Maciel
 
D

Dave Angel

I don't see how it should be puzzling. You've deleted the attribute, so it
ceassed to exist.

Clearly you didn't reason through all the code in Peter's example.
Likewise if you can show a benefit of the
position = []

line.

I wrote the code that way to declare intent and help document the code. In
this case that the class Point is expected to have an attribute named
position which will point to a list.


So why do you also have an instance attribute of the same name? If you
really want all instances to have the same value for position, you'd
better change the __init__() function so it doesn't mask that single value.

Could it be that you really meant that instances of class Point are each
expected to have an attribute named Position? Could it be that you want
each instance's position to be independent of the others?

By having a class attribute with the same name as the instance
attribute, you're occasionally going to use the class version when you
meant the instance one.

I suspect you didn't realize the distinction between class attributes
and instance attributes, and that a comment would be a much better way
to communicate than creating a misleading value.

In one of your other messages, you asked:
How do you guarantee that any object of a class has a
specific set of attributes?

Answer is to define those INSTANCE attributes in the __init__() method
(or occasionally in the __new__() method), and to make sure you don't
ever delete them. The attributes of the object are instance attributes,
while the attributes defined inside the class are class attributes.

Occasionally, it can be useful to let a class attribute be a 'backup' to
the instance attributes, but you've got to think through your use case.
If memory is really tight, and if nearly all of the instances want the
same value, then you could omit the instance attribute except for the
exceptional ones, and let the class attribute fill in. But as Peter
says, if it's mutable, you then open yourself to somebody changing them
all, thinking it was only changing the one. But there are good reasons
not to use this trick to save memory.
 
T

Terry Jan Reedy

We've established that you don't like attribute declarations, at least those
you describe as not fulfill a technical purpose. What I don't understand is
why you claim that that would "cause nothing but trouble".

Three answers:
Look how much trouble it has already caused ;-)
Since you are a self-declared newbie, believe us!
Since, be definition, useless code can do no good, it can only cause
trouble. Think about it.

Terry
 
T

Terry Jan Reedy

class Model:
points = []
lines = []

Unless you actually need keep the points and lines ordered by entry
order, or expect to keep sorting them by whatever, sets may be better
than lists. Testing that a point or line is in the model will be faster,
as will deleting one by its identify.
 
I

Ian Kelly

# Case A: this works
model.points[0].position = [2,3,4]
line.points

# Case B: this doesn't work
test.model.points[0] = test.Point(5,4,7)
line.points
</code>


Is there a Python way of getting the same effect with Case B?


It's informative to think about what object is actually being mutated
in each of those examples. In A, you are mutating the Point instance
by replacing its "position" attribute with a new list. Since the Line
object references the same Point instance, it "sees" the change. In
B, you are actually mutating a list *containing* the Point instance,
by replacing the Point in the list with some new Point. The replaced
Point itself is not changed at all (apart from having one fewer
reference), and so from the perspective of the Line nothing has
changed.

There are a couple of ways you might get this to work the way you
want. One is by adding as an extra layer a proxy object, which could
be as simple as:

class Proxy(object):
def __init__(self, ref):
self.ref = ref

Instead of adding points to the model, add instances of Proxy that
contain the points. When adding points to the lines, add the same
Proxy objects instead. Later, when you want to replace a point in the
model, leave the Proxy in place but change the Point it contains by
modifying the "ref" attribute. Since the Line only directly
references the Proxy, it must follow the same ref attribute to access
the Point, and so in that way it will "see" the change. The downsides
to this approach are that you have to then use the Proxy objects all
over the place and explicitly "dereference" them by using the ref
attribute all over the place.

Another possibility is to subclass the list used to store the points,
in order to modify the way that it replaces items. That could look
something like this:

class UpdateList(list):
def __setitem__(self, index, item):
if isinstance(index, slice):
indices = range(* index.indices(len(self)))
items = list(item)
if len(indices) != len(items):
raise TypeError("Slice length does not match sequence length")
for i, x in zip(indices, items):
self.update(x)
else:
self[index].update(item)

Then you just need to add an "update" method to the Point class (and
any other class you might want to use this with) that would update the
Point's attributes to match those of another point. The downside here
is that this is not very Pythonic, in that somebody familiar with
Python who is reading the code would be surprised by the way the model
updates work.

A third possibility of course would be to just not try to do case B.

Cheers,
Ian
 
R

Rui Maciel

Terry said:
class Model:
points = []
lines = []

Unless you actually need keep the points and lines ordered by entry
order, or expect to keep sorting them by whatever, sets may be better
than lists. Testing that a point or line is in the model will be faster,
as will deleting one by its identify.

Thanks for the tip, Terry. Sounds great. I'll update my code.


Once again, thanks for the help,
Rui Maciel
 
R

Rui Maciel

Terry said:
Three answers:
Look how much trouble it has already caused ;-)
Since you are a self-declared newbie, believe us!
Since, be definition, useless code can do no good, it can only cause
trouble. Think about it.

I don't doubt that there might good reasons for that, but it is always
preferable to get the rationale behind a decision to be able to understand
how things work and how to do things properly.


Rui Maciel
 

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. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top