Accessing list items with getattr/setattr in Python - python

Trying to access/assign items in a list with getattr and setattr funcions in Python.
Unfortunately there seems to be no way of passing the place in the list index along with the list name.
Here's some of my tries with some example code:
class Lists (object):
def __init__(self):
self.thelist = [0,0,0]
Ls = Lists()
# trying this only gives 't' as the second argument. Python error results.
# Interesting that you can slice a string to in the getattr/setattr functions
# Here one could access 'thelist' with with [0:7]
print getattr(Ls, 'thelist'[0])
# tried these two as well to no avail.
# No error message ensues but the list isn't altered.
# Instead a new variable is created Ls.'' - printed them out to show they now exist.
setattr(Lists, 'thelist[0]', 3)
setattr(Lists, 'thelist\[0\]', 3)
print Ls.thelist
print getattr(Ls, 'thelist[0]')
print getattr(Ls, 'thelist\[0\]')
Also note in the second argument of the attr functions you can't concatenate a string and an integer in this function.
Cheers

getattr(Ls, 'thelist')[0] = 2
getattr(Ls, 'thelist').append(3)
print getattr(Ls, 'thelist')[0]
If you want to be able to do something like getattr(Ls, 'thelist[0]'), you have to override __getattr__ or use built-in eval function.

You could do:
l = getattr(Ls, 'thelist')
l[0] = 2 # for example
l.append("bar")
l is getattr(Ls, 'thelist') # True
# so, no need to setattr, Ls.thelist is l and will thus be changed by ops on l
getattr(Ls, 'thelist') gives you a reference to the same list that can be accessed with Ls.thelist.

As you discovered, __getattr__ doesn't work this way. If you really want to use list indexing, use __getitem__ and __setitem__, and forget about getattr() and setattr(). Something like this:
class Lists (object):
def __init__(self):
self.thelist = [0,0,0]
def __getitem__(self, index):
return self.thelist[index]
def __setitem__(self, index, value):
self.thelist[index] = value
def __repr__(self):
return repr(self.thelist)
Ls = Lists()
print Ls
print Ls[1]
Ls[2] = 9
print Ls
print Ls[2]

Related

How to tell a class method which variable should be processed

sorry for the noob question. Lets say i have a class which holds 3 lists and a method to combine one of the lists to a string. How can i tell the method which list it should take ? or should i move the method out of the class into a function ?
Here is what i mean:
class images():
def __init__(self, lista, listb, listc):
self.lista=lista
self.listb=listb
self.listc=listc
def makelist(self):
items = ""
for item in self.whateverListIWant:
items=items+item
return items
test=images([1,2,3],["b"],["c"])
print (test.makelist(WhichListIWant))
You can do this
class images():
def __init__(self, lista, listb, listc):
self.lista=lista
self.listb=listb
self.listc=listc
def makelist(self, param):
items = ""
for item in param:
items= items + str(item)
return items
test=images([1,2,3],["b"],["c"])
print (test.makelist(test.lista))
Be aware that "class method" (indicated by the decorator #classmethod) is not what you have in your example. Yours is a standard object method that acts on the object you create, as highlighted by your use of "self" as the first parameter.
The object method acts on the object, and can refer to the data contained within self. A "class method" would act only on a class, and only use parameters available to that class.
Use of a class method would be something like this:
class ClassyImages():
lista = []
listb = []
listc = []
#classmethod
def makelist(cls, which):
if which == 'a':
the_list = cls.lista
elif which == 'b':
the_list = cls.listb
elif which == 'c':
the_list = cls.listc
else:
raise ValueError("Invalid value " + str(which))
return the_list[:] # returns a soft copy of the list
And you would use it as follows:
ClassyImages.lista.extend([1,2,3])
ClassyImages.listb.add("b")
ClassyImages.listc.add("c")
print(ClassyImages.makelist("a"))
See the difference? In your example, you're using methods on the instance of an object, which you create. In this case, we're only using class-level variables, and never using an instance of an object.
However, the nice feature of the #classmethod decorator, is you still can use the class method on an object. So you can also do this:
my_classy_object_1 = ClassyImages()
my_classy_object_2 = ClassyImages()
print(my_classy_object_1.makelist("b")) # prints: ['b']
ClassyImages.listb.add("bb")
print(my_classy_object_1.makelist("b")) # prints: ['b', 'bb']
print(my_classy_object_2.makelist("b")) # prints: ['b', 'bb']
My answer glossed over what you may really be asking - how to tell it which list to look at. You can inject the list into the argument, as suggested by sjaymj64. Or you can pass a string or a number into the function, similar to how I did for the class-level makelist. It's largely up to you - provide whatever way seems most fitting and convenient for letting the logic of makelist choose which of its components to look at.

List of objects function not working

Sorry for the title, I hope it reflects correctly my problem :
In the following code, I was expecting the result to be result 0 1 2 but instead I have 2 2 2. The code inside my_function seems to be interpreted with the last instance of obj. What is wrong ?
class Example:
def __init__(self, x):
self.x = x
def get(self):
return self.x
a_list = []
for index in range(3):
obj = Example(index)
def my_function(x):
#some stuff with x like obj.another_function(x)
return obj.get()
a_list.append(my_function)
for c in a_list:
print(c())
When you define this
def my_function():
return obj.get()
Python will understand that my_function should run the get() method of an object called obj and return the value. It won't know the value of obj and what the get() method does until you attempt to call it.
So, you are actually defining three different functions that will eventually do the same thing. And, in the end, running the same code thrice.
But why is the return 2 2 2?
Because after the last iteration, the value of obj is Example(2)* because you redefine its value at every iteration, and the last one remains.
*
because of this line obj = Example(index)
Understanding a few things about how python works will help you understand what's happening here. Here obj is a closure, closures are evaluated at call time, not when the function is defined so if I do this:
x = "hello"
def printX():
print x
x = "goodbye"
printX() # goodbye
I get "goodbye" because printX is referencing a global variable in my module, which changes after I create printX.
What you want to do is create a function with a closure that references a specific object. The functional way to do this is to create a function that returns another function:
x = "hello"
def makePrintX(a):
def printX():
# We print a, the object passed to `makePrintX`
print a
return printX
# x is evaluated here when it is still "hello"
myPrintX = makePrintX(x)
x = "goodbye"
myPrintX() # "hello"
If you're having trouble understanding the above example I would recommend reading up on python's scoping rules. For your example, you could do something like this:
class Example:
def __init__(self, x):
self.x = x
def get(self):
return self.x
def makeObjFunction(obj):
def objFunction(x):
return obj.get()
return objFunction
a_list = []
for index in range(3):
obj = Example(index)
my_function = makeObjFunction(obj)
a_list.append(my_function)
for c in a_list:
print(c("some value"))
You are appending three my_functions to the a_list which are all closures over the same Example object. Try:
def my_function():
return obj
<__main__.Example object at 0x0054EDF0>
<__main__.Example object at 0x0054EDF0>
<__main__.Example object at 0x0054EDF0>
You can see they have the same id so calling get() on each should give the same answer.
If you just append the obj.get function (and drop the my_function) it'll work fine.
a_list.append(obj.get)
....
0
1
2
Edit: You've updated your question so to let you do more stuff in my_function(). It's still basically a scoping problem.
def my_func_factory(p_obj):
def my_function(x):
#some stuff with x like obj.another_function(x)
return p_obj.get()
return my_function
for index in range(3):
obj = Example(index)
a_list.append(my_func_factory(obj))
Since my_function can't see obj being reassigned, each instance doesn't pick up the change.
I think append() during the for just append the function address in a_list[]. After for iteration, the a_list is really given the number. Then it discovers the address of my_function, and they get the number in my_function, this is, 2. That's why you get [2,2,2].
Or maybe, in my_function, function give the method of "obj". But for iteration change the "obj" memory address each time, so the symbol "obj" always aim to the newest object Example. Due to my_function always get "obj", you get the same number from the last object.

Adding items to a list if it's not a function

I'm trying to write a function right now, and its purpose is to go through an object's __dict__ and add an item to a dictionary if the item is not a function.
Here is my code:
def dict_into_list(self):
result = {}
for each_key,each_item in self.__dict__.items():
if inspect.isfunction(each_key):
continue
else:
result[each_key] = each_item
return result
If I'm not mistaken, inspect.isfunction is supposed to recognize lambdas as functions as well, correct? However, if I write
c = some_object(3)
c.whatever = lambda x : x*3
then my function still includes the lambda. Can somebody explain why this is?
For example, if I have a class like this:
class WhateverObject:
def __init__(self,value):
self._value = value
def blahblah(self):
print('hello')
a = WhateverObject(5)
So if I say print(a.__dict__), it should give back {_value:5}
You are actually checking if each_key is a function, which most likely is not. You actually have to check the value, like this
if inspect.isfunction(each_item):
You can confirm this, by including a print, like this
def dict_into_list(self):
result = {}
for each_key, each_item in self.__dict__.items():
print(type(each_key), type(each_item))
if inspect.isfunction(each_item) == False:
result[each_key] = each_item
return result
Also, you can write your code with dictionary comprehension, like this
def dict_into_list(self):
return {key: value for key, value in self.__dict__.items()
if not inspect.isfunction(value)}
I can think of an easy way to find the variables of an object through the dir and callable methods of python instead of inspect module.
{var:self.var for var in dir(self) if not callable(getattr(self, var))}
Please note that this indeed assumes that you have not overrided __getattr__ method of the class to do something other than getting the attributes.

Python Scoping/Static Misunderstanding

I'm really stuck on why the following code block 1 result in output 1 instead of output 2?
Code block 1:
class FruitContainer:
def __init__(self,arr=[]):
self.array = arr
def addTo(self,something):
self.array.append(something)
def __str__(self):
ret = "["
for item in self.array:
ret = "%s%s," % (ret,item)
return "%s]" % ret
arrayOfFruit = ['apple', 'banana', 'pear']
arrayOfFruitContainers = []
while len(arrayOfFruit) > 0:
tempFruit = arrayOfFruit.pop(0)
tempB = FruitContainer()
tempB.addTo(tempFruit)
arrayOfFruitContainers.append(tempB)
for container in arrayOfFruitContainers:
print container
**Output 1 (actual):**
[apple,banana,pear,]
[apple,banana,pear,]
[apple,banana,pear,]
**Output 2 (desired):**
[apple,]
[banana,]
[pear,]
The goal of this code is to iterate through an array and wrap each in a parent object. This is a reduction of my actual code which adds all apples to a bag of apples and so forth. My guess is that, for some reason, it's either using the same object or acting as if the fruit container uses a static array. I have no idea how to fix this.
You should never use a mutable value (like []) for a default argument to a method. The value is computed once, and then used for every invocation. When you use an empty list as a default value, that same list is used every time the method is invoked without the argument, even as the value is modified by previous function calls.
Do this instead:
def __init__(self,arr=None):
self.array = arr or []
Your code has a default argument to initialize the class. The value of the default argument is evaluated once, at compile time, so every instance is initialized with the same list. Change it like so:
def __init__(self, arr=None):
if arr is None:
self.array = []
else:
self.array = arr
I discussed this more fully here: How to define a class in Python
As Ned says, the problem is you are using a list as a default argument. There is more detail here. The solution is to change __init__ function as below:
def __init__(self,arr=None):
if arr is not None:
self.array = arr
else:
self.array = []
A better solution than passing in None — in this particular instance, rather than in general — is to treat the arr parameter to __init__ as an enumerable set of items to pre-initialize the FruitContainer with, rather than an array to use for internal storage:
class FruitContainer:
def __init__(self, arr=()):
self.array = list(arr)
...
This will allow you to pass in other enumerable types to initialize your container, which more advanced Python users will expect to be able to do:
myFruit = ('apple', 'pear') # Pass a tuple
myFruitContainer = FruitContainer(myFruit)
myOtherFruit = file('fruitFile', 'r') # Pass a file
myOtherFruitContainer = FruitContainer(myOtherFruit)
It will also defuse another potential aliasing bug:
myFruit = ['apple', 'pear']
myFruitContainer1 = FruitContainer(myFruit)
myFruitContainer2 = FruitContainer(myFruit)
myFruitContainer1.addTo('banana')
'banana' in str(myFruitContainer2)
With all other implementations on this page, this will return True, because you have accidentally aliased the internal storage of your containers.
Note: This approach is not always the right answer: "if not None" is better in other cases. Just ask yourself: am I passing in a set of objects, or a mutable container? If the class/function I'm passing my objects in to changes the storage I gave it, would that be (a) surprising or (b) desirable? In this case, I would argue that it is (a); thus, the list(...) call is the best solution. If (b), "if not None" would be the right approach.

How to make print call the __str__ method of Python objects inside a list?

In Java, if I call List.toString(), it will automatically call the toString() method on each object inside the List. For example, if my list contains objects o1, o2, and o3, list.toString() would look something like this:
"[" + o1.toString() + ", " + o2.toString() + ", " + o3.toString() + "]"
Is there a way to get similar behavior in Python? I implemented a __str__() method in my class, but when I print out a list of objects, using:
print 'my list is %s'%(list)
it looks something like this:
[<__main__.cell instance at 0x2a955e95f0>, <__main__.cell instance at 0x2a955e9638>, <__main__.cell instance at 0x2a955e9680>]
how can I get python to call my __str__() automatically for each element inside the list (or dict for that matter)?
Calling string on a python list calls the __repr__ method on each element inside. For some items, __str__ and __repr__ are the same. If you want that behavior, do:
def __str__(self):
...
def __repr__(self):
return self.__str__()
You can use a list comprehension to generate a new list with each item str()'d automatically:
print([str(item) for item in mylist])
Two easy things you can do, use the map function or use a comprehension.
But that gets you a list of strings, not a string. So you also have to join the strings together.
s= ",".join( map( str, myList ) )
or
s= ",".join( [ str(element) for element in myList ] )
Then you can print this composite string object.
print 'my list is %s'%( s )
Depending on what you want to use that output for, perhaps __repr__ might be more appropriate:
import unittest
class A(object):
def __init__(self, val):
self.val = val
def __repr__(self):
return repr(self.val)
class Test(unittest.TestCase):
def testMain(self):
l = [A('a'), A('b')]
self.assertEqual(repr(l), "['a', 'b']")
if __name__ == '__main__':
unittest.main()
I agree with the previous answer about using list comprehensions to do this, but you could certainly hide that behind a function, if that's what floats your boat.
def is_list(value):
if type(value) in (list, tuple): return True
return False
def list_str(value):
if not is_list(value): return str(value)
return [list_str(v) for v in value]
Just for fun, I made list_str() recursively str() everything contained in the list.
Something like this?
a = [1, 2 ,3]
[str(x) for x in a]
# ['1', '2', '3']
This should suffice.
When printing lists as well as other container classes, the contained elements will be printed using __repr__, because __repr__ is meant to be used for internal object representation.
If we call: help(object.__repr__) it will tell us:
Help on wrapper_descriptor:
__repr__(self, /)
Return repr(self).
And if we call help(repr) it will output:
Help on built-in function repr in module builtins:
repr(obj, /)
Return the canonical string representation of the object.
For many object types, including most builtins, eval(repr(obj)) == obj.
If __str__ is implemented for an object and __repr__ is not repr(obj) will output the default output, just like print(obj) when non of these are implemented.
So the only way is to implement __repr__ for your class. One possible way to do that is this:
class C:
def __str__(self):
return str(f"{self.__class__.__name__} class str ")
C.__repr__=C.__str__
ci = C()
print(ci) #C class str
print(str(ci)) #C class str
print(repr(ci)) #C class str
The output you're getting is just the object's module name, class name, and then the memory address in hexadecimal as the the __repr__ function is not overridden.
__str__ is used for the string representation of an object when using print. But since you are printing a list of objects, and not iterating over the list to call the str method for each item it prints out the objects representation.
To have the __str__ function invoked you'd need to do something like this:
'my list is %s' % [str(x) for x in myList]
If you override the __repr__ function you can use the print method like you were before:
class cell:
def __init__(self, id):
self.id = id
def __str__(self):
return str(self.id) # Or whatever
def __repr__(self):
return str(self) # function invoked when you try and print the whole list.
myList = [cell(1), cell(2), cell(3)]
'my list is %s' % myList
Then you'll get "my list is [1, 2, 3]" as your output.

Categories

Resources