Why __str__(self) doesn't work when calling the print() function? - python

I'm diving into OOP and learning magic (or dunder) techniques. Python 3.8.8.
I created class FreqStack() with a pop() method that removes the most frequent elements and returns an updated stack.
class FreqStack():
def __init__(self, lst:list = None):
if lst is None:
self.stack = []
else:
self.stack = lst[::-1]
def push(self, el: int):
self.stack.insert(0, el)
return self.stack
def pop(self):
if len(self.stack) != 0:
hash_map = {}
for el in self.stack:
hash_map[el] = hash_map.get(el, 0) + 1
most_freq_el = max(hash_map, key=hash_map.get)
while most_freq_el in self.stack:
self.stack.remove(most_freq_el)
return self.stack
else:
return 'Stack is empty!'
def __str__(self):
return '\n|\n'.join(str(el) for el in self.stack)
I also added the dunder method str(), which, as far as I understand correctly, must return a custom string when calling the print() function.
However, the print() function in the example below, instead of returning a string, returns a list.
lst = [1, 1, 1, 5, 5, 5, 3, 3, 3, 7, 7, 9]
freq_stack = FreqStack(lst)
for i in range(6):
print(freq_stack.pop())
Output:
[9, 7, 7, 5, 5, 5, 1, 1, 1]
[9, 7, 7, 1, 1, 1]
[9, 7, 7]
[9]
[]
Stack is empty!
I googled everything related to this problem, and couldn't solve it. What am I doing wrong?

You are printing the return value of pop, not the freq_stack itself.
The __str__ method is for the freq_stack object, so you may try something like:
freq_stack.pop()
print(freq_stack)

Related

Why is this iterator returning none with the expected output?

I am writing an iterator that should return all odd numbers from a giver iter. However, my code is printing none as well as the number I expect returned.
def __iter__(self):
return self
def __next__(self):
k = next(self.other_it)
if (k % 2) != 0: return k
if __name__ == '__main__':
for i in OddIterator([2, 7, 4, 6, 7, 5, 9, 8, 11]):
print(i)

Problem When Creating Custom Class in Python

With the code below:
class Int_set(list):
def __init__(self):
self.vals=[]
self.index=0
def insert(self, elm): #assume a string of numbers
for x in elm:
if x not in self.vals:
self.vals.append(int(x))
return self.vals
def member(self, elm):
return elm in self.vals
def remove(self, elm):
try:
self.vals.remove(elm)
except:
raise ValueError(str(elm)+' not found')
def get_members(self):
return self.vals[:]
def __str__(self):
if self.vals == []:
return '[]'
self.vals.sort()
result = ''
for e in self.vals:
result = result + str(e) + ','
return f'{{{result[:-1]}}}'
def union(self, other):
'''add all non-duplicate elements from other set to self set'''
print(len(other))
for e in range(len(other)):
if other[e] not in self.vals:
self.vals.append(other[e])
else:
continue
return self.vals
set1=Int_set()
set1.insert('123456')
print(set1.get_members())
set2=Int_set()
set2.insert('987')
print(set2.get_members())
print(set1.union(set2))
I get output:
[1, 2, 3, 4, 5, 6]
[9, 8, 7]
0 # the print(len(other)) in def union
[1, 2, 3, 4, 5, 6]
Notice that the def union(self, other) did not add in all the unique numbers from set2 to set1. However, if I use:
print(set1.union(set2.vals))
then I get:
[1, 2, 3, 4, 5, 6]
[9, 8, 7]
3 # the print(len(other)) in def union
[1, 2, 3, 4, 5, 6, 9, 8, 7]
What is causing this? I assume set2 inside .union(set2) was in the state when it was initialized(thus it's an empty list). But I inserted some numbers inside and printed it out
You call len on the instance of Int_set. This is derived from list and gives you the length of the instance itself which is 0. You need to overwrite __len__ in your class and give back the value of the instances val.
def __len__(self):
return len(self.vals)
And you have to change your union method too. At the moment you iterate over the members of the instance of Int_set and there are none. You have to iterate over the vals attribute.
def union(self, other):
'''add all non-duplicate elements from other set to self set'''
print(len(other))
for element in other.vals:
if element not in self.vals:
self.vals.append(element)
else:
continue
return self.vals
Overall it's unclear why you decided to make Int_set a subclass of list since you use no features from the list class.
Your problem is that you are trying to read values from class itself, you are using other instead of other.vals. You have answered your question when you tried with
print(set1.union(set2.vals))
So you can change your function to this:
def union(self, other):
'''add all non-duplicate elements from other set to self set'''
print(len(other.vals))
for e in range(len(other.vals)):
if other.vals[e] not in self.vals:
self.vals.append(other.vals[e])
else:
continue
return self.vals

Yield all full root-leaf paths through tree structure in Python

I'm trying to adapt this answer in two ways:
I want to make the traverse function a class method, and
I want a call to traverse to yield the list of all root-to-leaf paths (list of lists) in the tree
First change was trivial, second one I'm struggling with. Here's my class definition:
class createnode:
""" thanks to https://stackoverflow.com/a/51911296/1870832"""
def __init__(self,nodeid):
self.nodeid=nodeid
self.child=[]
def __str__(self):
print(f"{self.nodeid}")
def traverse(self, path = []):
path.append(self.nodeid)
if len(self.child) == 0:
#print(path)
yield path
path.pop()
else:
for child in self.child:
child.traverse(path)
path.pop()
I construct a tree with:
ROOT_NODE = 0
root = createnode(ROOT_NODE)
lvl1 = [createnode(1), createnode(2), createnode(3)]
root.child += lvl1
root.child[0].child += [createnode(4), createnode(5)]
root.child[1].child += [createnode(6), createnode(7)]
root.child[2].child += [createnode(8), createnode(9)]
Desired output for printing all full root-leaf paths (e.g. w/ code below)
paths = root.traverse()
for p in paths:
print(p)
is:
[0, 1, 4]
[0, 1, 5]
[0, 2, 6]
[0, 2, 7]
[0, 3, 8]
[0, 3, 9]
You need to look into a recursive generator.
I have corrected your setup code:
class createnode:
""" thanks to https://stackoverflow.com/a/51911296/1870832"""
def __init__(self,nodeid):
self.nodeid=nodeid
self.child=[]
def __str__(self):
print(f"{self.nodeid}")
def traverse(self, path = None):
if path is None:
path = []
path.append(self.nodeid)
if len(self.child) == 0:
yield path
path.pop()
else:
for child in self.child:
yield from child.traverse(path)
path.pop()
ROOT_NODE = 0
root = createnode(ROOT_NODE)
children = [createnode(32), createnode(5)]
root.child += children
paths = root.traverse()
for p in paths:
print(p)
Output:
[0, 32]
[0, 5]
I don't have any experience with yield yet but I'd do it like this:
class createnode:
""" thanks to https://stackoverflow.com/a/51911296/1870832"""
def __init__(self,nodeid):
self.nodeid=nodeid
self.child=[]
def __str__(self):
print(f"{self.nodeid}")
def traverse(self, path = []):
path.append(self.nodeid)
if len(self.child) == 0:
print(path)
path.pop()
else:
for child in self.child:
child.traverse(path)
path.pop()
ROOT_NODE = 0
root = createnode(ROOT_NODE)
lvl1 = [createnode(1), createnode(2), createnode(3)]
root.child += lvl1
root.child[0].child += [createnode(4), createnode(5)]
root.child[1].child += [createnode(6), createnode(7)]
root.child[2].child += [createnode(8), createnode(9)]
root.traverse()
[0, 1, 4]
[0, 1, 5]
[0, 2, 6]
[0, 2, 7]
[0, 3, 8]
[0, 3, 9]

How can we unpack an arbitrarily nested iterator, of iterators, of [...], of iterators?

If we have an iterator of non-iterators, then we can unroll (unpack) it as follows:
unroll = lambda callable, it: callable(it)
inputs = range(0, 10)
print(unroll(list, inputs))
# prints "[1, 2, 3, 4, 5, 6, 7, 8, 9]"
If we have an iterator of iterators or non-iterators, then we can unroll it as follows:
unroll = lambda callable, it: callable(map(callable, it))
inputs = [range(0, 2), range(2, 4), range(4, 6)]
print(unroll(list, inputs))
# prints "[[0, 1], [2, 3], [4, 5]]"
I don't want to flatten the iterator. A flattening of [[0, 1], [2, 3], [4, 5]] is [0, 1, 2, 3, 4, 5] I want to preserve nesting, but have fully-populated containers (lists, tuples, arrays, etc...) instead of iterators.
The question is, how can we unroll an iterator of arbitrarily nested iterators? My attempt is shown below, but it doesn't work.
import abc
class StringPreservationistBase(abc.ABC):
#abc.abstractmethod
def __str__(i):
raise NotImplementedError()
class StringPreservationist(StringPreservationistBase):
"""
The idea behind this class if you get
something which requires calculation, then
the result is stored for future read-like
operations until such a time that the value
becomes stale.
For example, if this was a `Square` class
`Square.get_area()` would only compute `length*width`
the first time.
After that, `Square.get_area()` would simply returned
the pre-calculated value stored in `area`.
If any member variable which `Square.getarea()`
reads from are written to, then the process resets.
That is, if `length` or `width` were written to,
then we go back to the implementation of
`Square.getarea()` which calculates `length*width`
For this particular class the result of
`__str__` is stored.
"""
# Any method with write permission
# is supposed to set state back to StringPreservationistState0
#
# That is, if string become stale, we
# delete the string
#
def __init__(i, elem, count: int):
i._count = count
i._elem = elem
i._state = i._StringPreservationistState0(i)
def __len__(i):
return i._count
def __iter__(i):
return itts.repeat(i._elem, i._count)
def __str__(i):
stryng = str(i._state)
i._state = i._StringPreservationistState1(i, stryng)
return stryng
class _StringPreservationistState1(StringPreservationistBase):
def __init__(i, x, stryng: str):
i._x = x
i._stryng = stryng
def __str__(i):
return i._stryng
class _StringPreservationistState0(StringPreservationistBase):
def __init__(i, x):
i._x = x
def __str__(i):
# s = '',join(itts.repeat(i._x._elem, i._x._count))
s = ''.join(str(x) for x in i._x)
return s
class Spacer(StringPreservationistBase):
def __init__(i, count: int):
i._innerself = StringPreservationist(" ", count)
def __len__(i):
return len(i._innerself)
def __iter__(i):
return iter(i._innerself)
def __str__(i):
return str(i._innerself)
# end class
def indent_print(parent, indent=Spacer(0)):
assert(not isinstance(parent, type("")))
# "a"[0][0][0][0][0][0] == "a"[0]
try:
for child in parent:
nxt_indent = type(indent)(4 + len(indent))
indent_print(child, nxt_indent)
except: # container not iterable
print(indent, parent)
# def get_indent_iter(parent, indent=Spacer(0)):
# try:
# for child in parent:
# it = indent_print(child, type(indent)(4 + len(indent)))
# yield something
# except: # container not iterable
# yield indent
# yield parent
def rasterize_dot_verify_args(callable, parent):
if not hasattr(callable, "__call__"):
raise ValueError()
import inspect
siggy = inspect.signature(callable)
if (len(siggy.parameters) > 1):
raise ValueError()
def rasterize(callable, xparent, make_copy:bool = False):
rasterize_dot_verify_args(callable, xparent)
iparent = xparent
if make_copy:
import copy
iparent = copy.deepcopy(xparent)
if hasattr(iparent, "__iter__"):
iter_kids = iter(iparent)
if iter_kids != iparent:
# ----------------------------------
# why
# iter_kids != parent
# ?!???
# ----------------------------------
# because a single character string
# returns an iterator to iti.
#
# "a"[0][0][0][0][0][0][0][0] = a[0]
# iter(iter(iter(iter("a")))) == iter("a")
#
lamby = lambda p, *, c=callable: rasterize(c, p)
out_kids = map(lamby, iter_kids)
r = callable(out_kids)
else: # iter_kids == iparent
r = callable(iter_kids)
else: # `parent` is not iterable
r = iparent
return r
# iterator to non-iterables
# [1, 2, 3, 4]
input0 = "iter([1, 2, 3, 4])"
# iterator to iterators of non-iterables
import itertools as itts
input1A = "map(lambda x: itts.repeat(x, 6), range(1, 5))"
input1B = "iter([range(0, 2), range(1, 3), range(2, 4)])"
# input1A = [
# [1, 1, 1, 1, 1, 1]
# [2, 2, 2, 2, 2, 2]
# ...
# [2, 2, 2, 2, 2, 2]
# ]
# input1B = [
# [0, 1]
# [1, 2]
# [2, 3]
# ]
inputs = [input0, input1A, input1B]
import copy
for input in inputs:
print(256 * "#")
print(input)
print(list)
iterator = eval(input)
raster = rasterize(list, input)
indent_print(raster)
print(256*"#")
You can try the following function:
def is_iter(i):
if isinstance(i, str):
return len(i) != 1
return hasattr(i, '__iter__')
def unroll(func, iterator):
def _unroll(it): # recursive helper method
if not is_iter(it):
yield it
for i in it:
if not is_iter(i):
yield i
else:
yield func(_unroll(i)) # apply function to iterator and recurse
return func(_unroll(iterator)) # apply function to end result
>>> inputs = [(0,3), '345']
>>> print(unroll(list, inputs))
[[0, 3], ['3', '4', '5']]
>>> inputs = [range(0, 2), range(2, 4), range(4, 6)]
>>> print(unroll(tuple, inputs))
((0, 1), (2, 3), (4, 5))
>>> print(unroll(list, inputs))
[[0, 1], [2, 3], [4, 5]]
>>> inputs = [[range(0, 2), range(2, 4)], range(4, 6)]
>>> print(unroll(tuple, inputs))
(((0, 1), (2, 3)), (4, 5))
>>> print(unroll(list, inputs))
[[[0, 1], [2, 3]], [4, 5]]

Python decorating property setter with list [duplicate]

This question already has answers here:
python: how to have a property and with a setter function that detects all changes that happen to the value
(3 answers)
Closed 6 years ago.
globalList = []
class MyList:
def __init__(self):
self._myList = [1, 2, 3]
#property
def myList(self):
return self._myList + globalList
#myList.setter
def myList(self, val):
self._myList = val
mL1 = MyList()
print("myList: ", mL1.myList)
mL1.myList.append(4)
print("after appending a 4, myList: ", mL1.myList)
mL1.myList.extend([5,6,"eight","IX"])
print("after extend, myList: ", mL1.myList)
Result:
myList: [1, 2, 3]
after appending a 4, myList: [1, 2, 3]
after extend, myList: [1, 2, 3]
The problem I am facing is that mL1.myList.append(4) and mL1.myList.extend([5,6,"eight","IX"]) do not modify the _myList attribute in the mL1 object. How could I do to resolve the problem?
I define a method append() and a method extend() for the class object. It respectively appends to member myList and extends member myList.
global globalList
globalList = []
class MyList():
def __init__(self):
self._myList = [1, 2, 3]
#property
def myList(self):
return self._myList + globalList
#myList.setter
def myList(self, val):
self._myList = val
def append(self, val):
self.myList = self.myList + [val]
return self.myList
def extend(self, val):
return self.myList.extend(val)
mL1 = MyList()
print("myList: ", mL1.myList)
mL1.append(4)
print("after appending a 4, myList: ", mL1.myList)
mL1.myList.extend([5,6,"eight","IX"])
print("after extend, myList: ", mL1.myList)
result is
>>>
('myList: ', [1, 2, 3])
('after appending a 4, myList: ', [1, 2, 3, 4])
('after extend, myList: ', [1, 2, 3, 4, 5, 6, 'eight', 'IX'])
I would subclass list and override a few methods:
import itertools
class ExtendedList(list):
def __init__(self, other=None):
self.other = other or []
def __len__(self):
return list.__len__(self) + len(self.other)
def __iter__(self):
return itertools.chain(list.__iter__(self), iter(self.other))
def __getitem__(self, index):
l = list.__len__(self)
if index > l:
return self.other[index - l]
else:
return list.__getitem__(self, index)
It should work with just about everything:
In [9]: x = ExtendedList([1, 2, 3])
In [10]: x
Out[10]: [1, 2, 3]
In [11]: x.append(9)
In [12]: x
Out[12]: [9, 1, 2, 3]
In [13]: x.extend([19, 20])
In [14]: x
Out[14]: [9, 19, 20, 1, 2, 3]
In [15]: sum(x)
Out[15]: 54

Categories