Python Class design: attributes - python

I constructed a class:
class Foo (object):
def __init__(self,List):
self.List=List
#property
def numbers(self):
L=[]
for i in self.List:
if i.isdigit():
L.append(i)
return L
#property
def letters(self):
L=[]
for i in self.List:
if i.isalpha():
L.append(i)
return L
>>> inst=Foo(['12','ae','45','bb'])
>>> inst.letters
['ae', 'bb']
>>> inst.numbers
['12', '45']
How can I add attributes so I could do inst.numbers.odd that would return ['45']?

Your numbers property returns a list, so a numbers.odd won't work.
However, you could follow a workflow like:
define a small class Numbers, that would define two properties even and odd
For example, Numbers could take a list as argument of its __init__, the even property would return only the even number of this list [i for i in List if int(i)%2 == 0] (and odd the odd ones)...
create an instance of Numbers in your Foo.numbers property (using your Foo.List to initialize it) and return this instance...
Your Numbers class could directly subclass the builtin list class, as suggested. You could also define it like
class Numbers(object):
def __init__(self,L):
self.L = L
#property
def even(self):
return [i for i in self.L if not int(i)%2]
def __repr__(self):
return repr(self.L)
Here, we returning the representation of Numbers as the representation of its L attribute (a list). Fine and dandy until you want to append something to a Numbers instance, for example: you would have to define a Numb.append method... It might be easier to stick with making Numbers a subclass of list:
class Numbers(list):
#property
def even(self):
...
Edited: corrected the // by a %, because I went too fast and wasn't careful enough

Here's a silly example:
class mylst(list):
#property
def odd(self):
return [ i for i in self if int(i)%2 == 1 ]
class Foo(object):
def __init__(self,lst):
self.lst = list(lst)
#property
def numbers(self):
return mylst( i for i in self.lst if i.isdigit() )
a = Foo(["1","2","3","ab","cd"])
print(a.numbers)
print(a.numbers.odd)
Basically, we just subclass list and add a property odd which returns another list. Since our structure is a subclass of list, it is virtually indistinguishable from the real thing (Horray duck typing!). mylst.odd could even return a new instance of mylst if you wanted to be able to filter it again (e.g. a.numbers.odd.in_fibinocci )

Related

Automatically extend class with methods coming from another module

Immagine that I have defined several methods acting on an object, and I have two or more different classes that cannot inherit from the same parent, having an instance of that object. I want to automatically add all the methods to the two classes, removing the first argument (the object) and replacing it with the instance owned by the class.
Is there a way to do it?
I am sure my question is not clear, so I try to give a super simplified settings. to keep things simple the object is just a list. I hope that after the example my objective is clear! Thanks in advance for your time.
# I define some methods acting on an object (just 2 useless methods acting on a list in this example)
def get_avg(input_list):
return sum(input_list) / len(input_list)
def multiply_elements(input_list, factor):
return [i * factor for i in input_list]
Then we have 2 different classes, both have an instance of our object (the list)
class A:
list_of_apples = []
def get_list_of_apples(self):
return self.list_of_apples
class B:
"""Totally different class from A(pples), also containing a list"""
list_of_bears = []
def get_list_of_bears(self):
return self.list_of_bears
Now, to call a "list" method on the lists owned by A and B instances, I would need to do the following:
b = B()
get_avg(b.get_list_of_bears())
My goal, instead, is to automatically define some wrappers (as the following ones) which would allow me to call list methods directly from instances of A and B. Here there is an example for B:
class B:
"""Totally different class from A(pples), but containing a list"""
list_of_bears = []
def get_list_of_bears(self):
return self.list_of_bears
def get_avg(self):
return get_avg(self.list_of_bears)
def multiply_elements(self, factor):
return multiply_elements(self.list_of_bears, factor)
With the extended class, I can simply do:
b = B()
b.get_avg()
b.multiply_elements(factor=10)
I would like to automatically extend A and B.
I don't know why your classes cannot inherit from a common ancestor but one solution I can think of is to make the ancestor dynamically:
def make_ancestor():
class Temp:
def get_avg(self):
input_list = getattr(self, self.list_name)
return sum(input_list) / len(input_list)
def multiply_elements(self, factor):
input_list = getattr(self, self.list_name)
return [i * factor for i in input_list]
return Temp
class A(make_ancestor()):
list_of_apples = []
list_name = 'list_of_apples'
def get_list_of_apples(self):
return self.list_of_apples
class B(make_ancestor()):
list_of_bears = []
list_name = 'list_of_bears'
def get_list_of_bears(self):
return self.list_of_bears
Now since the parent classes are being generated dynamically your child classes don't inherit from the same parent.
As a test:
print(make_ancestor() == make_ancestor()) # False

'MyList' object does not support item assignment

Dears,
I am trying to understand better the OOP paradigm in Python, so I've created this simple class
class MyList:
def __init__(self,list):
self.list=list
list1=MyList([1,2,3,4,5])
Until here is everything fine, the problem occurs when I try to set a value to any element of my list. As example:
list1[0]=5
Then I got this TypeError: 'MyList' object does not support item assignment
Someone could help me with this?
You don't have any code in the class that allows for item assignment. For an object to allow item assignment, it needs to implement __setitem__.
You would need something like:
class MyList:
def __init__(self,list):
self.list=list
def __setitem__(self, i, elem):
self.list[i] = elem
Since you're trying to better understand OOP, here's something you can try: Subclass list itself, so your class gets all the properties that lists do.
class MyList(list):
def __init__(self,list):
super().__init__(list)
list1 = MyList([1,2,3,4,5])
list1[0] = 5
print(list1)
# [5, 2, 3, 4, 5]
class MyList:
def __init__(self,list):
self.list=list
def __str__(self):
return str(self.__dict__)
list1=MyList([1,2,3,4,5])
print(list1)
list1.list[0]=5
print(list1)
You need to assign to the list attribute not to the class
You are probably trying to create a new MyList type which should have the functionalities of a normal Python list plus some functionalities you might like to add or modify. For this to work, you need to inherit from the inbuilt Python list class, like so,
class MyList(list):
pass
list1=MyList([1,2,3,4,5])
list1[0]=5
Now in your MyList class, define only those methods which you want to change in the inbuilt list class
I got it!
I implemented the special method setitem and getitem and it worked. Thank you all
class MyList:
def __init__(self,list):
self.list=list
def __getitem__(self, index):
return self.list[index]
def __setitem__(self, index, value):
self.list[index] = value

How to extends a list with all superclass values in Python?

My intention is to get a list completed with the values that are assigned to a variable in each class of superior nature in the simplest way possible.
class First:
list = []
def get_final_list(self):
return self.list
class Second(First):
list = ['one']
class Third(Second):
list = ['two']
Result: list = ['one','two']
This is an example of the final result but obviously the whole logic of the function to return this value is missing.
I did something similar recently where I wanted child classes to be able to define additional values within attributes of their parents. You can do this using metaclasses, which allow you to hook into class creation in the same way that classes let you hook into instance creation.
In your case, for example, you could do something like:
class ListCombiner(type):
def __new__(cls, name, bases, dct):
l = []
for base in bases:
l = getattr(base, 'list', []) + []
dct['list'] = l + dct.get('list', [])
return type.__new__(cls, name, bases, dct)
class First(metaclass=ListCombiner):
list = []
def get_final_list(self):
return self.list
class Second(First):
list = ['one']
class Third(Second):
list = ['two']
Now the result is:
>>> Third().list
['one', 'two']
For more information on metaclasses, see e.g. What are metaclasses in Python?
If you're really interested, you can see where I introduced it in this commit; this shows how I was able to replace some awkward code with the metaclass. The fact that I was using sets made it slightly easier, as I didn't care about the order.
How about using proper methods etc. instead of a mere field in the class?
class First:
def list(self):
return []
def get_final_list(self):
return self.list
class Second(First):
def list(self):
return super().list() + ['one']
class Third(Second):
def list(self):
return super().list() + ['two']
Then you can:
Third().list() # returns ['one', 'two']

How to remove duplicates from a list of custom objects?

I have a custom class of objects with an assortment of various attributes of different types. I would like to remove duplicates from a list of these objects based on one of these attributes.
Something like this, but actually get a list of the objects rather than a list of the specified attribute.
filteredData = list(set([x.attribute[0] for x in objList]))
You need realize methods hash and eq on object
class A:
def __init__(self, a):
self.attr1 = a
def __hash__(self):
return hash(self.attr1)
def __eq__(self, other):
return self.attr1 == other.attr1
def __repr__(self):
return str(self.attr1)
Example:
l = [A(5), A(4), A(4)]
print list(set(l))
print list(set(l))[0].__class__ # ==> __main__.A. It's a object of class

Python "strange" output

class Foo(object):
def __init__(self,x):
self.x = x
self.is_bar = False
def __repr__(self): return str(self.x)
class Bar(object):
def __init__(self,l = []):
self.l = l
def add(self,o):
self.l += [o]
def __repr__(self): return str(self.l)
def foo_plus_foo(f1,f2):
t = Bar()
if not (f1.is_bar and f2.is_bar):
f1.is_bar = True
f2.is_bar = True
t.add(f1)
t.add(f2)
print 'HERE'
return t
if __name__ == '__main__':
li = [Foo(1), Foo(2)]
print foo_plus_foo(li[0],li[1])
print foo_plus_foo(li[0],li[1])
UNEXPECTED OUTPUT:
HERE
[1, 2]
[1, 2]
EXPECTED OUTPUT:
HERE
[1, 2]
[]
What is happening? What did I do wrong? Why is python using old value? What do I do to avoid this?
Thanks!
Never. Do. This.
def __init__(self,l = []):
Never.
One list object is reused. And it's Mutable, so that each time it's reused, the one and only [] created in your method definition is updated.
Always. Do. This.
def __init__( self, l= None ):
if l is None: l = []
That creates a fresh, new, unique list instance.
You are defining l as having a default value of [].
This is a classic Python pitfall.
class Bar(object):
def __init__(self,l = []):
Default values are evaluated at definition time not run-time.
It is evaluated only once.
So t=Bar() sets t.l to the very same list every time.
To fix this, change Bar to
class Bar(object):
def __init__(self,l = None):
if l is None:
l=[]
self.l = l
def add(self,o):
self.l += [o]
def __repr__(self): return str(self.l)
The culprit is the l=[] defined in the Bar class definition.
This list is instantiated once during class definition and is used as the default.
Super dangerous!! I and many others have been burned by this one, trust me the scarring is deep.
Problematic use of mutable.
class Bar(object):
def __init__(self,l = []):
self.l = l
def add(self,o):
self.l += [o]
def __repr__(self): return str(self.l)
Try using an immutable:
class Bar(object):
def __init__(self,l = None):
if l is None:
self.l = []
else:
self.l = l
def add(self,o):
self.l += [o]
def __repr__(self): return str(self.l)
Others have explained the problem and suggested using l=None and an explicit test for it. I'd like to suggest a different approach:
class Bar(object):
def __init__(self, l=[]):
self.l = list(l)
# and so on...
This guarantees a new blank list each time by default, and also assures that if a caller passes in a list of their own, you get a copy of that list rather than a reference to the caller's list. As an added benefit, callers can pass in anything that the list constructor can consume, such as a tuple or a string, and your code doesn't have to worry about that; it can just deal with a list.
If you just save a reference to the list the caller gives you, and later change the list named self.l, you may be inadvertently changing the list that was passed in, too (since both your class and the caller now have a reference to the same object). Similarly, if they change the list after calling your constructor, your "copy" will be changed too (since it's not actually a copy). Of course, it could be that this is behavior you want, but probably not in this case.
Although if you never manipulate the list (i.e. add, replace, or delete items), but only refer to it or replace it wholesale, copying it is usually a waste of time and memory.
The copy made using the list() constructor is shallow. That is, the list itself is a new object, but if the original list contains references to mutable objects (such as other lists or dictionaries, or instances of most other classes), the same problem can arise if you change those objects, because they are still shared between the lists. This is an issue less frequently than you might think, but if it is, you can perform a deep copy using the deepcopy() function in the copy module.

Categories