Python slice without copy? [duplicate] - python

This question already has answers here:
Can I create a "view" on a Python list?
(10 answers)
Closed 8 years ago.
Is there a way to create a "slice view" of a sequence in Python 3 that behaves like a regular slice but does not create a copy of the sliced part of the sequence? When the original sequence is updated, the "slice view" should reflect the update.
>>> l = list(range(100))
>>> s = Slice(l, 1, 50, 3) # Should behave like l[1:50:3]
>>> s[1]
4
>>> l[4] = 'foo'
>>> s[1] # Should reflect the updated value
'foo'
I can write my own Slice class that does this but I wanted to find out if there was a built-in way.

Use islice from itertools library
EDIT:
I see where I misunderstood the question.
Well, there is no such thing. If you want to create your class, you'll have to:
Keep a reference to the original list in you Slice class
Define, __iter__, __getitem__ and __setitem__ methods to work on the original list with index conversion

Related

Why does dict.pop() here remove the value from two dictonaries while specifing only one? [duplicate]

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 2 years ago.
I am writing a program in which I need to iterate through many long dictionaries, so I thought I could remove unneeded values from it, to save on some runtime. Here is a minimal working example with lacking context:
years = {0: 2019, 1: 2020, 2: 2021}
def iterate_years():
for i in years:
temp_years = years
print("years:", years)
print("temp_years:", temp_years)
temp_years.pop(i)
minimum = min(temp_years)
maximum = max(temp_years)
print("temp_years:", years)
print("years:", years)
iterate_years()
When I run this it returns a runtime error because the size f the dictionary changed while iteration. As you can see through the example the line temp_years.pop(i) seems to pop the key-value pair i in temp_years, as specified but also in years.
Why does this happen? I am not very experienced but years is never assigned to temp_years after the pop() so the value for years should stay like they were specified in the first line.
The problem here is that temp_years and years are pointing to the same object in memory (reference), you can check it comparing the id of both variables:
>>> id(temp_years) == id(years)
>>> True
If you want a copy, you have to create a new dict with temp_years = years.copy(). But if you have nested objects in the dictionary, you will have to do a deep copy, because dict.copy() only does a shallow copy.
>>> import copy
>>> temp_years = copy.deepcopy(years)
>>> id(temp_years)
140641196228824
>>> id(years)
140641197662072
You need to make a copy when you create the dict temp_years otherwise it will just function as a pointer to the original object (years). That's why the changes you're making to temp_years also change years. So all you need is to change that assignment line:
temp_years = years.copy()
You could also make it a deepcopy if it's a nested dict (otherwise you'll only get the top level keys):
import copy
temp_years = copy.deepcopy(years)
Check out this answer for more details: How to copy a dictionary and only edit the copy

Python 3 integer addresses [duplicate]

This question already has answers here:
The `is` operator behaves unexpectedly with non-cached integers
(2 answers)
Closed 1 year ago.
x=300
y=300
print(id(x),id(y))
a=[300,300]
print(id(a[0]),id(a[1]))
On executing above code I get different addresses for x and y but the same address for a[0] and a[1]. Can anyone tell me why that is happening?
Take a look at below example:
>>> a=256
>>> b=256
>>> print(id(a),id(b))
(31765012, 31765012)
>>>
>>> c=257
>>> d=257
>>> print(id(c),id(d))
(44492764, 44471284)
>>>
This will help you understand the unexpected behavior for integers. Whenever you create a int in range -5 to 256 you actually just get back a reference to the existing object. This is called Integer Caching in python.
In CPython, the C-API function that handles creating a new int object is PyLong_FromLong(long v). see the documentation on this link
EDIT: Now coming to the list. For the same list elements (larger integers) you are getting same id's because list is created at once or you can say in one go.
You can achieve similar behavior with integers as well, see below example with parallel assignment.
>>>
>>> a,b = 300,300
>>>
>>> print(id(a),id(b))
(36132288, 36132288)
>>>
Hope this will clear your doubts.

Set many variables to the same: Adding to one variable adds to many [duplicate]

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Closed 8 years ago.
I wanted to efficiently set a bunch of variables to an empty list. However, using this method, operations on one variable seem to apply to all:
>>> r1,r2,r3,r4,r5,r6,r7,r8=[[]]*8
>>> r1
[]
>>> r1+=[2]
>>> r1,r2
([2], [2])
Why is this? Also, what is the most efficient method that leaves each variable independent?
Doing [[]]*8 creates a list of eight references to the same list object. That is why they all get affected when you update one. To make eight unique lists, use a generator expression:
r1,r2,r3,r4,r5,r6,r7,r8 = ([] for _ in range(8))
You might also use a list comprehension:
r1,r2,r3,r4,r5,r6,r7,r8 = [[] for _ in range(8)]

How do I create a list of independent sets in python? [duplicate]

This question already has answers here:
List of lists changes reflected across sublists unexpectedly
(17 answers)
Why does this code for initializing a list of lists apparently link the lists together? [duplicate]
(1 answer)
Closed 8 years ago.
I have a list that needs to contain a variable number of independent sets.
When I run the following piece of code, I want to add the string "testing" to only the first set.
numberOfSets = 3
test = [set()]*numberOfSets
test[0].add("testing")
print test
However, when I print test, it shows three identical sets that all contain testing. How can I set up my list so I can separately access each set?
When you do [set()]*3, you create three sets that reference the same object, thus when you change one value, the others change. Use a list comprehension here instead:
>>> numberOfSets = 3
>>> test = [set() for _ in xrange(numberOfSets)]
>>> test[0].add("testing")
>>> print test
[set(['testing']), set([]), set([])]
You can do
test = [set() for _ in xrange(numberOfSets)] # use 'range' in Python 3.x
[set()]*x creates a list with x of the same set instance, whereas the comprehension above creates a new, independent set on each iteration, as desired.
In general, you should be very cautious whenever you multiply lists whose elements are mutable.

How to get a copy from a list? [duplicate]

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 9 years ago.
I am trying to get an element from list and make some change on this element (which is also a list). Weirdly, the change applied on the previous list. Here is my code:
>>>sentences[0]
['<s>/<s>',
'I/PRP',
'need/VBP',
'to/TO',
'have/VB',
'dinner/NN',
'served/VBN',
'</s>/</s>']
>>>sentence = sentences[0]
>>>sentence.insert(0,startc); sentence.append(endc)
>>>sentences[0]
['<s>/<s>',
'<s>/<s>',
'I/PRP',
'need/VBP',
'to/TO',
'have/VB',
'dinner/NN',
'served/VBN',
'</s>/</s>'
'</s>/</s>']
It is like I just got a pointer to that element, not a copy
You do get a "pointer", in fact. Lists (and any mutable value type!) are passed around as reference in Python.
You can make a copy of a list by passing it to the list() object constructor, or by making a full slice using [:].
a = [1,2,3]
b = a
c = list(a)
d = a[:]
a[1] = 4 # changes the list referenced by both 'a' and 'b', but not 'c' or 'd'
You're exactly right! In Python, when you pass a list as an argument to a function, or you assign a list to another variable, you're actually passing a pointer to it.
This is for efficiency reasons; if you made a separate copy of a 1000-item list every time you did one of the aforementioned things, the program would consume way too much memory and time.
To overcome this in Python, you can duplicate a one-dimensional list using = originalList[:] or = list(originalList):
sentence = sentences[0][:] # or sentence = list(sentences[0])
sentence.insert(0,startc)
sentence.append(endc)
print(sentence) # modified
print(sentences[0]) # not modified
Consider using list comprehension if you need to duplicate a 2D list.

Categories