I'm currently learning Python and I have this exercise where you have to handle exceptions. The goal of this one is to throw an exception when the value of the given key is not in the range of the list "self.data".
This is what I'm supposed to enter in my function to test it:
v = Vecteur(-9, -6, 2, 3, -2)
v[-1]
Now I'm supposed to get an IndexError
class Vecteur:
def __init__(self, *valeurs):
self.data = list(valeurs)
def __getitem__(self,key):
try:
erreur = self.data[key]
except IndexError:
print('indice invalide pour ce vecteur')
return self.data[key]
def __setitem__(self,key,item):
self.data[key] = item
Can one of you enlighten me on the subject?
Thanks in advance!
Your problem stems from the fact that you're catching the IndexError, and then printing, and letting execution fall through.
I'd do this instead:
class Vecteur:
def __init__(self, *valeurs):
self.data = list(valeurs)
def __getitem__(self,key):
try:
return self.data[key]
except IndexError:
raise IndexError('indice invalide pour ce vecteur')
def __setitem__(self,key,item):
self.data[key] = item
By the way, -1 is a valid index. Therefore, you might want to change your __getitem__:
def __getitem__(self, key):
if key < 0:
raise ValueError("Index has to be greater than 0")
# the rest of your __getitem__ here
Related
I have written the below code to implement the stack class instead of using the build in function, not sure if I have got it rigth. Can someone please check for me and advise if there is an error?
class DSAStack():
maxCap = 100
def __init__(self):
self.stack = []
self.count = 0
def isEmpty(self):
if self.count == 0:
return True
else:
return false
def isFull(self):
if self.count == maxCap:
return True
else:
return false
def push(self, item):
if self.isFull:
raise ('Stack is full...')
else:
self.stack[self.count] = item
self.count += 1
def pop(self):
self.topVal = self.top
self.stack[self.count - 1] = 0
self.count = self.count - 1
return self.topVal
def top(self):
if self.isEmpty:
raise ('Stack is empty...')
else:
self.top = self.stack[self.count - 1]
Firstly, you need not keep track of count. Accessing the length of the list will do that for you.
def __init__(self):
self.stack =[]
Now, your logic for isEmpty and isFull was alright, but since we are removing the self.count variable, here's how you'd go about it
def isEmpty(self):
return len(self.stack) == 0 #Directly return true or false
def isFull(self):
return len(self.stack) == maxCap #Directly return true or false
In your push function, there are a few things I'd like to point out.
First, you need to call the isFull function. So we need to add parentheses to it.
Secondly, you cant just raise ('Stack is full...'), you will get a TypeError: Exceptions must derive from BaseException. This basically means what you raise must be some type of error.
Lastly, you can't add a new element by doing self.stack[self.count]=item since you will get an IndexError
Here are the changes:
def push(self,item):
if self.isFull(): #Need to call the function, self.isFull is not a variable
raise Exception('Stack is full...') #Or any other type of error
else:
self.stack.append(item) #Adds new item to the end of self.stack
Now coming to the pop function, setting a value to zero in a list does not really get rid of it. And it can cause a lot of confusion, especially since we are using the len(self.stack) to implement this.
Here's how you would pop:
def pop(self):
if self.isEmpty():
raise Exception('Stack is empty...')
else:
return self.stack.pop() #List has a built-in pop method
So now we don't really need the top function. And that concludes that. Btw, in your top function, you have defined self.top = self.stack[self.count-1]
Giving the same name to a function and a variable is never really a good idea.
To implement the pop functionality yourself, you could do the following:
def pop(self):
if self.isEmpty():
raise Exception('Stack is empty...')
else:
topVal = self.stack[-1] #-1 gives you the last element
#if stack will contain objects instead of primitive data types, use self.stack[-1].copy()
del self.stack[-1] #Deletes the element
return topVal
Polishing your top function will be like this:
def top(self):
if self.isEmpty():
raise Exception('Stack is empty...')
else:
return self.stack[-1]
def pop(self):
topVal = self.top()
del self.stack[-1]
return topVal
Note how the top function is defined before the pop function.
Also, try to test the code out and resolving any issues.
I am looking to return a status when setting an instance variable. This return value will either indicate that the value was set or the value was invalid. For example:
class C():
def __init__(self):
self._attr = 1
#property
def attr(self):
return self._attr
#attr.setter
def attr(self, val):
if val < 10:
self._attr = val
return 'value set'
else:
return 'value must be < 10'
status = C().attr = 11 # should return failed
status = C().attr = 9 # should return worked
Adding return values to all my setters would be time-consuming and seems to be bad practice. Is there a better way to get a status from a setter?
The other solution I thought of was to write a "setSetter()" function that calls attr = val then checks if attr == val (which works like a status variable). However, it seems like there should be a better method.
Also, if there is a better way to structure the flow control so that attributes aren't set to invalid values I'm open to changing my strategy.
Thanks in advance for the help!
The standard way to indicate failure in Python and/or prevent code from reaching an invalid state is to raise an exception to indicate you've encountered a situation where you can't continue execution. This doesn't require you to modify the return type:
#attr.setter
def attr(self, val: int) -> None:
if val < 10:
self._attr = val
else:
raise ValueError('value must be < 10')
try:
C().attr = 9 # works
C().attr = 11 # raises ValueError and goes to nearest matching except
C().attr = 13 # is never executed
except ValueError as e:
print(e) # prints 'value must be < 10'
I have a sorted array:
arr = ['Alexander', 'Belman', 'Erik', 'Nicholas', ... , 'Zahir']
I would like to do something like this:
arr['B':'M'] # ['Belman', 'Erik']
How can I create a class and implement
__getitem__
__index__
in the right way to achieve this ?
I am thinking of using something like
def __getitem__(self, key):
if isinstance(key, slice):
return [self.list[i] for i in range(key.start, key.stop)]
return self.list[key]
but I don't know how to index the strings. How can I create an
__index__
method to apply a binarySearch to self.list and return the correct indices ?
I think you can get away with as simple implementation as below:
from collections import UserList
class MyList(UserList):
def __getitem__(self, key):
if isinstance(key, slice):
return [e for e in self.data if key.start <= e < key.stop]
# TODO implement the rest of the usecases and/or error handling...
# for now slicing with integers will miserably fail,
# and basic integer indexing returns None
arr = MyList(['Alexander', 'Belman', 'Erik', 'Nicholas', 'Zahir'])
print(arr['B':'M'])
which will output
['Belman', 'Erik']
likewise,
print(arr['Alex':'Er'])
will output
['Alexander', 'Belman']
Note that I used key.start <= e < key.stop to be in line with the inclusive:exclusive ([)) behavior which is used throughout python.
Also note that I implemented only the string slices usecase. You can implement the other usecases and error handling as you see fit.
I also post my solution with binary search using numpy
class Stock():
def __init__(self, name, date):
self.name = name
self.date = np.array(date)
def __getitem__(self, key):
if isinstance(key, slice):
if key.start is not None and key.stop is not None:
return self.date[np.searchsorted(self.date, key.start, side='left', sorter=None):np.searchsorted(self.date, key.stop, side='left', sorter=None)]
elif key.start is not None:
return self.date[np.searchsorted(self.date, key.start, side='left', sorter=None):]
elif key.stop is not None:
return self.date[:np.searchsorted(self.date, key.stop, side='left', sorter=None)]
else:
return self.date[:]
i = np.searchsorted(self.date, key, side='left', sorter=None)
if key != self.date[i]:
raise KeyError('key: {} was not found!'.format(key))
else:
return self.date[i]
aapl = Stock('aapl', ['2010','2012', '2014', '2016', '2018'])
print(aapl['2011':])
print(aapl['2014':'2017'])
print(aapl[:'2016'])
print(aapl['2010'])
print(aapl['2013'])
'''
['2012' '2014' '2016' '2018']
['2014' '2016']
['2010' '2012' '2014']
2010
Traceback (most recent call last):
File "C:\Users\...\Desktop\...\stock.py", line ##, in <module>
print(aapl['2013'])
File "C:\Users\...\Desktop\...\stock.py", line ##, in __getitem__
raise KeyError('key: {} was not found!'.format(key))
KeyError: 'key: 2013 was not found!'
'''
I have data like data = [[t1, t2, ...], [v1, v2, ...]]. I want to wrap this in a class so I can call data.t instead of having to use data[0].
I tried to do this with the following:
class Variable:
def __init__(self, data):
self.t = data[0]
self.v = data[1]
def __getitem__(self, key):
if key == 0:
return self.t
elif key == 1:
return self.v
else:
raise ValueError("not valid key '{}'".format(key))
def __setitem__(self, key, value):
if key == 0:
self.t = value
elif key == 1:
self.v = value
else:
raise ValueError("not valid key '{}'".format(key))
The reason for the __getitem__ and __setitem__ overloading is for backwards compability so that data[0] still works. This works for most things, however I run into problems with the following call:
func_that_takes_two_arguments(*data) # unpacking data
The error I get is
/Users/pingul/Workspace/lhcfill/oml.py in __getitem__(self, key)
52 return self.val
53 else:
---> 54 raise ValueError("not valid key '{}'".format(key))
55
56 def __setitem__(self, key, value):
ValueError: not valid key '2'
How can I make my class work properly with the argument unpacking operator?
The * operator works by iterating over the object. This iteration can well be performed with only implementing __getitem__(), but your implementation is faulty. Instead if raising ValueError, you should throw IndexError which signals the end of the iteration.
See also https://docs.python.org/3/reference/datamodel.html#object.getitem which explicitly states
Note: for loops expect that an IndexError will be raised for illegal indexes to allow proper detection of the end of the sequence.
https://docs.python.org/2/library/functions.html#iter states that this is called the "sequence protocol".
I would like to expand on the autovivification example given in a previous answer from nosklo to allow dictionary access by tuple.
nosklo's solution looks like this:
class AutoVivification(dict):
"""Implementation of perl's autovivification feature."""
def __getitem__(self, item):
try:
return dict.__getitem__(self, item)
except KeyError:
value = self[item] = type(self)()
return value
Testing:
a = AutoVivification()
a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6
print a
Output:
{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}
I have a case where I want to set a node given some arbitrary tuple of subscripts. If I don't know how many layers deep the tuple will be, how can I design a way to set the appropriate node?
I'm thinking that perhaps I could use syntax like the following:
mytuple = (1,2,3)
a[mytuple] = 4
But I'm having trouble coming up with a working implementation.
Update
I have a fully working example based on #JCash's answer:
class NestedDict(dict):
"""
Nested dictionary of arbitrary depth with autovivification.
Allows data access via extended slice notation.
"""
def __getitem__(self, keys):
# Let's assume *keys* is a list or tuple.
if not isinstance(keys, basestring):
try:
node = self
for key in keys:
node = dict.__getitem__(node, key)
return node
except TypeError:
# *keys* is not a list or tuple.
pass
try:
return dict.__getitem__(self, keys)
except KeyError:
raise KeyError(keys)
def __setitem__(self, keys, value):
# Let's assume *keys* is a list or tuple.
if not isinstance(keys, basestring):
try:
node = self
for key in keys[:-1]:
try:
node = dict.__getitem__(node, key)
except KeyError:
node[key] = type(self)()
node = node[key]
return dict.__setitem__(node, keys[-1], value)
except TypeError:
# *keys* is not a list or tuple.
pass
dict.__setitem__(self, keys, value)
Which can achieve the same output as above using extended slice notation:
d = NestedDict()
d[1,2,3] = 4
d[1,3,3] = 5
d[1,2,'test'] = 6
This seems to work
def __setitem__(self, key, value):
if isinstance(key, tuple):
node = self
for i in key[:-1]:
try:
node = dict.__getitem__(node, i)
except KeyError:
node = node[i] = type(self)()
return dict.__setitem__(node, i, value)
return dict.__setitem__(self, key, value)