I would like to map a method over a list of objects instantiating different classes. All the objects inherit from the same base class and define a method of the desired name.
To make it clearer consider the following code:
class A:
def __init__(self, x):
self.x = x
def foo (self):
return self.x
class B(A):
def foo (self):
return self.x+1
class C(A):
def foo (self):
return self.x-1
Now consider a list of objects instantiating the classes B and C. I would like to do something like that:
result = []
for obj in [B(1), C(1)]:
result.append(obj.foo())
How would you proceed to map the method foo on each element of the list? Is it at all possible? The best I could come up with is something like that:
map(A.foo, [B(1), C(1)])
but clearly it doesn't return my desired result. How can I specify the method related to the object?
I hope I made myself clear.
NB: I work primarily with Python2.7, but I would equally be interested in solutions valid for "newer" versions.
Map(A.foo, [B(1), C(1)]) is basically doing A.foo(B(1)) and A.foo(C(1)) which isn't what you are looking for.
Using your classes from above, I would just do:
In: objs = [B(1), C(1)]
In: [x.foo() for x in objs]
Out: [2, 0]
Amjith has a pure map implementation if you'd prefer that.
>>> map(lambda x: x.foo(), [B(1), C(1)])
>>> [2, 0]
The lambda function will take each object in the list and call foo() on that object. Thus the resulting list will have the results returned by the corresponding object's foo().
For most practical purposes, I'd recommend #AlG's list comprehension, but you can do this with map as well:
>>> import operator
>>> map(operator.methodcaller("foo"), [B(1), C(1)])
[2, 0]
Related
I understand that there are magic methods in python that can be overwritten by classes to control the way certain built in functions treat the members of these classes. For example, the behavior of len() and str() can be overwritten via magic methods __len__() and __str__():
class EmptySet(object):
def __len__(self):
return 0
def __str__(self):
return '[]'
>>> e = EmptySet()
>>> str(e)
[]
>>> len(e)
0
There are also __cmp__() and __ge__(), __le__() etc methods to control how these objects can be compared and how a list of them should be ordered by list.sort(). My question is not about customizing the ordering of objects in a list but about sorting the object itself. Suppose the set weren't empty and I want to use sorted() to sort it:
class SetOfTwo(object):
def __init__(self, a , b):
el_0 = a
el_1 = b
def __len__(self):
return 2
def __str__(self):
return '[{}, {}]'.format(el_0, el_1)
Is there a magic method I can implement to have sorted() flip the elements if they aren't in order? I'm picturing the following behavior:
>>> s = SetOfTwo(2, 1)
>>> str(s)
[2, 1]
>>> t = sorted(s)
>>> str(t)
[1, 2]
>>> type(t)
>>> SetOfTwo
You should definitely read the official documentation of how to emulate container types. Basically a class supposed to work as a container (list, dict etc.) needs to implement methods to set or get members __getitem__(), __setitem__() and iterate over items __iter__() and to get the number of items - method __len__(). This is the minimum. But you can also add the ability to delete items and other operations.
The behaviour of sorted() built-in function is to iterate over elements of your container and compare them using methods you mentioned __cmp__(), __ge__(), __le__() which should be defined for items and not the container as you know already. Then a new list instance is created with items sorted and this new instance is returned. You can pass it to the constructor of your custom container then or you can wriap sorted() with a custom function which will return the desired class instance.
As some have said in the comments, sets are unordered but I don't think your question is really about sets.
Python uses the data model methods you mentioned, ge, le, and cmp to determine how a class behaves when sorted() is called on it. You can see how I try to call it here, but Python objects and asks me to implement <.
>>> class a(object):
... pass
...
>>> b = a()
>>> c = a()
>>> d = [b, c]
>>> sorted(d)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'a' and 'a'
Hope this helps. Aslo, as other people said, it's a good idea to subclass something in collections.abc. I'd read Item 28 in effective python that talks about this to get a good idea.
len() and str() are functions who take an object as parameter and return an integer (resp. string). The object can personalize the way the len is calculated, or the string is generated, via the __len__() and __str__() magic methods.
Similarly, sorted() is a function that takes a list (or any iterable) of objects and returns a list of the sorted objects. The objects can personalize the way they get compared through the __lt__() magic method.
Some confusion arises when we think of `sorted(my_list) as a function that "sorts the list", rather than "sorts the elements of the list".
You don't want to sort your objects (i.e. make an ordered list of objects), but only sort some data in their internal representation. So you need an instance method on your object that will update that internal representation. You can name it as you wish, .sort() if you'd like, but you will have to call it on your one object, and it will not be involved in comparing objects.
You have to implement comparison operator magic methods. Python will automatically take care of the sort and sorted.
class Edge:
def __init__(self,source, dest, weight=float('inf')):
self.source = source
self.dest = dest
self.weight = weight
def __repr__(self):
return f"Edge: ({self.source}, {self.dest}, {self.weight})"
def __lt__(self, other):
return self.weight < other.weight
e1 = Edge(0, 1, 2)
e2 = Edge(1, 2, 3)
e3 = Edge(2, 3, 10)
e4 = Edge(2, 4, 10)
e5 = Edge(2, 4, 0)
l = [e1, e3, e4, e2, e5]
print(l)
print(e3 > e2)
print(e3 == e4)
print(sorted(l))
I am trying to create functions for the property list. How do you create the attribute function for something like this?
list([1,2,3,4,5]).even()
should return:
[2,4]
The method should be easy but I am having trouble associating the function with the predefined object list.
You cannot add methods or attributes to any of the built-in objects. This is by design.
Instead, you can create your own list type that is derived from the built-in one:
class MyList(list):
def even(self):
return [x for x in self if x % 2 == 0]
Demo:
>>> class MyList(list):
... def even(self):
... return [x for x in self if x % 2 == 0]
...
>>> MyList([1,2,3,4,5]).even()
[2, 4]
For more information, see Classes in the documentation, specifically the section on Inheritance.
You can't monkeypatch built in objects in Python like you can in Ruby. You'd have to build a new object, inherit list, and put your method on that.
class MyList(list):
def even(self):
return [num for num in self if num % 2 == 0]
MyList([1,2,3,4,5]).even()
Subclassing frozenset and set doesn't seem to work the same when it comes to iterables. Try to run the following MWE:
class MonFrozenSet(frozenset):
def __new__(self, data):
super(MonFrozenSet,self).__init__(data)
return self
class MonSet(set):
def __init__(self, data):
super(MonSet,self).__init__(data)
x=(1,2,3,4)
A=MonSet(x)
B=MonFrozenSet(x)
for y in A: #Works
print y
for y in B: #Doesn't work
print y
The second for returns:
for y in B:
TypeError: 'type' object is not iterable
Any idea on how I can solve this?
If you are asking yourselves why I would like to use frozenset, the anwer is that I am trying to create a set of sets of tuples. The sets of tuples will be frozenset and the set of sets of tuples will be a set.
I use Python-2.7
When overriding __new__ you need to call the superclass's __new__, not its __init__. Also, you need to pass self (better named cls), since __new__ is a classmethod. Also, you need to return the result, since __new__ actually creates an object, it doesn't modify self. So:
class MonFrozenSet(frozenset):
def __new__(cls, data):
return super(MonFrozenSet,cls).__new__(cls, data)
Then:
>>> a = MonFrozenSet([1, 2, 3])
>>> for item in a:
... print item
1
2
3
After asking this question, it received a comment about how you could do something like this:
>>> def a(n):
print(n)
return a
>>> b = a(3)(4)(5)
3
4
5
Is it possible to use this or similar concepts to make it possible to index lists like my_list(n) instead of my_list[n]?
You'd have to use a custom class, and give it a __call__ special method to make it callable. A subclass of list would do nicely here:
class CallableList(list):
def __call__(self, item):
return self[item]
You cannot use this to assign to an index, however, only item access works. Slicing would require you to use to create a slice() object:
a = CallableList([1, 2, 3])
a(2)
a(slice(None, 2, None))
nested = CallableList([1, 2, CallableList([4, 5, 6])])
nested(2)(-1)
For anything more, you'd have to create a custom Python syntax parser to build an AST, then compile to bytecode from there.
the parentheses in my_list() are treated as a function call. If you want, you could write your own class that wraps a list and overwrite the call method to index into the list.
class MyList(object):
def __init__(self, alist):
self._list = alist
def __call__(self, index):
return self._list[index]
>>> mylist = MyList(['a','b','c','d','e','f'])
>>> mylist(3)
'd'
>>> mylist(4)
'e'
You could create a function that returns a lambda function:
def make_callable(some_list):
return lambda x: some_list[x]
original_list = [ 1, 2, 3, 4 ]
callable_list = make_callable(original_list)
print(callable_list(1)) # Prints 2
I need to create a class that mimics this behavior (in mathematics, we say list, dict, are "idempotent"):
>>> list(list([3,4]))
[3, 4]
>>> dict({'a':1,'b':2})
{'a':1,'b':2}
So, if A is my class, I want to write
>>> a = A(1)
>>> b = A(a)
>>> b == a
True
I imagine my class A has to look like this :
class A(object):
def __init__(self,x):
if isinstance(x, A) :
self = x
else :
self.x = x
self.y = 'hello'
I try it
>>> A(1).x
1
>>> A(A(1)).x
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'A' object has no attribute 'x'
It does not work !
I don't want to copy x attributes in self, i just want self to BE x or "point" x
Some idea ?
Thanks
What you are looking for is the __new__() method, which takes is run before the class is constructed, as opposed to __init__(), which takes place after. With __new__() you can hook in and replace the object being created.
def __new__(cls, x):
if isinstance(x, A):
return x
else:
return object.__new__(cls, x)
You can't do this in __init__() as the object has already been created. Changing self simply changes the value of the local variable, it doesn't affect the object.
It's also worth noting that type-checking is almost always the wrong thing to do in Python. Instead, check to see if the class has the information/attributes you need. This way, someone can create a class that acts like yours and works with your code.
As a final word of warning, this is pretty confusing behaviour - people won't expect your class to act like this and it's generally not a great idea. Your example of list() and dict() isn't accurate to what you are doing here, as list(some_list) does not give some_list, it gives a new list which is a copy of some_list - the same is true for dict():
>>> x = [1, 2, 3]
>>> list(x) is x
False
When you call a constructor, it's natural to expect a new object, rather than a reference to the existing one. I would recommend making A(some_a) copy some_a, and restructure your calling code not to rely on A(some_a) is some_a).