tags:

views:

231

answers:

4

I have a function that takes a string-like argument.

I want to decide if I can safely store the argument and be sure that it won't change. So I'd like to test if it's mutable, e.g the result of a buffer() built from an array.array(), or not.

Currently I use:

type(s) == str

Is there a better way to do it?

(copying the argument is too costly, that's why I want to avoid it)

+3  A: 

Just use duck typing -- remember, it's "Easier to Ask for Forgiveness Than Permission". Try to mutate the string-like object, and be prepared to catch an exception if you can't.

Daniel Pryden
And realize that this isn't waterproof; someone can always pass in an object that looks like a string, but has a method that effectively changes its value. For example, a string-like object could have a force_upper() method that doesn't change the string itself, but causes getters to return uppercase data. If you have important invariants that depend on knowing if you're receiving something mutable, be sure to document it--since most APIs don't care.
Glenn Maynard
This is true, but the OP wants to ensure the argument *won't* be modified.
bstpierre
The idea is nice (and pythonic), but it's tricky to test if mutating works while keeping the string "unmodified" at the same time (`a[0] = a[0]` + special case for empty string). And btw, try/except is slower than an if statement if you except an exception to be raised.
tonfa
+4  A: 

It would be better to use

isinstance(s, basestring)

It works for Unicode strings too.

Bastien Léonard
I don't need it to work for unicode, it's really for bytes (I'm working on a VCS).
tonfa
It also has the advantage of working for classes derived for `str`.
Bastien Léonard
+2  A: 

I'd just convert it to an immutable string:

>>> s1 = "possibly mutable"
>>> 
>>> s2 = str(s1)
>>> s1 is s2
True

In case s1 is immutable the same object is given back, resulting in no memory overhead. If it's mutable a copy is being made.

Georg
Doesn't work so well if s1 is not an actual str:>>> s1 = u"Foo">>> s2 = str(s1)>>> s2 is s1False
bstpierre
It doesn't work, a copy is too costly here.
tonfa
Could you elaborate on why "a copy is too costly here"? This is the natural Pythonic way to solve your problem: if you pass an immutable string to str() it does nothing, quickly; if you pass in something mutable, you get a safe immutable string back. It does the minimal work needed to ensure immutability. I'm trying to imagine when this would be too expensive, and I'm not seeing it yet.
steveha
@steveha: the string is huge (think like all the filenames from the linux kernel with an additional 40 bytes hash for each), doing an unconditional copy here results in my benchmark being 3x slower (30s vs 110s for applying 375 patches to the repository).
tonfa
+4  A: 

If it's just a heuristic for your caching, just use whatever works. isinstance(x, str), for example, almost exactly like now. (Given you want to decide whether to cache or not; a False-bearing test just means a cache miss, you don't do anything wrong.)


(Remark: It turns out that buffer objects are hashable, even though their string representation may change under your feet; The hash discussion below is interesting, but is not the pure solution it was intended to be.)

However, well implemented classes should have instances being hashable if they are immutable and not if they are mutable. A general test would be to hash your object and test for success.

>>> hash({})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: dict objects are unhashable

This will give false positives I'm sure, but mutable objects being hashable is strictly an interface break; I would expect python library types to obey this interface, a test of a small sample gives correct answers:

hashabe: str (Immutable), buffer (Warning, immutable slice of (possibly) mutable object!)
unhashable: list, array.array
kaizer.se
If you want to avoid the exception, you could do this as well:if '__hash__' in dir(obj) and obj.__hash__: # do stuff
Ian Clelland
Yeah, it seems there's not really a better way to do that. For fun I timed isinstance() vs type() and isinstance() is slower when it returns false. Is it because it does a try/except internally?
tonfa
@kaizer.se: buffer is mutable (if you create a buffer from an array.array)
tonfa
tonfa: Did you try to create one that way and call hash() on it? I tested hash(buffer("abc"))
kaizer.se
kaizer: `a = array.array('c', 'foobar'); b = buffer(a); hash(b); a.append('x'); hash(b)`. b was changed while the hash stayed the same.
tonfa
tonfa: Of course you are right. I simply did not know, but I verified as well. The repr is misleading: `<read-only buffer for 0x480a5880, size 1, offset 0 at 0x4808e7a0>`. Did you also notice that `hash(b)` (buffer b) stays constant even if `str(b)` ("content") changes?
kaizer.se
kaizer: yes, and I was surprised as well
tonfa
I like this solution, but pleasing remove the part about hashing since it doesn't work.
tonfa
tonfa: Thank you. I'm reluctant to edit out a nice piece of discussion/discovery, so there is only a remark about it not working.
kaizer.se