How to isolate a constant?

G

Gnarlodious

Say this:

class tester():
_someList = [0, 1]
def __call__(self):
someList = self._someList
someList += "X"
return someList

test = tester()

But guess what, every call adds to the variable that I am trying to
copy each time:
test()
[0, 1, 'X'] test()
[0, 1, 'X', 'X']


Can someone explain this behavior? And how to prevent a classwide
constant from ever getting changed?

-- Gnarlie
 
C

Chris Rebert

Say this:

class tester():

Style note: either have it explicitly subclass `object`, or don't
include the parens at all. Empty parens for the superclasses is just
weird.
       _someList = [0, 1]
       def __call__(self):
               someList = self._someList
               someList += "X"
               return someList

test = tester()

But guess what, every call adds to the variable that I am trying to
copy each time:
test()
[0, 1, 'X'] test()
[0, 1, 'X', 'X']


Can someone explain this behavior?

The line `someList = self._someList` does NOT copy the list. It make
`someList` point to the same existing list object. Hence,
modifications to that object from either variable will affect the
other.
Similarly, `someList += "X"` modifies someList *in-place*; it does not
produce a new list object.

The upshot is that you're just modifying and returning references to
*the same list* repeatedly, never producing a new list object.
And how to prevent a classwide
constant from ever getting changed?

Python doesn't have any language-enforced notion of constants. So,
short of writing/using a library to try and enforce such a notion,
you're out of luck. You could use an immutable datatype (e.g. a tuple)
instead of a mutable one (e.g. a list) as some level of safeguard
though.

Cheers,
Chris
 
M

MRAB

Say this:

class tester():
_someList = [0, 1]
def __call__(self):
someList = self._someList
someList += "X"
return someList

test = tester()

But guess what, every call adds to the variable that I am trying to
copy each time:
test()
[0, 1, 'X'] test()
[0, 1, 'X', 'X']


Can someone explain this behavior? And how to prevent a classwide
constant from ever getting changed?
'_someList' is part of the class itself.

This:

someList = self._someList

just creates a new _reference to the list and this:

someList += "X"

appends the items of the sequence "X" to the list.

Note that a string is also a sequence of characters, so:
['X', 'Y']

Python will copy something only when you tell it to copy. A simple way
of copying a list is to slice it:

someList = self._someList[:]
 
D

Dennis Lee Bieber

Say this:

class tester():
_someList = [0, 1]
def __call__(self):
someList = self._someList
someList += "X"
return someList

test = tester()

But guess what, every call adds to the variable that I am trying to
copy each time:

You never copied it... You just created a temporary local reference
to the same object

_someList = [0, 1]

creates a mutable list object containing two elements; it then binds the
/name/ "_someList" to that object.

someList = self._someList

finds the OBJECT to which "self._someList" is bound, and binds a second
name "someList" to the same object.

someList += "X"

retrieves the OBJECT to which "someList" is bound, mutates it by
appending an "X".

Nowhere do you ever create a NEW list object. Try

someList = self._someList[:]

which finds the object to which "self._someList" is bound, extracts a
sublist [:] (from first element to last element) -- this is a NEW list
-- and binds "someList" to that new object.
Can someone explain this behavior? And how to prevent a classwide
constant from ever getting changed?

"Ever"? Can't really be done with Python -- if you know it exists,
you can get to it and rebind it, or mutate it if it is a mutable object.
 
G

Gnarlodious

The line `someList = self._someList` does NOT copy the list. It make
`someList` point to the same existing list object.
Thanks for all those explanations, I've already fixed it with a tuple.
Which is more reliable anyway.

-- Gnarlie
 
8

88888 Dihedral

Thank you for the good trick for a static class owned property. Someone might object this but this is really useful.
 
S

Steven D'Aprano

Thanks for all those explanations, I've already fixed it with a tuple.
Which is more reliable anyway.

No, tuples are not "more reliable" than lists. Don't make the mistake of
confusing your inexperience and lack of understanding about Python's
object model for "lists are unreliable". They are completely reliable.
You just have to learn how they work, and not make invalid assumptions
about how they work.

You wouldn't say "Nails are more reliable than screws, because I hammered
a screw into a plaster wall and it just fell out." Of course it fell out:
you used it incorrectly for what you needed.
 
P

Paul Rudin

Gnarlodious said:
Thanks for all those explanations, I've already fixed it with a tuple.
Which is more reliable anyway.

neither of lists or tuples are "more reliable" than the other. They both
have perfectly well defined behaviour (which can be gleaned from reading
the documentation) and reliably behave as documented. You just have to
choose which fits better for the computation you're trying to implement.
 
A

Alan Meyer

Say this:

class tester():
_someList = [0, 1]
def __call__(self):
someList = self._someList
someList += "X"
return someList

test = tester()

But guess what, every call adds to the variable that I am trying to
copy each time:
test()
[0, 1, 'X'] test()
[0, 1, 'X', 'X']
....
Python will copy something only when you tell it to copy. A simple way
of copying a list is to slice it:

someList = self._someList[:]

And another simple way:

...
someList = list(self._someList)
...

Alan
 
I

Ian Kelly

Python will copy something only when you tell it to copy. A simple way
of copying a list is to slice it:

someList = self._someList[:]

And another simple way:

   ...
   someList = list(self._someList)
   ...

I generally prefer the latter. It's clearer, and it guarantees that
the result will be a list, which is usually what you want in these
situations, rather than whatever unexpected type was passed in.

Cheers,
Ian
 
D

Dennis Lee Bieber

Python will copy something only when you tell it to copy. A simple way
of copying a list is to slice it:

someList = self._someList[:]

And another simple way:

   ...
   someList = list(self._someList)
   ...

I generally prefer the latter. It's clearer, and it guarantees that
the result will be a list, which is usually what you want in these
situations, rather than whatever unexpected type was passed in.

Where's the line form to split those who'd prefer the first vs the
second result in this sample said:
unExpected = "What about a string"
firstToLast = unExpected[:]
repr(firstToLast) "'What about a string'"
explicitList = list(unExpected)
repr(explicitList)
"['W', 'h', 'a', 't', ' ', 'a', 'b', 'o', 'u', 't', ' ', 'a', ' ', 's',
't', 'r', 'i', 'n', 'g']"
 
I

Ian Kelly

Where's the line form to split those who'd prefer the first vs the
second result in this sample said:
unExpected = "What about a string"
firstToLast = unExpected[:]

Strings are immutable. That doesn't suffice to copy them, even
assuming you would want to do so in the first place.
True

If you find yourself needing to make a copy, that usually means that
you plan on modifying either the original or the copy, which in turn
means that you need a type that supports modification operations,
which usually means a list. If you pass in a string and then copy it
with [:] and then try to modify it, you'll get an exception. If you
don't try to modify it, then you probably didn't need to copy it in
the first place.

Cheers,
Ian
 
M

Mel

Dennis said:
Where's the line form to split those who'd prefer the first vs the
second result in this sample said:
unExpected = "What about a string"
firstToLast = unExpected[:]
repr(firstToLast) "'What about a string'"
explicitList = list(unExpected)
repr(explicitList)
"['W', 'h', 'a', 't', ' ', 'a', 'b', 'o', 'u', 't', ' ', 'a', ' ', 's',
't', 'r', 'i', 'n', 'g']"

Well, as things stand, there's a way to get whichever result you need. The
`list` constructor builds a single list from a single iterable. The list
literal enclosed by `[`, `]` makes a list containing a bunch of items.

Strings being iterable introduces a wrinkle, but `list('abcde')` doesn't
create `['abcde']` just as `list(1)` doesn't create `[1]`.

Mel.
 

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,731
Messages
2,569,432
Members
44,832
Latest member
GlennSmall

Latest Threads

Top