Values change unexpectedly after function is called - python

can you lease tell me why S[f'{colors[0]}'] is changing after calling this function, and how to fix it.
S = {"1": list(range(0,5)), "2": list(range(20,25)), "3": list(range(10,15))}
colors = [1, 2 ,3]
def count_bycolor(colors):
countries_bycolor = S[f'{colors[0]}']
for i in range(1, len(colors)):
countries_bycolor.extend(S[f'{colors[i]}'])
return countries_bycolor
count_bycolor(colors)
len(S[f'{colors[0]}'])
count_bycolor(colors)
len(S[f'{colors[0]}'])
Thank for your help, and happy holidays!

You are performing operations on a list in a dict. Both of these are mutable objects and due to python being passed by reference pass-by-object-reference that has this consequence of changing your "original" object (it's the same object).
This means that you need to make copies of those objects if you want to operate on them without changing the original.
Based on your question it could be as simple as one line change:
import copy
def count_bycolor(colors):
countries_bycolor = copy.copy(S[f'{colors[0]}'])
for i in range(1, len(colors)):
countries_bycolor.extend(S[f'{colors[i]}'])
return countries_bycolor
count_bycolor(colors)
>>> [0, 1, 2, 3, 4, 20, 21, 22, 23, 24, 10, 11, 12, 13, 14]
S
>>> {'1': [0, 1, 2, 3, 4], '2': [20, 21, 22, 23, 24], '3': [10, 11, 12, 13, 14]}

Related

Unexpected None printed after shuffling list

from random import *
day = list(range(1, 29))
day = day[3:29]
shuffleday = shuffle(day)
print(shuffleday)
The result is None. What's wrong?
random.shuffle does not return anything. It modifies the input list.
import random
day=list(range(1,29))
print(day)
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]
day=day[3:29]
print(day)
# [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]
shuffleday=random.shuffle(day)
print(shuffleday)
# None
print(day)
# [27, 9, 14, 15, 7, 17, 28, 10, 23, 21, 16, 12, 6, 11, 22, 25, 24, 20, 5, 19, 13, 4, 18, 8, 26]
random.shuffle modifies day. It does not return anything.
This is per convention in Python, where functions which modify the arguments given to them return None.
If you wish to generate a new list shuffled, without modifying the original, you may wish to use random.sample.
from random import *
day = list(range(1, 29))
shuffleday = sample(day[3:29], len(day2))
print(shuffleday)
If you simply wish to get a random element from a list, you can use random.choice.
from random import *
day = list(range(1, 29))
randomday = choice(day[3:29])
print(randomday)
To get a random number from day list use choice instead of shuffle, it will return a value of random element from day[3:29]:
from random import *
day = list(range(1, 29))
day = day[3:29]
shuffleday = choice(day)
print(shuffleday)
The value of shuffleday is None because the shuffle function does not return a new list, it just changes the order of the elements in the list supplied as an argument.
This will return a value of random number. Choose one:
shuffleday = choice(range(3,29))
or
shuffleday = randint(3,29)
or
shuffleday = randrange(3,29)
in fact the shuffle function does not return anything, it shuffles the elements in place (in day for your case).
try print(day) instead
random.shuffle modifies the list in place, it doesn't return a value. so you can just replace your second last line with
shuffle(day)
then simply print shuffle without making your new variable.
Additionally I would say it's better practice to just import what you need from random, rather than doing a * import and cluttering your namespace (especially considering you are new to the language and unlikely to know the name of every function in the random library). In this case you can do:
from random import shuffle

This particular way of using .map() in python

I was reading an article and I came across this below-given piece of code. I ran it and it worked for me:
x = df.columns
x_labels = [v for v in sorted(x.unique())]
x_to_num = {p[1]:p[0] for p in enumerate(x_labels)}
#till here it is okay. But I don't understand what is going with this map.
x.map(x_to_num)
The final result from the map is given below:
Int64Index([ 0, 3, 28, 1, 26, 23, 27, 22, 20, 21, 24, 18, 10, 7, 8, 15, 19,
13, 14, 17, 25, 16, 9, 11, 6, 12, 5, 2, 4],
dtype='int64')
Can someone please explain to me how the .map() worked here. I searched online, but could not find anything related.
ps: df is a pandas dataframe.
Let's look what .map() function in general does in python.
>>> l = [1, 2, 3]
>>> list(map(str, l))
# ['1', '2', '3']
Here the list having numeric elements is converted to string elements.
So, whatever function we are trying to apply using map needs an iterator.
You probably might have got confused because the general syntax of map (map(MappingFunction, IteratorObject)) is not used here and things still work.
The variable x takes the form of IteratorObject , while the dictionary x_to_num contains the mapping and hence takes the form of MappingFunction.
Edit: this scenario has nothing to with pandas as such, x can be any iterator type object.

Why are there so many refcounts for my variable?

In my attempt to understanding the Python GIL (in Python 3.7.6.
), I played with sys.getrefcount() and the results are a bit bizarre.
From the documentation for sys.getrefcount(object)
Return the reference count of the object. The count returned is generally one higher than you might expect, because it includes the (temporary) reference as an argument to getrefcount().
In an attempt to grok it myself, here's the progression/confusion:
Firstly, should sys.getrefcount(object) work on values/literals? (please correct my terminology if I'm wrong), and why are the refcounts so random?
>>> import sys
>>> [sys.getrefcount(i) for i in range (10)]
[320, 195, 128, 43, 69, 24, 32, 18, 44, 17]
>>> [sys.getrefcount(str(i)) for i in range (10)] #refcount of every value is same now (?)
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
Then, I tried to explore further
>>> # Let's probe further
>>> import random
>>> [sys.getrefcount(str(random.randint(1,20))) for i in range (10)]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
>>> [sys.getrefcount(str(random.randint(1,20)*'a')) for i in range (10)]
[1, 1, 1, 1, 14, 1, 1, 1, 1, 1] # not every item is same
>>> [sys.getrefcount(random.choice('abcde')) for i in range (10)]
[20, 20, 12, 12, 9, 13, 12, 13, 12, 12]
>>> [sys.getrefcount(str(random.choice('abcde'))) for i in range (10)]
[9, 20, 12, 12, 12, 9, 12, 12, 12, 12]
What is going on above? I'm not sure if all the behaviors can be explained with just one misunderstanding that I might have or there are multiple things at play here. Please feel safe in assuming that the above lines were run sequentially in the Python Interpreter and nothing else was there that is not here.
For the question to make more sense, everything began here:
>>> sys.getrefcount(1)
187
>>> a = 1
>>> sys.getrefcount(a)
185
EDIT: I get it all, but why should sys.getrefcount(1) be so high?
Firstly, should sys.getrefcount(object) work on values/literals? (please correct my terminology if I'm wrong)
Yes. The literal is an expression that returns an object. That object might be cached (e.g., small numbers) or not (arbitrary strings).
, and why are the refcounts so random?
Coincidence/idiosyncracies of the interpreter.
>>> [sys.getrefcount(i) for i in range (10)]
[320, 195, 128, 43, 69, 24, 32, 18, 44, 17]
Small numbers literals are cached in CPython; they're all referring to the same object in memory, and whatever object is in memory has a reference to them. In this case, CPython might keep a reference to the small numbers cache for loops.
>>> [sys.getrefcount(str(i)) for i in range (10)] #refcount of every value is same now (?)
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
These are new objects, not searched in cache.
For the rest, strings are usually cached, and some references were in memory at the time.
>>> sys.getrefcount(1)
187
>>> a = 1
>>> sys.getrefcount(a)
185
a is merely a reference to the (usually cached) "1", and whatever difference there is between refcounts probably refers to whatever process the REPL carries in order to have a reference to the literal and print it.

Replace entry in specific numpy array stored in dictionary

I have a dictionary containing a variable number of numpy arrays (all same length), each array is stored in its respective key.
For each index I want to replace the value in one of the arrays by a newly calculated value. (This is a very simplyfied version what I'm actually doing.)
The problem is that when I try this as shown below, the value at the current index of every array in the dictionary is replaced, not just the one I specify.
Sorry if the formatting of the example code is confusing, it's my first question here (Don't quite get how to show the line example_dict["key1"][idx] = idx+10 properly indented in the next line of the for loop...).
>>> import numpy as np
>>> example_dict = dict.fromkeys(["key1", "key2"], np.array(range(10)))
>>> example_dict["key1"]
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> example_dict["key2"]
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> for idx in range(10):
example_dict["key1"][idx] = idx+10
>>> example_dict["key1"]
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
>>> example_dict["key2"]
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
I expected the loop to only access the array in example_dict["key1"], but somehow the same operation is applied to the array stored in example_dict["key2"] as well.
>>> hex(id(example_dict["key1"]))
'0x26a543ea990'
>>> hex(id(example_dict["key2"]))
'0x26a543ea990'
example_dict["key1"] and example_dict["key2"] are pointing at the same address. To fix this, you can use a dict comprehension.
import numpy
keys = ["key1", "key2"]
example_dict = {key: numpy.array(range(10)) for key in keys}

Alternate for range in python

If I have to generate natural numbers, I can use 'range' as follows:
list(range(5))
[0, 1, 2, 3, 4]
Is there any way to achieve this without using range function or looping?
You could use recursion to print first n natural numbers
def printNos(n):
if n > 0:
printNos(n-1)
print n
printNos(100)
Based on Nihal's solution, but returns a list instead:
def recursive_range(n):
if n == 0:
return []
return recursive_range(n-1) + [n-1]
Looping will be required in some form or another to generate a list of numbers, whether you do it yourself, use library functions, or use recursive methods.
If you're not opposed to looping in principle (but just don't want to implement it yourself), there are many practical and esoteric ways to do it (a number have been mentioned here already).
A similar question was posted here: How to fill a list. Although it has interesting solutions, they're all still looping, or using range type functions.
Well, yes, you can do this without using range, loop or recursion:
>>> num = 10
>>> from subprocess import call
>>> call(["seq", str(num)])
You can even have a list (or a generator, of course):
>>> num = 10
>>> from subprocess import check_output
>>> ls = check_output(["seq", str(num)])
>>> [int(num) for num in ls[:-1].split('\n')]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42]
But...what's the purpose?

Categories