I am new to oop in python.
Below is a class for a mathod that is similar to range() except that it is inclusive for the range boundary.
I am trying to create an indexing method inside the class so that when a specific index is called the element with that index is returned. I read that __getitem__ can perform indexing yet I have not been successful in implementing it correctly. If there is a more efficient way not necessarily using __getitem__ please advise.
Please take a look at the code below, this is a simple code aimed at learning how create classes.
the method starting at def __getitem__(self,index) is the one that does not work and this corresponds to calling the index at the end o[4] which is what I would like to achieve.
class inclusive_range:
def __init__(self, *args):
numargs = len(args)
if numargs < 1: raise TypeError('requires at least one argument')
elif numargs == 1:
self.stop = args[0]
self.start = 0
self.step = 1
elif numargs == 2:
(self.start,self.stop) = args
self.step = 1
elif numargs == 3:
(self.start,self.stop,self.step) = args
else:
raise TypeError('three arguments required at most, got {}'.format(numargs))
def __iter__(self): # this makes the function (the method) and iterable function
i = self.start
while i <= self.stop:
yield i
i += self.step
def __getitem__(self,index):
return self[index]
print(self[index])
def main():
o = inclusive_range(5, 10, 1)
for i in o: print(i, end=' ')
o[2]
if __name__ == "__main__": main()
Thank you
You can just calculate the number based on self.start, the index and the step size. For your object to be a proper sequence you also need a length, which comes in handy when testing for the boundaries:
def __len__(self):
start, stop, step = self.start, self.stop, self.step
if step < 0:
lo, hi = stop, start
else:
lo, hi = start, stop
return ((hi - lo) // abs(step)) + 1
def __getitem__(self, i):
length = len(self)
if i < 0:
i += length
if 0 <= i < length:
return self.start + i * self.step
raise IndexError('Index out of range: {}'.format(i))
The above is based on my own translation of the range() source code to Python, with a small adjustment to account for the end being inclusive.
I'd cache the __len__ result in __init__, to avoid having to re-calculate it each time you want to know the length.
Related
I've started to learn iterators am trying to implement them myself.
I have created a class that should provide numbers within a range from user defined start to user defined end, in an iterable form.
Now my code looks like this:
class Can_be_divided_by_three:
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.start > self.end:
raise StopIteration
item = self.start
self.start += 1
if item % 3 == 0:
return item
iterator = Can_be_divided_by_three(3, 8)
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
And this is the output:
3
None
None
6
So actually there is output even if the number is not divided by 3 and it is None.
Am I getting this wrong, and if yes, how to get it right? I actually need the only output in the form of number divisible by 3 with iteration capabilities.
Thank you in advance.
As per your logic next method will return the number if the number is divisible by 3 but you have not specified what this function should do if the number is not divisible by three, so try below code:
class Can_be_divided_by_three:
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.start > self.end:
raise StopIteration
item = self.start
self.start += 1
if item % 3 == 0:
return item
else:
return self.__next__()
iterator = Can_be_divided_by_three(3, 8)
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
Based on John Coleman's comment that you only need to find the smallest multiple of 3, you can achieve the same with this:
def Can_be_divided_by_three(start, end):
while start % 3:
start += 1
for i in range(start, end, 3):
yield i
You only return item if item % 3 == 0.
You should return something else in other scenarios if you want to avoid Nones
The code which you have written is right and would return number divisible by 3 within user defined range. The reason you are finding None in the output is because of the print() function.
Try running the only next(iterator). In Jupyter the iterator returns only 3 & 6
Again, I am new to python. I need a FIFO with a limited depth.
F.e the depth is 5000, so after 5000 and more added items the first one's should be deleted to keep its depth is 5000. Some times I need to read 'the first' one and sometimes read the 'last one'. If I read the first one then it should be removed.
# class
class DictionaryDeque:
from collections import OrderedDict
def __init__(self, dequeDict=10):
self._stack = OrderedDict()
self._range = dictRange
self.setRange(dictRange)
self._len = 0
def len(self):
self._len = len(self._stack)
return self._len
def getRange(self):
return self._range
def setRange(self, range):
self._range = range
# change the dict range if the dict has more items
self.do_pop()
def add(self, key, value):
self._stack[key] = value
self.len()
self.do_pop()
def stack(self):
if self._len > 0:
self.do_pop()
return self._stack
else:
return ""
def last(self):
self.do_pop()
if self._len > 0:
return list(self._stack)[-1]
else:
return list(self._stack)[0]
def first(self):
self.do_pop()
return list(self._stack)[0]
def do_pop(self):
while self.len() > self._range:
self._stack.popitem(last=False)
self.len()
# end of class
dequeDict = DictionaryDeque(30)
for i in range (0, 40):
now = str(datetime.datetime.now())
dequeDict.add(now, i)
dequeDict.setRange(10)
print(dequeDict.len())
print(dequeDict.last())
print(dequeDict.first())
print(dequeDict.stack())
I have to implement the 'first read and remove' and some more functions, but before I start with that, I would love to know if this the/a way to go, or should there be something better?
Is there a way to avoid the list part in
list(self._stack)[0]
?
BTW, what is a good name for this class? < class name changed
Thank you
I can only push two values onto the stack array that I have created when I use the myPush() function. The reason I need to do it this way is because I am not allowed to use the built-in stack functions. Plus I have to use an array not a lit. Thank you.
I can only push two values onto the stack array that I have created when I use the myPush() function. The reason I need to do it this way is because I am not allowed to use the built-in stack functions. Plus I have to use an array not a lit. Thank you.
class Stack:
def __init__(self, data):
if data > 0:
self.stack = [0] * data
self.top = -1
self.stack[self.top] = self.stack[-1]
elif data <= 0:
self.stack = [0] * 10
self.top = -1
def showStack(self):
for i in self.stack:
print(i, end=" ")
return
def isEmpty(self):
return self.stack == []
def myEmpty(self): #complete this function
return # just a placeholder
def push(self, data):
self.stack.append(data)
def myPush(self, data):
if data == 0:
print("You did not enter a value.")
elif self.sizeStack() <= 10:
current = self.stack[stack.top]
self.stack[stack.top] = data
self.stack[stack.top - 1] = current
def pop(self):
data = self.stack[-1]
del self.stack[-1]
return data
def myPop(self): #complete this function
return # just a placeholder
def myPeek(self):
temp = self.top
return temp
def sizeStack(self):
return len(self.stack)
userVal = int(input("Enter the size of the stack: "))
stack = Stack(userVal)
while True:
print('\n1 display the stack')
print('2 add a value to the stack')
print('3 check the value at the top of the stack')
print('4 remove the value at the top of the stack')
print('5 check if the stack is empty')
print('99 quit')
option = int(input("Enter your choice: "))
if option == 1:
stack.showStack()
elif option == 2:
temp = int(input("Enter a number to add to the stack: "))
stack.myPush(temp)
elif option == 3:
print(stack.peek())
elif option == 99:
break
else:
print('Wrong option')
print
try defining a some customized functions that map to your list. Read up on customization here
class stack:
def __init__(self, data):
self.stack = [0] * data
self.top = -1
# whatever you want to do to init the list
def __str__(self): # replaces your showstack function now just use print(stack)
return self.stack
def __iter__(self): # creates an iterator you can use
return iter(self.stack)
def __len__(self): # replaces your sizestack function
return len(self.stack)
basically just adjust the methods you need.
Here is a quote from https://stackoverflow.com/users/893/greg-hewgill answer to Explain Python's slice notation.
Python is kind to the programmer if there are fewer items than you ask
for. For example, if you ask for a[:-2] and a only contains one
element, you get an empty list instead of an error. Sometimes you
would prefer the error, so you have to be aware that this may happen.
So when the error is prefered, what is the Pythonic way to proceed ? Is there a more Pythonic way to rewrite this example ?
class ParseError(Exception):
pass
def safe_slice(data, start, end):
"""0 <= start <= end is assumed"""
r = data[start:end]
if len(r) != end - start:
raise IndexError
return r
def lazy_parse(data):
"""extract (name, phone) from a data buffer.
If the buffer could not be parsed, a ParseError is raised.
"""
try:
name_length = ord(data[0])
extracted_name = safe_slice(data, 1, 1 + name_length)
phone_length = ord(data[1 + name_length])
extracted_phone = safe_slice(data, 2 + name_length, 2 + name_length + phone_length)
except IndexError:
raise ParseError()
return extracted_name, extracted_phone
if __name__ == '__main__':
print lazy_parse("\x04Jack\x0A0123456789") # OK
print lazy_parse("\x04Jack\x0A012345678") # should raise ParseError
edit: the example was simpler to write using byte strings but my real code is using lists.
Here's one way that is arguably more Pythonic. If you want to parse a byte string you can use the struct module that is provided for that exact purpose:
import struct
from collections import namedtuple
Details = namedtuple('Details', 'name phone')
def lazy_parse(data):
"""extract (name, phone) from a data buffer.
If the buffer could not be parsed, a ParseError is raised.
"""
try:
name = struct.unpack_from("%dp" % len(data), data)[0]
phone = struct.unpack_from("%dp" % (len(data)-len(name)-1), data, len(name)+1)[0]
except struct.error:
raise ParseError()
return Details(name, phone)
What I still find unpythonic about that is throwing away the useful struct.error traceback to replace with a ParseError whatever that is: the original tells you what is wrong with the string, the latter only tells you that something is wrong.
Using a function like safe_slice would be faster than creating an object just to perform the slice, but if speed is not a bottleneck and you are looking for a nicer interface, you could define a class with a __getitem__ to perform checks before returning the slice.
This allows you to use nice slice notation instead of having to pass both the start and stop arguments to safe_slice.
class SafeSlice(object):
# slice rules: http://docs.python.org/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer-xrange
def __init__(self,seq):
self.seq=seq
def __getitem__(self,key):
seq=self.seq
if isinstance(key,slice):
start,stop,step=key.start,key.stop,key.step
if start:
seq[start]
if stop:
if stop<0: stop=len(seq)+stop
seq[stop-1]
return seq[key]
seq=[1]
print(seq[:-2])
# []
print(SafeSlice(seq)[:-1])
# []
print(SafeSlice(seq)[:-2])
# IndexError: list index out of range
If speed is an issue, then I suggest just testing the end points instead of doing arithmetic. Item access for Python lists is O(1). The version of safe_slice below also allows you to pass 2,3 or 4 arguments. With just 2 arguments, the second will be interpreted as the stop value, (similar to range).
def safe_slice(seq, start, stop=None, step=1):
if stop is None:
stop=start
start=0
else:
seq[start]
if stop<0: stop=len(seq)+stop
seq[stop-1]
return seq[start:stop:step]
Here is a more pythonic, more general rewrite of your code:
class ParseError(Exception):
pass
def safe_slice(data, start, end, exc=IndexError):
"""0 <= start <= end is assumed"""
r = data[start:end]
if len(r) != end - start:
raise exc()
return r
def lazy_parse(data):
"""extract (name, phone) from a data buffer.
If the buffer could not be parsed, a ParseError is raised."""
results = []
ptr = 0
while ptr < len(data):
length = ord(data[ptr])
ptr += 1
results.append(safe_slice(data, ptr, ptr + length, exc=ParseError))
ptr += length
return tuple(results)
if __name__ == '__main__':
print lazy_parse("\x04Jack\x0A0123456789") # OK
print lazy_parse("\x04Jack\x0A012345678") # should raise ParseError
Most of the changes are in the body of lazy_parse -- it will now work with multiple values instead of just two, and the correctness of the whole thing still depends on the last element being able to be parsed out exactly.
Also, rather than have safe_slice raise an IndexError which lazy_parse changes into a ParseError, I have lazy_parse give the desired exception to safe_slice to be raised in case of error (lazy_parse defaults to IndexError if nothing is passed to it).
Finally, lazy_parse isn't -- it's processing the entire string at once and returning all the results. 'Lazy' in Python means doing only what is needed to return the next piece. In the case of lazy_parse it would mean returning the name, then on a later call returning the phone. With only a slight modification we can make lazy_parse lazy:
def lazy_parse(data):
"""extract (name, phone) from a data buffer.
If the buffer could not be parsed, a ParseError is raised."""
ptr = 0
while ptr < len(data):
length = ord(data[ptr])
ptr += 1
result = (safe_slice(data, ptr, ptr + length, ParseError))
ptr += length
yield result
if __name__ == '__main__':
print list(lazy_parse("\x04Jack\x0A0123456789")) # OK
print list(lazy_parse("\x04Jack\x0A012345678")) # should raise IndexError
lazy_parse is now a generator that returns one piece at a time. Notice that we had to put list() around the lazy_parse call in the main section get lazy_parse to give us all the results in order to print them.
Depending on what you're doing this might not be the desired way, however, as it can be more difficult to recover from errors:
for item in lazy_parse(some_data):
result = do_stuff_with(item)
make_changes_with(result)
...
By the time the ParseError is raised you may have made changes that are difficult or impossible to back out. The solution in a case like this would be to do the same as we did in the print part of main:
for item in list(lazy_parse(some_data)):
...
The list call completely consumes lazy_parse and gives us a list of the results, and if an error was raised we'll know about it before we process the first item in the loop.
Here is a complete SafeSlice class re-using https://stackoverflow.com/users/107660/duncan and
https://stackoverflow.com/users/190597/unutbu answers.
The class is quite big because it have full slice support (start, stop and step). This may be overkill for the simple job done in the example but for a more complete real life problem, it might prove useful.
from __future__ import division
from collections import MutableSequence
from collections import namedtuple
from math import ceil
class ParseError(Exception):
pass
Details = namedtuple('Details', 'name phone')
def parse_details(data):
safe_data = SafeSlice(bytearray(data)) # because SafeSlice expects a mutable object
try:
name_length = safe_data.pop(0)
name = safe_data.popslice(slice(name_length))
phone_length = safe_data.pop(0)
phone = safe_data.popslice(slice(phone_length))
except IndexError:
raise ParseError()
if safe_data:
# safe_data should be empty at this point
raise ParseError()
return Details(name, phone)
def main():
print parse_details("\x04Jack\x0A0123456789") # OK
print parse_details("\x04Jack\x0A012345678") # should raise ParseError
SliceDetails = namedtuple('SliceDetails', 'first last length')
class SafeSlice(MutableSequence):
"""This implementation of a MutableSequence gives IndexError with invalid slices"""
def __init__(self, mutable_sequence):
self._data = mutable_sequence
def __str__(self):
return str(self._data)
def __repr__(self):
return repr(self._data)
def __len__(self):
return len(self._data)
def computeindexes(self, ii):
"""Given a slice or an index, this method computes what would ideally be
the first index, the last index and the length if the SafeSequence was
accessed using this parameter.
None indexes will be returned if the computed length is 0.
First and last indexes may be negative. This means that they are invalid
indexes. (ie: range(2)[-4:-3] will return first=-2, last=-1 and length=1)
"""
if isinstance(ii, slice):
start, stop, step = ii.start, ii.stop, ii.step
if start is None:
start = 0
elif start < 0:
start = len(self._data) + start
if stop is None:
stop = len(self._data)
elif stop < 0:
stop = len(self._data) + stop
if step is None:
step = 1
elif step == 0:
raise ValueError, "slice step cannot be zero"
length = ceil((stop - start) / step)
length = int(max(0, length))
if length:
first_index = start
last_index = start + (length - 1) * step
else:
first_index, last_index = None, None
else:
length = 1
if ii < 0:
first_index = last_index = len(self._data) + ii
else:
first_index = last_index = ii
return SliceDetails(first_index, last_index, length)
def slicecheck(self, ii):
"""Check if the first and the last item of parameter could be accessed"""
slice_details = self.computeindexes(ii)
if slice_details.first is not None:
if slice_details.first < 0:
# first is *really* negative
self._data[slice_details.first - len(self._data)]
else:
self._data[slice_details.first]
if slice_details.last is not None:
if slice_details.last < 0:
# last is *really* negative
self._data[slice_details.last - len(self._data)]
else:
self._data[slice_details.last]
def __delitem__(self, ii):
self.slicecheck(ii)
del self._data[ii]
def __setitem__(self, ii, value):
self.slicecheck(ii)
self._data[ii] = value
def __getitem__(self, ii):
self.slicecheck(ii)
r = self._data[ii]
if isinstance(ii, slice):
r = SafeSlice(r)
return r
def popslice(self, ii):
"""Same as pop but a slice may be used as index."""
self.slicecheck(ii)
r = self._data[ii]
if isinstance(ii, slice):
r = SafeSlice(r)
del self._data[ii]
return r
def insert(self, i, value):
length = len(self._data)
if -length <= i <= length:
self._data.insert(i, value)
else:
self._data[i]
if __name__ == '__main__':
main()
I have code for a Range class like this:
class Range:
def __init__(self, start, end):
self.setStart(start)
self.setEnd(end)
def getStart(self):
return self.start
def setStart(self, s):
self.start = s
def getEnd(self):
return self.end
def setEnd(self, e):
self.end = e
def getLength(self):
return len(range(self.start, self.end))
def overlaps(self, r):
if (r.getStart() < self.getEnd() and r.getEnd() >= self.getEnd()) or \
(self.getStart() < r.getEnd() and self.getEnd() >= r.getEnd()) or \
(self.getStart() >= r.getStart() and self.getEnd() <= r.getEnd()) or \
(r.getStart() >= self.getStart() and r.getEnd() <= self.getEnd()):
return True
else:
return False
My assignment is to create a subclass of Range, called DNAFeature, that represents a Range that also has a strand and a sequence name:
Implement setStrand and getStrand, which set and return strand information, and setSeqName and getSeqName, which set or return the name of the sequence the feature belongs to.
If a feature is on the minus (reverse) strand, getStrand() should return ‐1. If a feature is on the plus strand, getStrand() should return 1. If strand is not set, getStrand() should return 0.
I have tried to write something but doesn't look right at all for me, can everyone please help me with this, thank you so much guys, this is my code:
class DNAFeature(Range):
def __init__(self, strand, sequence):
self.setStrand(strand)
self.setSeqName(sequence)
def getSeqName(self):
return self.plus or minus
def setSeqName(self, seq):
self.sequence = seq
def getStrand(self):
if self.getSeqName(self.strand) == 'plus':
return 1
if self.getSeqName(self.strand) == 'minus':
return -1
else:
return 0
def setStrand(self, strand):
self.strand = strand
In general it is much easier to answer questions if you provide a specific error message or thing that is going wrong. Here's what happened when I tried to run the above:
First up:
`SyntaxError: invalid syntax`
on if seq == POSITIVE. What's wrong here? Oh yes, you're missing a colon after the conditional. If you add that the file at least parses. So let's try doing some coding:
# Your code here, then:
feature = DNAFeature()
Running that gives:
TypeError: __init__() takes exactly 3 positional arguments (1 given)
Oh, OK, we need to pass some arguments to the initialiser of DNAFeature. Let's put this on the + strand, and call it foo:
feature = DNAFeature(1, "foo")
Now we get:
AttributeError: 'DNAFeature' object has no attribute 'setStrand'
What's that about? OK, you haven't defined setStrand. (Note: you shouldn't have to. But more on that later.) Let's define it:
def setStrand(self, strand):
self.strand = strand
I don't want to go through the rest of the problems with the code (hint: you need to define variables before you use them), but this is the sort of thing you should be doing.
Right, something different. The above is bad code. I hope you've written the Range class and that it hasn't been provided as part of the course, because if it has you're taking a badly-taught course. The main problem is the use of getters and setters -- I'm guessing you're Java-born and bred? In Python you don't need to write getters and setters for everything, because you can always add them in later if you need them. Instead, just use class attributes. Look at the following code for Range:
class Range:
def __init__(self, start, end):
self.start = start
self.end = end
def length(self):
return self.end - self.start
def overlaps(self, other):
return not(self.end < other.start or other.end < self.start)
Isn't that much nicer? No more nasty accessors, no icky comparisons in the overlaps method... It helps if you work out the logic that your code is trying to implement before you implement it.
See if you can write a better DNAFeature now.
You still haven't told me what getStrand should, do, but here's what I think you're aiming towards. Suppose the strand name that gets passed to __init__ is of the form "+name" or "-name". You can then do the following:
def __init__(self, strand):
sequence = strand[0] #first character of strand
if sequence == "+":
self.strand = 1
self.sequence= strand[1:]
elif sequence == "-":
self.strand = -1
self.sequence = strand[1:]
else:
self.strand = 0
self.sequence = strand
See if you can work out how that works.
In the most generic case (without making any assumptions), it seems that this is what you need:
class DNAFeature(Range):
def __init__(self, start, end):
self.setStart(start)
self.setEnd(end)
self.strand = None
self.sequencename = None
def setStrand(self, s):
self.strand = s
def getStrand(self):
if self.sequenceName == 'plus':
return 1
elif self.sequenceName == 'minus':
return -1
else:
return 0
def setSequenceName(self, s):
self.sequencename = s
def getSequenceName(self, s):
return self.sequenceName
You will notice that here, I have redefined init. There is a reason for this. I remember that in one of your earlier questions, you had mentioned that this was a Java assignment, just renamed to python. In Java, constructors are not inherited (correct me if I'm wrong). Therefore, if the same grading rubric is being used, you will lose marks for not redefining the constructor here.
Hope this helps