Just curious,
Is there any difference (advantages and disadvantages) between using len() or def __len__() when I build a class? And which is the best Python style?
class foo(object):
def __init__(self,obs=[])
self.data = obs
self.max = max(obs)
self.min = min(obs)
self.len = len(obs)
or
class foo(object):
def __init__(self,obs=[])
self.data = obs
self.max = max(obs)
self.min = min(obs)
def __len__(self):
return len(self.data)
There is a huge difference.
The __len__() method is a hook method. The len() function will use the __len__ method if present to query your object for it's length.
The normal API people expect to use is the len() method, using a .len attribute instead would deviate from that norm.
If the length of self.data is not expected to change, you can always cache the length in an attribute and have .__len__() return that attribute.
class foo(object):
def __init__(self, obs=None):
if obs is None: # provide a default if no list was passed in.
obs = []
self.data = obs
self.max = max(obs)
self.min = min(obs)
self._data_len = len(obs)
def __len__(self):
return self._data_len
There are several differences:
Only the second approach will give you the familiar len(obj) syntax for foo. The first will require obj.len().
If the length of self.data can change post-construction, only the second version will reflect the new length.
Related
Hy,
I have a small class with only one attribute, which is a list with four elements. I want to make attributes for each object of the list with the help of property, but I don't want to write a setter and a getter method for each element. Currently I implemented it in the following way.
class MyClass:
def __init__(self):
self.my_list = [None in range(4)]
def __get_my_list_x(self, x):
return self.my_list[x]
def __set_my_list_x(self, val, x):
self.my_list[x] = val
def get_my_list_0(self):
return self.__get_my_list_x(x=0)
def set_my_list_0(self, val):
self.__set_my_list_x(val, x=0)
# continue getter and setter methods for position 1, 2 and 3
# of my list
my_list_0 = property(get_my_list_0, set_my_list_0)
my_list_1 = property(get_my_list_1, set_my_list_1)
my_list_2 = property(get_my_list_2, set_my_list_2)
my_list_3 = property(get_my_list_3, set_my_list_3)
At the moment I'm violating the Don't repeat yourself principle, because I have to write the getter and setter methods for my_list_0 to my_list_3. Is there a way to directly call the methods __get_my_list_x and __set_my_list_x in property() and specify the x argument?
I hope you guys get my question.
Have a nice day.
There are a lot of different solutions possible depending on your exact situation outside of this probably oversimplified example.
The best solution if you need to use actual attributes is probably to define your own custom descriptors (e.g. what property does under the hood):
class MyListIndexer:
def __init__(self, index):
self.index = index
def __get__(self, instance, owner):
return instance.my_list[self.index]
def __set__(self, instance, value):
instance.my_list[self.index] = value
class MyClass:
def __init__(self):
self.my_list = [None for _ in range(4)]
my_list_0 = MyListIndexer(0)
my_list_1 = MyListIndexer(1)
You can also add another parameter to MyListIndexer specifying the name of the attribute with help of getattr.
However, consider not using attributes at all and instead providing something like direct item access with __getitem__/__setitem__:
class MyClass:
def __init__(self):
self.my_list = [None for _ in range(4)]
def __setitem__(self, key, value):
self.my_list[key] = value
def __getitem__(self, item):
return self.my_list[item]
The extreme general solution that might have unexpected consequences and should only be used if there is no other solution is to use the __getattr__/__setattr__ functions:
class MyClass:
def __init__(self):
self.my_list = [None for _ in range(4)]
def __getattr__(self, item):
if item.startswith("my_list_"):
val = int(item[8:])
return self.my_list[val]
else:
return super(MyClass, self).__getattr__(item)
def __setattr__(self, key, value):
if key.startswith("my_list_"):
ind = int(key[8:])
self.my_list[ind] = value
else:
super(MyClass, self).__setattr__(key, value)
I've seen the following code.
class Primes:
def __init__(self, max):
self.max = max
self.number = 1
def __iter__(self):
return self
def __next__(self):
self.number += 1
if self.number >= self.max:
raise StopIteration
elif check_prime(self.number):
return self.number
else:
return self.__next__()
In the dunder init function, we set self.number=1, without having included earlier the attribute number. What's the meaning of it?
This code, only means that self.number is not customizable and will always values 1 when creating an instance of Primes. This is used when the class need an attribute, which will be used along its state and methods, but it's value should always be the same when instanciating the object
def __init__(self, max):
self.max = max
self.number = 1
I use following class to define event:
class Event(object):
def __init__(self):
self.handlers = set()
def handle(self, handler):
self.handlers.add(handler)
return self
def unhandle(self, handler):
try:
self.handlers.remove(handler)
except:
raise ValueError("Handler is not handling this event, so cannot unhandle it.")
return self
def fire(self, *args, **kwargs):
for handler in self.handlers:
print(handler)
handler(*args, **kwargs)
def getHandlerCount(self):
return len(self.handlers)
__iadd__ = handle
__isub__ = unhandle
__call__ = fire
__len__ = getHandlerCount
I have some model class defined like this:
class SomeModel(object):
def __init__(self):
self._foo = 0
self.fooChanged = Event()
#property
def foo(self):
return self._foo
#foo.setter
def foo(self, value):
self._foo = value
self.fooChanged(value)
Now, suppose that I want to change foo like this:
model = SomeModel()
other_model = SomeModel()
model.fooChanged += other_model.foo
model.foo = 1
After model.foo = 1, I get following error:
TypeError: 'int' object is not callable
Now, suppose that I use this code for defining model:
class SomeModel(object):
def __init__(self):
self._foo = 0
self.fooChanged = Event()
def get_foo(self):
return self._foo
def set_foo(self, value):
self._foo = value
self.fooChanged(value)
foo = property(get_foo, set_foo)
and this code to change the value of foo:
model = SomeModel()
other_model = SomeModel()
model.fooChanged += other_model.set_foo
model.foo = 1
Second version works fine, however, it seems little un-Pythonic to me. I have to define get_foo method, which I'd like to avoid (since properties are available). Is there some other workaround here, so first version of code could run?
Note: error will depend on self._foo type. If it's None, it will return error stating that NoneType is not callable, if it's string, error will state that str object is not callable.
After a lot of digging, I found this answer to be very informative and it pushed me in the right direction.
Using this knowledge, I was able to solve this problem by using:
model.fooChanged += lambda value: type(other_model).foo.__set__(other_model, value)
or
model.fooChanged += lambda value: type(other_model).foo.fset(other_model, value)
The later line looks more Pythonic to me, since no calls for double-underscore functions are made.
while you write model.fooChanged += other_model.foo, I guess what you actually want is its setter method, but as other_model.foo is a property object, you have to get from its class other_model.__class__.foo.fset, write as:
model.fooChanged += lambda value: other_model.__class__.foo.fset(other_model, value)
OTOH, I think your second version is pythonic to me, as:
Explicit is better than implicit.
I have a class with certain attributes.
I would like to define a method which sets other attributes based on the ones that have already been set. Something like this:
class Test:
def __init__(self, name):
self.name = name
self.list = []
max = None
def get_max(self)
self.max = max(list)
This doesn't work. I have a feeling I am doing something very wrong but I can't work out what. Please someone put me out of my misery.
Updated code gives me an Attribute error:
class Test:
def __init__(self, name):
self.name = name
self.lst = []
self.mx = None
def get_max(self)
self.mx = max(self.lst)
When I call the method, no exceptions are returned, but the self.mx attribute are not updated.
>>>t = Test('testname')
>>>t.lst = [1,2,3]
>>>t.get_max
>>>t.name
'testname'
>>>t.lst
[1,2,3]
>>>t.mx
AttributeError: Test instance has no attribute 'mx'
It would be self.list to refer to the attribute, list is referring to the python builtin list.
You would need to do something like:
class Test:
def __init__(self, name,lst):
self.name = name
self.lst = lst
self.mx = None # use self to create an attribute
def get_max(self):
self.mx = max(self.lst)
You can also simply just call max on the list whenever you want to get the max, you don't need to create an attribute and method simply to return or get the max of the list.
You should simply return the max of the list in the method if you really wanted to use a method to get the max:
class Test:
def __init__(self, name, lst):
self.name = name
self.lst = lst
def get_max(self):
return max(self.lst)
t = Test("foo", [1,2,3,4])
print(t.get_max())
4
The design of your class really depends on what exactly you want to do in it but you don't getters or setters in python.
If you want to do something based on the max element in the list you don't need an attribute:
class Test:
def __init__(self, name,lst):
self.name = name
self.lst = lst
def some_meth(self):
if max(self.lst) > 2:
# do something
Sorry if this is a silly question, but I could not make my mind up how it could work.
I defined an iterator which has a structure like that (it is a bit more complicated, but the model will do the job):
class MyIterator ():
def __init__(self):
print ('nothing happening here')
def __iter__ (self):
self.a_list=[x for x in range (10)]
for y in a_list:
print(y)
def __next__ (self):
self.a_list = [x+1 for x in self.a_list]
for y in a_list:
print (y)
But how can I loop over it? Do I always have to call the methods manually? Or am I simply using the wrong tool?
One of the problems is that you are mixing two concepts: And
iterable defines an __iter__() method that returns an iterator,
but no __next__() method. An iterator in turn defines a
__next__() method, and a trivial __iter__() implementation that
returns self. Something like this:
class Iterable(object):
def __iter__(self):
return Iterator()
class Iterator(object):
def __init__(self):
self.i = 0
def __iter__(self):
return self
def __next__(self):
result = self.i
self.i += 1
return result
An alternative is to define the __iter__() method of the iterable as a generator function:
class Iterable(object):
def __iter__(self):
i = 0
while True:
yield i
i += 1