4

I have a class (list of dicts) and I want it to sort itself:

class Table(list):
…
  def sort (self, in_col_name):
    self = Table(sorted(self, key=lambda x: x[in_col_name]))

but it doesn't work at all. Why? How to avoid it? Except for sorting it externally, like:

new_table = Table(sorted(old_table, key=lambda x: x['col_name'])

Isn't it possible to manipulate the object itself? It's more meaningful to have:

class Table(list):
  pass

than:

class Table(object):
  l = []
  …
  def sort (self, in_col_name):
    self.l = sorted(self.l, key=lambda x: x[in_col_name])

which, I think, works. And in general, isn't there any way in Python which an object is able to change itself (not only an instance variable)?

Thomas Orozco
  • 53,284
  • 11
  • 113
  • 116
Gyula Sámuel Karli
  • 3,118
  • 2
  • 15
  • 18
  • 1
    Based on your terminology (both in the text of the question and in the comments for Thomas Orozco's answer), I think you may be getting confused about what "change" really means. In Python, there are two kinds of change: *mutation* and *(re)binding*. To put it simply, if an object changes state, it's mutation; if a name changes what it refers to, it's rebinding. You may find [this blog post](http://nedbatchelder.com/text/names.html) helpful to explain these concepts. – John Y Sep 03 '13 at 21:24
  • Does this answer your question? [Why is \`self\` in Python objects immutable?](https://stackoverflow.com/questions/1015592/why-is-self-in-python-objects-immutable) – BeRT2me Oct 23 '22 at 05:37

3 Answers3

17

You can't re-assign to self from within a method and expect it to change external references to the object.

self is just an argument that is passed to your function. It's a name that points to the instance the method was called on. "Assigning to self" is equivalent to:

def fn(a):
   a = 2
a = 1
fn(a)
# a is still equal to 1

Assigning to self changes what the self name points to (from one Table instance to a new Table instance here). But that's it. It just changes the name (in the scope of your method), and does affect not the underlying object, nor other names (references) that point to it.


Just sort in place using list.sort:

def sort(self, in_col_name):
    super(Table, self).sort(key=lambda x: x[in_col_name])
Thomas Orozco
  • 53,284
  • 11
  • 113
  • 116
  • Good idea, but it looks that list.sort () doesn't support the key argument. (At least it dropped a TypeError and in the Python doc I didn't find the key argument) Thx – Gyula Sámuel Karli Sep 03 '13 at 19:59
  • @GyulaSámuelKarli Whoops, my bad - we need to call the original `list.sort` method here, using `super`. We were calling `Table.sort` instead, which doesn't support it because we didn't implement it. – Thomas Orozco Sep 03 '13 at 20:04
  • @ThomasOrozco: and which would also cause infinite recursion ^^ – nneonneo Sep 03 '13 at 20:08
  • @nneonneo Which would be bad =) (but potentially a bit easier to debug! I've always found the documentation for `list.sort` quite hard to find! ;) ) – Thomas Orozco Sep 03 '13 at 20:09
  • @ThomasOrozco: I've added some additional considerations to the original question. – Gyula Sámuel Karli Sep 03 '13 at 20:19
  • @GyulaSámuelKarli My answer explains why this doesn't work in the first section. The second implementation you suggested works, but the first one will not. You can re-assign to self in your method, but when you go `table = Table(); table.sort()`, it's the code that calls `table.sort()` that holds a reference to the instance (the reference being `table`), so changing `self` will not change that reference. – Thomas Orozco Sep 03 '13 at 20:24
  • @ThomasOrozco: I understand this, but isn't there _any_ way in Python which an object is able to change itself (not only an instance variable)? – Gyula Sámuel Karli Sep 03 '13 at 20:36
  • 1
    @GyulaSámuelKarli You **can modify** an object, but you **can't replace** it. This is because your method does not control the references that exist to the object, other pieces of code do. – Thomas Orozco Sep 03 '13 at 20:39
  • I was playing a bit with this and it seems you can do what you had originally (`self.sort(key=lambda x: x[in_col_name])`). No need to call `super`, just as with `pop` in my answer. I'm using Python 2.7.3 and using `class Table(list):`. – Paulo Almeida Sep 03 '13 at 22:29
  • @PauloAlmeida Not if `Table` already implements `sort`, in which case you need to use `super` to have `list`'s implementation of `sort`. – Thomas Orozco Sep 03 '13 at 22:35
  • Ah I see, I named it `my_sort`. I didn't think of the overriding issue. – Paulo Almeida Sep 03 '13 at 22:38
1

Python is pass by value, always. This means that assigning to a parameter will never have an effect on the outside of the function. self is just the name you chose for one of the parameters.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • No it's not pass by value, it's just the opposite. The problem is assignment doesn't do what you think it does. – Mark Ransom Sep 04 '13 at 03:01
  • 1
    @MarkRansom: You are wrong. It is pass by value. You just don't know what pass by value means. – newacct Sep 04 '13 at 03:10
  • It may *look* like pass by value, but it is not. If you perform any mutation on a function parameter you'll find that it also changes the original object. Assignment is not a mutation unless it is to a member or slice of the object. See e.g. http://ideone.com/vP7m25 – Mark Ransom Sep 04 '13 at 03:55
  • 1
    @MarkRansom: It IS pass by value. Mutating the object pointed to by a variable is not assigning to the variable. Assignment-to-slice is not assignment -- it is calling a method (`.__setitem__()`) on the object pointed to by the variable. – newacct Sep 04 '13 at 07:49
  • I guess we're just going to have to disagree on what a "value" is. – Mark Ransom Sep 04 '13 at 13:40
  • @MarkRansom: So, are you familiar with Java, C, etc.? Do you agree that those languages are pass by value? – newacct Sep 04 '13 at 22:06
  • I am familiar with C and C++ where the difference is blindingly obvious. Pass by value makes a copy of the value that is totally disconnected from the original value used in the function call. In Python you're making a copy of the name, not the value, and both the original name and the name within the function give you the same object until you reassign one of them. – Mark Ransom Sep 04 '13 at 22:13
  • @MarkRansom: Well, sir, you are very mistaken. In Python, passing, as well as assigning, makes a copy of the value. All values in Python are pointers to objects. The same semantics exists in C. – newacct Sep 04 '13 at 22:22
  • As I said, we disagree on what a "value" is. I believe it is the object, you believe it is the pointer. Other than that I think we understand each other. – Mark Ransom Sep 04 '13 at 22:32
  • @MarkRansom: Well, under that definition, you can also say in C when you have a pointer, you "believe the value is the thing pointed to". That makes it pretty meaningless. In any case, we are not talking about what "value" is. We are talking about "pass by value", which is a term of language semantics. If the exact same semantics exist in two languages, they must be both "pass by value", or both not "pass by value". Show me any Python, and I will show you the C that is structurally identical, and you'll see that they are semantically the same. – newacct Sep 04 '13 at 23:07
  • 1
    The semantics that I have often heard used is that python is considered "call by object": http://effbot.org/zone/call-by-object.htm – qwwqwwq Sep 23 '13 at 15:07
  • @qwwqwwq: It doesn't matter what's "heard used"; it matters what it's actually equivalent to when you look at it. Making up a new name for something does not explain what it is. "Call by object", "call by sharing" or whatever terms people invent are all cases of call by value. – newacct Sep 23 '13 at 21:29
  • @newacct Pass by value has a clear definition where it is used in C-type languages, and python does not use pass by value in all cases. Consider: def incrementFirstElement(L): L[0] += 1 ; a = [0]; incrementFirstElement(a); print a[0]; This will output "1", which would not happen were python a strictly pass by value language as you suggest. Thus, your statement is at best conflating the semantics of pass by value, and at worst downright wrong. – RussellStewart Oct 24 '13 at 22:19
  • @user2237635: It is pass by value in ALL cases. Pass-by-value has a very specific semantic meaning -- assignment to a parameter (e.g. `L = something`) has no effect on the calling scope. This is always true in Python, as it is in Java, C, and other pass-by-value languages. Your code is not dealing with the semantics of assignment to a parameter, so it is not relevant. The code can be written in C with the same structure, and produce the same output: `void incrementFirst(int *L) { L[0] += 1; } int main() { int *a = malloc(sizeof(int)); a[0] = 1; incrementFirst(a); printf("%d\n", a[0]); }` – newacct Oct 25 '13 at 08:43
  • what a meaningless polemy... In Python assignment and parameter passing (which is essentially the same) is always by value, which means the exact same as in all languages C, Pascal, Java, C#. For this add the information, that all variables are reference variables, and you then must understand what happens when you change the parameter by assignment vs. change the parameter state. It is ridicoulus seeing two 100k+ arguing on words, without explaining clearly the thing to a beginner – g.pickardou Jul 15 '22 at 04:35
0

I was intrigued by this question because I had never thought about this. I looked for the list.sort code, to see how it's done there, but apparently it's in C. I think I see where you're getting at; what if there is no super method to invoke? Then you can do something like this:

class Table(list):
    def pop_n(self, n):
        for _ in range(n):
            self.pop()

>>> a = Table(range(10))
>>> a.pop_n(3)
>>> print a
[0, 1, 2, 3, 4, 5, 6]

You can call self's methods, do index assignments to self and whatever else is implemented in its class (or that you implement yourself).

Community
  • 1
  • 1
Paulo Almeida
  • 7,803
  • 28
  • 36