简体   繁体   中英

How do you know in advance if a method (or function) will alter the variable when called?

I am new to Python from R. I have recently spent a lot of time reading up on how everything in Python is an object, objects can call methods on themselves, methods are functions within a class, yada yada yada.

Here's what I don't understand. Take the following simple code:

mylist = [3, 1, 7]

If I want to know how many times the number 7 occurs, I can do:

mylist.count(7)

That, of course, returns 1. And if I want to save the count number to another variable:

seven_counts = mylist.count(7)

So far, so good. Other than the syntax, the behavior is similar to R. However, let's say I am thinking about adding a number to my list:

mylist.append(9)

Wait a minute, that method actually changed the variable itself! (ie, "mylist" has been altered and now includes the number 9 as the fourth digit in the list.) Assigning the code to a new variable (like I did with seven_counts) produces garbage:

newlist = mylist.append(9)

I find the inconsistency in this behavior a bit odd, and frankly undesirable. (Let's say I wanted to see what the result of the append looked like first and then have the option to decide whether or not I want to assign it to a new variable.)

My question is simple:

Is there a way to know in advance if calling a particular method will actually alter your variable (object)?

Aside from reading the documentation (which for some methods will include type annotations specifying the return value) or playing with the method in the interactive interpreter (including using help() to check the docstring for a type annotation), no, you can't know up front just by looking at the method.

That said, the behavior you're seeing is intentional. Python methods either return a new modified copy of the object or modify the object in place; at least among built-ins, they never do both (some methods mutate the object and return a non- None value, but it's never the object just mutated; the pop method of dict and list is an example of this case).

This either/or behavior is intentional; if they didn't obey this rule, you'd have had an even more confusing and hard to identify problem, namely, determining whether append mutated the value it was called on, or returned a new object. You definitely got back a list , but is it a new list or the same list ? If it mutated the value it was called on, then

newlist = mylist.append(9)

is a little strange; newlist and mylist would be aliases to the same list (so why have both names?). You might not even notice for a while; you'd continue using newlist , thinking it was independent of mylist , only to look at mylist and discover it was all messed up. By having all such "modify in place" methods return None (or at least, not the original object), the error is discovered more quickly/easily; if you try and use newlist , mistakenly believing it to be a list , you'll immediately get TypeError s or AttributeError s.

Basically, the only way to know in advance is to read the documentation. For methods whose name indicates a modifying operation, you can check the return value and often get an idea as to whether they're mutating. It helps to know what types are mutable in the first place; list , dict , set and bytearray are all mutable, and the methods they have that their immutable counterparts (aside from dict , which has no immutable counterpart) lack tend to mutate the object in place.

The default tends to be to mutate the object in place simply because that's more efficient; if you have a 100,000 element list , a default behavior for append that made a new 100,001 element list and returned it would be extremely inefficient (and there would be no obvious way to avoid it). For immutable types (eg str , tuple , frozenset ) this is unavoidable, and you can use those types if you want a guarantee that the object is never mutate in place, but it comes at a cost of unnecessary creation and destruction of objects that will slow down your code in most cases.

Just checkout the doc:

>>> list.count.__doc__
'L.count(value) -> integer -- return number of occurrences of value'
>>> list.append.__doc__
'L.append(object) -> None -- append object to end'

There isn't really an easy way to tell, but:

immutable object --> no way of changing through method calls

So, for example, tuple has no methods which affect the tuple as it is unchangeable so methods can only return new instances.

And if you "wanted to see what the result of the append looked like first and then have the option to decide whether or not I want to assign it to a new variable" then you can concatenate the list with a new list with one element.

ie

>>> l = [1,2,3]
>>> k = l + [4]
>>> l
[1, 2, 3]
>>> k
[1, 2, 3, 4]

Not from merely your invocation (your method call). You can guarantee that the method won't change the object if you pass in only immutable objects, but some methods are defined to change the object -- and will either not be defined for the one you use, or will fault in execution.

I Real Life, you look at the method's documentation: that will tell you exactly what happens.

[I was about to include what Joe Iddon's answer covers ...]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM