How to make a class method in place? - python

Is there a way to make a method in a python class modify its data in place, specifically for lists?
For example, I want to write a function that behave like list.append() by modifying the origonal list instead of returning a new one
I have already
class newlist(list):
def add(self, otherlist):
self = self+otherlist
A method written like that does not seem to modify the variable it is called on.
Obviosly, I could add return self at the end, but then it would have to be called with mylist = mylist.add(stuff) to actually modify mylist. How do write the function so it will modify mylist when called with just mylist.add(stuff)?

Since newlist is a subclass of list it already has a method that does exactly what you want: extend. You don't have to write any code in newlist at all.
But if you really want to reinvent the wheel you can call extend within your new add method and get the same result:
class newlist(list):
def add(self, otherlist):
self.extend(otherlist)
mylist = newlist()
mylist.append(0)
mylist.extend([1,2,3])
print(mylist)
mylist = newlist()
mylist.append(0)
mylist.add([1,2,3])
print(mylist)
[0, 1, 2, 3]
[0, 1, 2, 3]

Plain assignment to self will rebind it; self is bound to the new object, and you've lost the reference to the original type.
The easiest approach here it to use lists existing overload for augmented assignment, +=:
class newlist(list):
def add(self, otherlist):
self += otherlist
That mutates self in place, rather than making a new object and reassigning it (it works because list is a mutable type; it wouldn't work for an immutable type without an overload for +=). You could also implement it as self.extend(otherlist), or for extra cleverness, don't even bother to write a Python level implementation at all and just alias the existing list method:
class newlist(list):
add = list.__iadd__ # Or add = list.extend
Since the definition of add is basically identical to existing += or list.extend behavior, just under a new name, aliasing concisely gets the performance of the built-in function; the only downside is that introspection (print(newline.add)) will not indicate that the function's name is add (because it's __iadd__ or extend; aliasing doesn't change the function metadata).

Try using the in-place addition += for your function:
class newlist(list):
def add(self, other):
self += other
a = newlist([1,2])
b = newlist([3,4])
a.add(b)
a
# returns:
[1, 2, 3, 4]

Related

How to assign another list to a list "passed by reference"? [duplicate]

This question already has answers here:
How do I pass a variable by reference?
(39 answers)
Closed 4 years ago.
It is believed that lists in python as a mutable object are passed by reference. However when I try to change the list passed to a function to be assigned by another list, particularly empty list, the change is not observable in the caller.
Giving more details here is my code:
def callee(l):
l = list() # this could be any list but my interest was empty list
def caller():
l = [1, 2, 3]
caller(l)
print(l) # prints [1, 2, 3] while I expect an empty list
How can I remove all elements of a list in the callee function?
When I change one particular element of a list in the callee, it is observable in the caller. What kind of passing is this, call-by-value or call-by-reference? I believe neither and I appreciate any clarification on this too.
You are just reassigning a local variable in the scope of callee.
So, just change the content of that list instead:
def callee(l):
l[:] = list()
# or l.clear()
You're changing the reference, instead of modifying the object referenced to. To see a difference, you need to actually change the object you pass by reference:
def callee(l):
while len(l) > 0:
l.pop()
def caller():
l = [1, 2, 3]
callee(l)
print(l) # prints []
Each time you use the = operator you are changing the object the variable is pointing to.
When you type
l = [1, 2, 3]
A python list is created in memory, and a reference in the scope of caller() named l is now pointing to this newly created list.
However, when the callee() function is called, another list is created in memory and now it has one reference in the scope of callee() pointing to it.
So now we have 2 lists in memory (and not one) each with its own reference pointer (named l in both cases but in different scopes). When callee() returns, the reference to the second list expires and the object now has no more pointers pointing to it, so the garbage collector deletes it from memory. The original list is left unchanged.
To clear the list up, you can use l.clear(). Since you didn't use the = operator, no new object is created in memory and you can be sure that you are pointing to the same old object.

Why is __getattr__ not called for indexing operations?

My question:
It seems that __getattr__ is not called for indexing operations, ie I can't use __getattr__ on a class A to provide A[...]. Is there a reason for this? Or a way to get around it so that __getattr__ can provide that functionality without having to explicitly define __getitem__, __setitem__, etc on A?
Minimal Example:
Let's say I define two nearly identical classes, Explicit and Implicit. Each creates a little list self._arr on initiation, and each defines a __getattr__ that just passes all attribute requests to self._arr. The only difference is that Explicit also defines __getitem__ (by just passing it on to self._arr).
# Passes all attribute requests on to a list it contains
class Explicit():
def __init__(self):
self._arr=[1,2,3,4]
def __getattr__(self,attr):
print('called __getattr_')
return getattr(self._arr,attr)
def __getitem__(self,item):
return self._arr[item]
# Same as above but __getitem__ not defined
class Implicit():
def __init__(self):
self._arr=[1,2,3,4]
def __getattr__(self,attr):
print('called __getattr_')
return getattr(self._arr,attr)
This works as expected:
>>> e=Explicit()
>>> print(e.copy())
called __getattr_
[1, 2, 3, 4]
>>> print(hasattr(e,'__getitem__'))
True
>>> print(e[0])
1
But this doesn't:
>>> i=Implicit()
>>> print(i.copy())
called __getattr_
[1, 2, 3, 4]
>>> print(hasattr(i,'__getitem__'))
called __getattr_
True
>>> print(i.__getitem__(0))
called __getattr_
1
>>> print(i[0])
TypeError: 'Implicit' object does not support indexing
Python bypasses __getattr__, __getattribute__, and the instance dict when looking up "special" methods for implementing language mechanics. (For the most part, special methods are ones with two underscores on each side of the name.) If you were expecting i[0] to invoke i.__getitem__(0), which would in turn invoke i.__getattr__('__getitem__')(0), that's why that didn't happen.

In Python, can I define an instance method map() for the list class?

I was hoping to define an instance method map() or join() for the list class (for array). For example, for map():
class list:
def map(self, fn):
result = []
for i in self:
result.append(fn(i))
return result
print [1, 3, 5].map(str)
Is it possible in Python to do that for the list class? (if not, can the last line [1, 3, 5].map(str) be made to work?)
Your code creates a new variable called list, which hides the original. You can do a little better by inheriting:
class mylist(list):
def map(self, fn):
result = []
for i in self:
result.append(fn(i))
return result
mylist([1, 3, 5]).map(str)
Note that it is not possible to override [] to generate anything other than a builtins.list
So that leaves monkeypatching the builtin. There's a module for that, forbiddenfruit, which in its own words:
may lead you to hell if used on production code.
If hell is where you're aiming for, then this is what you want:
from forbiddenfruit import curse
def list_map(self, fn):
result = []
for i in self:
result.append(fn(i))
return result
curse(list, "map", list_map)
print [1, 3, 5].map(str)

python- why the list doesn't modified?

>>> c=[1,100,3]
>>>def nullity (lst):
lst=[]
>>>nullity (c)
>>>c
[1,100,3]
Why c doesn't return []? Isn't nullity(c) mean c=lst so thy both point now at []?
Python has pass-by-value semantics, meaning that when you pass a parameter to a function, it receives a copy to the object's reference, but not the object itself. So, if you reassign a function parameter to something else (as you did), the assignment is local to the function, the original object "outside" the function remains unchanged. A completely different situation happens if you change something inside the object:
def test(a):
a[0] = 0
lst = [1, 2, 3]
test(lst)
lst
=> [0, 2, 3]
In the above snippet the list's elements were changed, that's because both lst and a are pointing to the exact same object. Now back to the original question - notice that in Python 3.2 and below there isn't a clear() method (it was added in Python 3.3), here is a related discussion. But you can do this for the same effect, and it'll work in Python 2.x and 3.x:
def nullity(lst):
del lst[:]
You're reassigning local variable lst to a new empty list. To empty an existing list, you need to delete all its members:
del lst[:]
Lets use the id() function
In [14]: c=[1,2,3]
In [15]: def nullity (lst):
...: print id(lst)
...: lst=[]
...: print id(lst)
...:
In [16]: id(c)
Out[16]: 33590520
In [17]: nullity(c)
33590520
33591024
When doing the reassignment a new local object is created which is not the same as the one used when calling the function.
Python does not allow you to replace parameters. You are only changing what "lst" refers to within the function, but it does not affect the calling code.
Instead change the list itself by clearing it:
def nullity (lst):
lst.clear() # With Python >= 3.3

Basic python. How to forbid functions to change the list?

>>> def test():
... a.remove(1)
>>> a = [1,2]
>>> test()
>>> print a
[2]
Why does a equal [2] rather than [1,2]?
List is mutable. If you pass it to a function, and the function changes it, it stays changed.
Use an immutable structure: tuple: a = (1,2)
Pass a copy of original list: b = list(a); b.remove(1) — now a and b have different contents, a hasn't changed.
Also, try not to use mutable global data. Either pass a to the function, or have a as an attribute of an object, and the function as its method.
It's not clear what you want. Your test() function modifies the global 'a' list, so it's unsurprising that 'a' gets modified.
If you want 'test' to work on a copy of a instead directly on a, you may copy it first.
For example,
def test():
a2 = list(a)
a2.remove(1)
Lists are mutable objects, they are meant to be changed. If you want to forbid changes, convert it to a tuple (e.g. a = (1, 2)) instead. Tuples are immutable, so it's not possible to change them without copying and re-assigning the variable.
Because the list a exists in the global namespace and when you call a remove on it, the value 1 is removed.
If you don't want it to be modified, simply create a new list. If you call remove on the list a, of course it going to remove the value.

Categories

Resources