Creating copy of arbitrary object in Python and calling constructor - python

I have classes A, B, and C which inherit from Foo. Their __init__ methods all do different things, but have a similar signature: they all take a single parameter i in __init__. Some number of instances of these classes are in a list l, and all mixed together. In l, all the objects have i=1.
I need to go through l, and for every object I see, I need to create the same object, but instantiated with i=2 instead of i=1.
How do I do this?
I tried this:
l2 = []
for obj in l:
obj_2 = type (obj).__init__(2)
l2.append(obj_2)
But it didn't work.

I'm not sure what you mean by "create the same object". If you mean "create a brand-new distinct object of the same type", then try this:
obj_2 = type (obj)(2)
Your code, rewritten as a list comprehension:
l2 = [type(obj)(2) for obj in l]

Related

Have a result be both a class and destructurable as a tuple

I came across a method in Python that returns a class, but can be destructured as if it's a tuple.
How can you define a result of a function to be both an instance of a class AND use destructure assignment as if it's a tuple?
An example where you see this behavior:
import scipy.stats as stats
res = stats.ttest_ind(data1, data2)
print(type(res)) # <class 'scipy.stats.stats.Ttest_indResult'>
# One way to assign values is by directly accessing the instance's properties.
p = res.pvalue
t = res.statistic
# A second way is to treat the result as a tuple, and assign to variables directly. But how is this working?
# We saw above that the type of the result is NOT a tuple but a class. How would Python know the order of the properties here? (It's not like we're destructuring based on named properties)
t, p = stats.ttest_ind(data1, data2)
It's a named tuple, which is basically an extension to tuple type in python.
To unpack a data type with a, b = some_object, the object on the right side needs to be iterable. A list or tuple works, obviously, but you can make your own class iterable by implementing an __iter__ method.
For example, the following class would behave consistently with the interface you've shown the Ttest_indResult class to have (though it's probably implemented very differently):
class MyClass:
def __init__(self, statistic, pvalue):
self.statistic = statistic # these attributes are accessible by name
self.pvalue = pvalue
def __iter__(self): # but you can also iterate to get the same values
yield self.statistic
yield self.pvalue

Can you use list.index for a list of objects?

If I have a list of objects made from classes, is there a way to get the index of a particular object in that list?
I've tried using list.index like this:
obj_list = [object1(), object2()]
object1_index = obj_list.index(object1())
return object1_index
But this just returns a ValueError, saying that object1() is not in list, even though it is.
object1 is a constructor; each time you say object1() you're constructing a new object of class object1. Hence the object1() in your index() call does not refer to the same object as the one in obj_list.
You could do something like:
next(i for i, x in enumerate(obj_list) if isinstance(x, object1))
to find the index of the first object in obj_list that is an instance of object1.

How to create different objects with a constructor list

I have a list which has different constructors of different classes. But the constructors always return the same object because they have the same memory direction.
I have something like this:
l=[class1(),class2(),class3()]
l2 = []
If I try to create different objects with it, it returns the same object with the same memory direction. I'm doing this:
for i in range(50):
obj = l[random]
l2.append(obj)
l2 has 50 objects but all the objects of the first class are the same and they have the same memory direction. Same happens with the other classes.
I would like to have 50 differents objects.
You must call the constructor of the class each time you want to add an instance to the list. For this I would suggest that you use a list of classes rather than a list of object instances (otherwise you're just adding the same references to these 3 instances to the list)
l=[class1,class2,class3]
for _ in range(50):
cls = l[random]
l2.append(cls())

How to call method on multiple objects with similar names and same type?

How might I go about simplifying this code, preferably putting it into a loop or one line?
object1.callMethod()
object2.callMethod()
object3.callMethod()
difObject1.callDifMethod()
difObject2.callDifMethod()
difObject3.callDifMethod()
I know that I can put the objects in a list and iterate through that, however, that would still require two separate loops and two separate lists. Is there any way that I can make a singular list of [1,2,3] and use that to distinguish between the three different objects of each type, since the numbers are in the object names as well?
getattr(object, method_name)()
If all of the method and object names are generally semantic, you can use getattr to reference the method based on a string variable, and then call it with ().
objects = [object1, object2, object3]
for object in objects:
getattr(object, method_name)()
If you want to run the objects/method in parallel, use zip.
objects = [object1, object2, object3]
methods = ['method1name', 'method2name', 'method3name']
for object, method in zip(objects, methods):
getattr(object, method)()
You could use a dictionary approach:
methods = {cls1.method1: [cls1_obj1, cls1_obj2, cls1_obj3],
cls1.method2: [cls1_obj4, cls1_obj5, cls1_obj6],
cls2.method1: [cls2_obj1, cls2_obj2}
for method, objs in methods.items():
for obj in objs:
method(obj)
This assumes you are using an instance method though. For a static/class method you'll need to adjust the object being passed for the method.
I'm not sure there's anything elegant that doesn't involve predefining multiples (or combinations) of lists and dicts and loop over it, since you would need to be explicit in which object runs which methods, a definition is required either way.
Ideally, if you have multiple similar objects of the same class, you might opt to instantiate them in a list from the get go:
# Instead of this
object1 = Foo(1)
object2 = Foo(2)
object3 = Foo(3)
...
# do this
foos = [Foo(i) for i in range(3)]
# Or this
bars = {name: Bar(name) for name in list_of_names}
Then it becomes trivial to manipulate them in group:
for foo in foos:
foo.foo_method()
for bar in bars.values():
bar.bar_method()
While still easy to reference the object on its own:
foo[index].foo_method()
bar[key].bar_method()
You could use the eval function:
>>> for i in range(1,4):
>>> eval("object%d" % i).callMethod()
>>> eval("difObject%d" % i).callDifMethod()

How can I create n numbers of instances for a class?

I am trying to write a code whereby I can set a variable, say n, to create n numbers of instances for that particular class. The instances have to be named 'Node_1', 'Node_2'...'Node_n'. I've tried to do this in several ways using the for loop, however I always get the error: 'Can't assign to operator.'
My latest effort is as follows:
class C():
pass
for count in range(1,3):
"node"+str(count)=locals()["C"]()
print(node)
I understand that the "node" + str(count) is not possible, but I don't see how I can solve this issue.
Any help on the matter will be greatly appreciated.
You could do what you're trying to do, but it's a really bad idea. You should either use a list or a dict; since you seem to want the names to be nodeX, and starting from 1, you should use a dict.
nodes = {'node{}'.format(x): C() for x in range(1, 3)}
Depending on what you're doing, you could also use a defaultdict.
from collections import defaultdict
nodes = defaultdict(C)
print(nodes['node1'])
nodes['node2'].method()
print(nodes['anything-can-go-here'])
Once you're doing that though, there's no need for the 'node' prefix.
The best pattern for creating several similar objects is a list comprehension:
class C():
pass
nodes = [C() for i in range(3)]
This leaves you with three objects of class C, stored in a list called nodes. Access each object in the normal way, with indexing (e.g. nodes[0]).
You're trying to assign a value to a string. You can write Node_1 = C(), but "Node_1" = C() is meaningless, as "Node_1" is a string literal, not an identifier.
It's a little sketchy, but you can use the locals() dictionary to access the identifiers by name:
for count in range(1, 3):
locals()["node" + str(count)] = C()
...and, having done that, you can then use node1 and node2 as if they were defined explicitly in your code.
Typically, however, it's preferable to not access your locals this way, rather you should probably be using a separate dictionary of your own creation that stands on its own and contains the values there:
nodes = {}
for count in range(1, 3):
nodes[count] = C()
... and the values can then be accessed like so: nodes[1], nodes[2], etc.
What I like to do, to keep a registry of all the instances of a class:
class C(object):
instances = {}
def __new__(cls, *args, **kwargs):
instance = super(C, cls).__new__(cls, *args, **kwargs)
instance_name = 'node_{}'.format(len(cls.instances))
cls.instances[instance_name] = instance
return instance
if __name__ == '__main__':
for _ in range(3):
C()
print C.instances
OrderedDict([('node_0', <main.C object at 0x10c3fe8d0>), ('node_1', <main.C object at 0x10c4cb610>), ('node_2', <main.C object at 0x10c4e04d0>)])

Categories