Is there a way to group names together in python, to repeatedly assign to them en masse?
While we can do:
a,b,c = (1,2,3)
I would like to be able to do something like:
names = a,b,c
*names = (3,2,1) # this syntax doesn't work
a,b,c == (3,2,1) #=> True
Is there a built-in syntax for this? If not, I assume it would be possible with an object that overloads its assignment operator. In that case, is there an existing implementation, and would this concept have any unexpected failure modes?
The point is not to use the names as data, but rather to be able to use the actual names as variables that each refer to their own individual item, and to be able to use the list as a list, and to avoid code like:
a = 1
b = 2
c = 3
sequence = (a,b,c)
You should go one level up in your data abstraction. You are not trying to access the entries by their individual names -- you rather use names to denote the whole collection of values, so a simple list might be what you want.
If you want both, a name for the collection and names for the individual items, then a dictionary might be the way to go:
names = "a b c".split()
d = dict(zip(names, (1, 2, 3)))
d.update(zip(names, (3, 2, 1)))
If you need something like this repeatedly, you might want to define a class with the names as attributes:
class X(object):
def __init__(self, a, b, c):
self.update(a, b, c)
def update(self, a, b, c)
self.a, self.b, self.c = a, b, c
x = X(1, 2, 3)
x.update(3, 2, 1)
print x.a, x.b. x.c
This reflects that you want to block a, b and c to some common structure, but keep the option to access them individually by name.
This?
>>> from collections import namedtuple
>>> names = namedtuple( 'names', ['a','b','c'] )
>>> thing= names(3,2,1)
>>> thing.a
3
>>> thing.b
2
>>> thing.c
1
You should use a dict:
>>> d = {"a": 1, "b": 2, "c": 3}
>>> d.update({"a": 8})
>>> print(d)
{"a": 8, "c": 3, "b": 2}
I've realised that "exotic" syntax is probably unnecessary. Instead the following achieves what I wanted: (1) to avoid repeating the names and (2) to capture them as a sequence:
sequence = (a,b,c) = (1,2,3)
Of course, this won't allow:
*names = (3,2,1) # this syntax doesn't work
a,b,c == (3,2,1) #=> True
So, it won't facilitate repeated assignment to the same group of names without writing out those names repeatedly (except in a loop).
Well, you shouldn't do this, since it's potentially unsafe, but you can use the exec statement
>>> names = "a, b, c"
>>> tup = 1,2,3
>>> exec names + "=" + repr(tup)
>>> a, b, c
(1, 2, 3)
Python has such an elegant namespace system:
#!/usr/bin/env python
class GenericContainer(object):
def __init__(self, *args, **kwargs):
self._names = []
self._names.extend(args)
self.set(**kwargs)
def set(self, *args, **kwargs):
for i, value in enumerate(args):
self.__dict__[self._names[i]] = value
for name, value in kwargs.items():
if name not in self._names:
self._names.append(name)
self.__dict__[name] = value
def zip(self, names, values):
self.set(**dict(zip(names, values)))
def main():
x = GenericContainer('a', 'b', 'c')
x.set(1, 2, 3, d=4)
x.a = 10
print (x.a, x.b, x.c, x.d,)
y = GenericContainer(a=1, b=2, c=3)
y.set(3, 2, 1)
print (y.a, y.b, y.c,)
y.set(**dict(zip(('a', 'b', 'c'), (1, 2, 3))))
print (y.a, y.b, y.c,)
names = 'x', 'y', 'z'
y.zip(names, (4, 5, 6))
print (y.x, y.y, y.z,)
if __name__ == '__main__':
main()
Each instance of GenericContainer is an isolated namespace. IMHO it is better than messing with the local namespace even if you are programming under a pure procedural paradigm.
Not sure whether this is what you want...
>>> a,b,c = (1,2,3)
>>> names = (a,b,c)
>>> names
(1, 2, 3)
>>> (a,b,c) == names
True
>>> (a,b,c) == (1,2,3)
True
Related
I have three variables
a = 1
b = 2
c = 3
and I want to have a string like 'a=1, b=2, c=3'
so, I use f-string,
x = ''
for i in [a, b, c]:
x += f"{i=}"
but it gives,
x
'i=1, i=2, i=3, '
how do I make the i to be a, b, and c?
The list [a, b, c] is indistiguishable from the list [1, 2, 3] -- the variables themselves are not placed in the list, their values are, so there is no way to get the variable names out of the list after you've created it.
If you want the strings a, b, c, you need to iterate over those strings, not the results of evaluating those variables:
>>> ', '.join(f"i={i}" for i in "abc")
'i=a, i=b, i=c'
If you want to get the values held by the variables with those names, you can do this by looking them up in the globals() dict:
>>> a, b, c = 1, 2, 3
>>> ', '.join(f"{var}={globals()[var]}" for var in "abc")
'a=1, b=2, c=3'
but code that involves looking things up in globals() is going to be really annoying to debug the first time something goes wrong with it. Any time you have a collection of named values that you want to iterate over, it's better to just put those values in their own dict instead of making them individual variables:
>>> d = dict(a=1, b=2, c=3)
>>> ', '.join(f"{var}={val}" for var, val in d.items())
'a=1, b=2, c=3'
A list doesn't remember the names of variables assigned to it. For that, you need a dictionary.
x = ""
my_dict = {'a': a, 'b': b, 'c': c}
for k, v in my_dict.items():
x += f"{k}={v}, "
Using gloabls is not always a good idea if you want a solution that aovid using them you can inspect the variables that are declare using inspect module, there is thread regarding the getting the name of varibles here, from were the I took the function.
import inspect
def retrieve_var(var):
callers_local_vars = inspect.currentframe().f_back.f_locals.items()
return [var_name for var_name, var_val in callers_local_vars if var_val is var]
and now you can use a loop as similar to that you where using
a, b, c = 1, 2, 3
x = ''
for i in [a, b, c]:
var = retrieve_var(i)
x += f"{var[0]}={i}, "
Sorry if I'm asking something dumb. I'm begginer btw.
Can I somehow place multiple variables in one variable, like:
login = "user"
enter = input(login + ">")
commandLogin = "login"
commandRegister = "register"
commandExit = "exit"
commandList = commandLogin, commandRegister, commandExit
while enter != commandList:
print("incorrect command!")
enter = input(login + ">")
Well, in the example you're already doing that.
The main correction is that you probably want while enter not in commandList
You can use an 'iterable', such as a tuple or a list. Check here: Python docs
So for your code you can use:
login = "user"
commandLogin = "login"
commandRegister = "register"
commandExit = "exit"
commandList = [commandLogin, commandRegister, commandExit]
enter = input(login + ">")
while enter not in commandList:
print("Incorrect command!")
enter = input(login + ">")
Sure. You can use a dictionary to put multiple variables in one variable:
>>> a={}
>>> a['first']='this is the first'
>>> a['second']='the second'
>>> a
{'first': 'this is the first', 'second': 'the second'}
>>> a['first']
'this is the first'
>>>
You can also create a class, an object of the class, and access attributes:
>>> class Empty():
... pass
...
>>> a = Empty()
>>> a.first = 'the first value'
>>> a.second = 'the second value'
>>> a
<__main__.Empty object at 0x7fceb005dbd0>
>>> a.first
'the first value'
>>> a.second
'the second value'
>>> a.third
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Empty' object has no attribute 'third'
>>>
In your example, you probably want to use a list:
>>> a = []
>>> a.append("first")
>>> a.append("second")
>>> a.append("third")
>>> a
['first', 'second', 'third']
>>>
But it's not really clear what you are trying to do in your example.
When you separate multiple variables by comma without any surrounding parenthesis, python creates a tuple for you:
a = 1
b = 2
c = 3
# implicit tuple
x = a, b, c
# equivalent, explicit tuple
x = (a, b, c)
Likewise, you can expand a tuple (or any iterable) into multiple variables:
# Expanding a tuple
x = (1, 2, 3)
a, b, c = x # a=1, b=2, c=3
# Expanding a list
x = [1, 2, 3]
a, b, c = x # a=1, b=2, c=3
# Expanding a dict (the keys are expanded into the new variables)
x = {'a': 1, 'b': 2}
a, b = x # a = 'a', b = 'b'
# Expanding an iterator
a, b = range(2) # a=0, b=1
The result of this is a tuple. A tuple is similar to a list but once a tuple is created, the values cannot be changed. The technique in the code is called tuple packing and essentially just creates a tuple.
So you can do it by many ways, i'll give you two of them!
You can always try to get to know some other ;)
You can do a list(changeable list) like this : colors = ["white", "blue", "red"].
Or you can do a tuple ( non-changeable list ( you can change the variable, but not the list itself )) like this : colors = ("white", "blue", "red")
Yes You can Place multiple Variable
but avoid it
https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/
Given three or more variables, I want to find the name of the variable with the min value.
I can get the min value from the list, and I can get the index within the list of the min value. But I want the variable name.
I feel like there's another way to go about this that I'm just not thinking of.
a = 12
b = 9
c = 42
cab = [c,a,b]
# yields 9 (the min value)
min(cab)
# yields 2 (the index of the min value)
cab.index(min(cab))
What code would yield 'b'?
The magic of vars prevents you from having to make a dictionary up front if you want to have things in instance variables:
class Foo():
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def min_name(self, names = None):
d = vars(self)
if not names:
names = d.keys()
key_min = min(names, key = (lambda k: d[k]))
return key_min
In action
>>> x = Foo(1,2,3)
>>> x.min_name()
'a'
>>> x.min_name(['b','c'])
'b'
>>> x = Foo(5,1,10)
>>> x.min_name()
'b'
Right now it'll crash if you pass an invalid variable name in the parameter list for min_name, but that's resolvable.
You can also update the dictionary and it's reflected in the source
def increment_min(self):
key = self.min_name()
vars(self)[key] += 1
Example:
>>> x = Foo(2,3,4)
>>> x.increment_min()
>>> x.a
3
You cannot get the name of the variable with the minimum/maximum value like this*, since as #jasonharper commented: cab is nothing more than a list containing three integers; there is absolutely no connection to the variables that those integers originally came from.
A simple workaround is to user pairs, like this:
>>> pairs = [("a", 12), ("b", 9), ("c", 42)]
>>> min(pairs)
('b', 9)
>>> min(pairs)[0]
'b'
See Green Cloak Guy's answer, but if you want to go for readability, I suggest following a similar approach to mine.
You'd have to get very creative for this to work, and the only solution I can think of is rather inefficient.
You can get the memory address of the data b refers to fairly easily:
>>> hex(id(b))
'0xaadd60'
>>> hex(id(cab[2]))
'0xaadd60'
To actually correspond that with a variable name, though, the only way to do that would be to look through the variables and find the one that points to the right place.
You can do this by using the globals() function:
# get a list of all the variable names in the current namespace that reference your desired value
referent_vars = [k for k,v in globals().items() if id(v) == id(cab[2])]
var_name = referent_vars[0]
There are two big problems with this solution:
Namespaces - you can't put this code in a function, because if you do that and then call it from another function, then it won't work.
Time - this requires searching through the entire global namespace.
The first problem could be alleviated by additionally passing the current namespace in as a variable:
def get_referent_vars(val, globals):
return [k for k,v in globals.items() if id(v) == id(val)]
def main():
a = 12
b = 9
c = 42
cab = [a, b, c]
var_name = get_referent_vars(
cab[cab.index(min(cab))],
globals()
)[0]
print(var_name)
# should print 'b'
a = munching mcdonalds
a,b,c = a.split(" ")
Traceback (most recent call last):
File "terminal.py", line 25, in <module>
a,b,c = a.split(" ")
ValueError: need more than 1 value to unpack
What code should I use instead if I want it to be either 2 or 3 words and still be able to print a?
If you don't actually need b or c, you could do something like:
>>> sentence = 'munching macdonalds'
>>> first, rest = sentence.split(' ', 1)
>>> first
'munching'
>>> rest
'macdonalds'
This uses the maxsplit parameter of str.split (see the docs) to limit to splitting on the first space.
You can then do e.g. if ' ' in rest: to determine whether the second part can be further split up.
If you're using Python 3, you can use the * notation to do the whole split, making first a string and rest a list:
>>> sentence = 'munching macdonalds'
>>> first, *rest = sentence.split(' ')
>>> first
'munching'
>>> rest
['macdonalds']
>>> sentence = 'foo bar baz'
>>> first, *rest = sentence.split(' ')
>>> first
'foo'
>>> rest
['bar', 'baz']
This version is easily repeatable and gives you values for all your variables on the left, in case you decide that you want to use them later (not just a). Note that it also does not modify the original list, so that it can be used as needed later.
def pad(iterable, length=0, padding=None):
li = list(iterable) # Generate new list from `iterable`.
li.extend([padding] * (length - len(li))) # Negative value generates empty list...
return li
sentence = 'munching mcdonalds'
a, b, c = pad(sentence.split(), 3)
assert (a, b, c) == ('munching', 'mcdonalds', None)
a, b, c, d = pad(range(3), 4)
assert (a, b, c, d) == (0, 1, 2, None)
a = pad(range(4), 3)
assert a == [0, 1, 2, 3]
Updated Answer
I thought about this after I made my initial post, and then myaut also pointed
out in a comment that it can be dangerous to generate this as a list. What if
we want to pad it to, say, 1E30 elements or so? So here's the function as a
generator.
def pad(iterable, length=0, padding=None):
for element in iterable:
yield element
for _ in range(length - len(iterable)):
yield padding
sentence = 'munching mcdonalds'
a, b, c = pad(sentence.split(), 3)
assert (a, b, c) == ('munching', 'mcdonalds', None)
a, b, c, d = pad(range(3), 4)
assert (a, b, c, d) == (0, 1, 2, None)
a = list(pad(range(4), 3))
assert a == [0, 1, 2, 3]
Thanks, myaut!
In Python 3, you can assign an arbitrary number of elements to the last item of a tuple unpacking.
>>> a, *b = "munching mcdonalds".split()
>>> a
munching
>>> b
['mcdonalds']
Well... If you really want it:
a, b, c = [value if index >= len(a.split(' ')) else a.split(' ')[index]
for index, value in enumerate([None] * 3)]
split() return a list. Therefore it should be use as one:
a = "munching mcdonalds"
b = a.split(" ")
>>>b
['munching', 'mcdonalds']
>>>b[0]
'munching'
>>>b[1]
'mcdonalds'
I'm having problems using copy.copy() and copy.deepcopy() and Python's scope. I call a function and a dictionary is passed as an argument. The dictionary copies a local dictionary but the dictionary does not retain the values that were copied.
def foo (A, B):
localDict = {}
localDict['name'] = "Simon"
localDict['age'] = 55
localDict['timestamp'] = "2011-05-13 15:13:22"
localDict['phone'] = {'work':'555-123-1234', 'home':'555-771-2190', 'mobile':'213-601-9100'}
A = copy.deepcopy(localDict)
B['me'] = 'John Doe'
return
def qua (A, B):
print "qua(A): ", A
print "qua(B): ", B
return
# *** MAIN ***
#
# Test
#
A = {}
B = {}
print "initial A: ", A
print "initial B: ", B
foo (A, B)
print "after foo(A): ", A
print "after foo(B): ", B
qua (A, B)
The copy.deepcopy works and within function "foo", dict A has the contents of localDict. But outside the scope of "foo", dict A is empty. Meanwhile, after being assigned a key and value, dict B retains the value after coming out of function 'foo'.
How do I maintain the values that copy.deepcopy() copies outside of function "foo"?
Ponder this:
>>> def foo(d):
... d = {1: 2}
...
>>> d = {3: 4}
>>> d
{3: 4}
>>> foo(d)
>>> d
{3: 4}
>>>
Inside foo, d = {1: 2} binds some object to the name d. This name is local, it does not modify the object d used to point to. On the other hand:
>>> def bar(d):
... d[1] = 2
...
>>> bar(d)
>>> d
{1: 2, 3: 4}
>>>
So this has nothing to do with your use of (deep)copy, it's just the way "variables" in Python work.
What's happening is that inside foo() you create a copy of B and assigns it to A, shadowing the empty dict you sent as an argument by reassigning a new object to the same name. Now inside the function you have a new dict called A, completely unrelated to the A outside in the global scope, and it gets garbage collected when the function ends, so actually nothing happens, only the 'me' key added to B.
If instead of:
A = copy.deepcopy(localDict)
You do something like this, it would work as you expect:
C = copy.deepcopy(localDict)
A.update(C)
But it seems like what you really want has nothing to do with the copy module and would be something like this:
def foo (A, B):
A['name'] = "Simon"
A['age'] = 55
A['timestamp'] = "2011-05-13 15:13:22"
A['phone'] = {'work':'555-123-1234', 'home':'555-771-2190', 'mobile':'213-601-9100'}
B['me'] = 'John Doe'
The behavior you are seeing isn't related to deepcopy(), you are reassigning the name A to a new value, and that assignment will not carry over unless you use the global keyword. The reason the changes to B are persistent is that you are modifying a mutable variable, here are two options for how you could get the behavior you want:
Instead of using localDict, just modify A:
def foo(A, B):
A['name'] = "Simon"
A['age'] = 55
A['timestamp'] = "2011-05-13 15:13:22"
A['phone'] = {'work':'555-123-1234', 'home':'555-771-2190', 'mobile':'213-601-9100'}
B['me'] = 'John Doe'
return
Use A.update(copy.deepcopy(localDict)) instead of A = copy.deepcopy(localDict):
def foo(A, B):
localDict = {}
localDict['name'] = "Simon"
localDict['age'] = 55
localDict['timestamp'] = "2011-05-13 15:13:22"
localDict['phone'] = {'work':'555-123-1234', 'home':'555-771-2190', 'mobile':'213-601-9100'}
A.update(copy.deepcopy(localDict))
B['me'] = 'John Doe'
return