How to add multiple initialised strings into and append() method - python

So I'm working on a simple code that works like a bag, this code currently can read and add singular initialised strings into an array that has been also initialised under all classes. Here's the code:
class Items:
# This class initializes the items name
def __init__(self, name):
self.name = name
class Bag:
# This class adds the initialised items and reads anything in the bag
def __init__(self, items, max_items):
# max_items is the maximum capacity of the Bag
self.items = items
self.max_items = max_items
def add_items(self):
if len(self.items) < self.max_items:
self.items.append(i.name) # <- Here is the problem at '(i.name)'
def read_items(self):
for item in self.items:
print(item)
i = Items("Item1")
i2 = Items("Item2")
b = Bag([], 10) # I'm just going to put example 10 strings for the max_items
b.add_items()
b.read_items()
The output of this code is "Item1".
I wanted the 'append()' method in 'add_items()' function to also add 'i2' initialisation and other initialisation that will be added in the future with any name that the user will put in automatically.

I think what you need is a list of items passed to your add_items function:
def add_items(self, items: list):
if (len(self.items) + len(items)) < self.max_items:
for i in items:
self.items.append(i.name)
You then later call it with
b.add_items([i, i2])

If you want to add multiple items at the same time without iterations, you can use extend() rather than append(), but you have to redefine add_items() accordingly:
def add_items(self, *args): # args will collect any number of items you pass in a tuple
if len(self.items) + len(args) <= self.max_items:
self.items.extend([i.name for i in args])
i = Items("Item1")
i2 = Items("Item2")
b = Bag([], 10)
b.add_items(i, i2)
b.read_items()
# output:
Item1
Item2
This would be a Pythonic way to do the job without unexpected behaviors.

Related

How do I count the items on a "grocery list" using a class?

I am trying to make a class that includes the ability to display the length of a list. However, I get the message 'List has 0 items' instead of the actual number.
Here is my code:
'''
grocery_list = []
class GroceryList:
grocery_list = []
def __init__(self):
print('Grocery List Created')
def add(self, item):
self.grocery_list.append(item)
def show(self):
print(self.grocery_list)
def size(self):
print('List has {} items'.format(len(grocery_list)))
gro_list = GroceryList()
gro_list.add('Banana')
gro_list.add('Cat Food')
gro_list.show()
gro_list.size()
'''
Any help is appreciated.
In your question the indentation was messed up, however the problem was in method size(self) with grocery_list used instead of self.grocery_list.
Everything else works OK.
class GroceryList:
# declare grocery_list
grocery_list = []
def __init__(self):
print('Grocery List Created')
def add(self, item):
self.grocery_list.append(item)
def show(self):
print(self.grocery_list)
def size(self):
# self.grocery_list was the error
print('List has {} items'.format(len(self.grocery_list)))
if __name__ == "__main__":
gro_list = GroceryList()
gro_list.add('Banana')
gro_list.add('Cat Food')
gro_list.show()
gro_list.size()
You have two grocery_list in your code. One is a global one and the other is a class variable.
By len(grocery_list) You are finding the length of the grocery_list that is declared before the Class (global). Since you're not adding to that list (see your add() function), it shows you 0 items.
It should be len(self.grocery_list) - means find the length of grocery_list (class variable). See the self before the variable name.
class GroceryList:
grocery_list = []
def __init__(self):
print('Grocery List Created')
def add(self, item):
self.grocery_list.append(item)
def show(self):
print(self.grocery_list)
def size(self):
print('List has {} items'.format(len(self.grocery_list)))
gro_list = GroceryList()
gro_list.add('Banana')
gro_list.add('Cat Food')
gro_list.show()
gro_list.size()
Grocery List Created
['Banana', 'Cat Food']
List has 2 items
The reason you get zero in your code is that you are appending to the list (grocery_list) which is an attribute of the class. But in the size() function you are printing the length of the global grocery_list in which no elements were appended (i.e it is an empty list) so you get length as '0'
Try changing your size() function to this -
def size(self):
print('List has {} items'.format(len(self.grocery_list)))
Also, you can remove the grocery_list variable at the global level (first line), because it is useless in this code.

How to simulate ```WHERE``` from SQL or ```grep``` in Python to iterate and change data with certain attributes?

I'm looking for a quick and simple tool to simulate WHERE from SQL. In a collection (list, set, dictionary, etc) of objects (each of which should be a list or class instance), I'd like to pick out just those with some certain properties/attributes and process them.
Example 1:
stuff = {
"item1":[1,2,3],
"item2":[4,5,6,7,8,9],
"item3":[2,4,5]
}
def myfunc(this): # this is the dict above
result = list()
for item in this:
if len(item) == 3:
result.append(item)
return result
In example 1, myfunc simulates return items in stuff WHERE len(item)==3 which looks for and returns objects with length 3 in the dictionary stuff.
Example 2:
class thing:
def init(self, a, b, c):
self.a = a
self.b = b
self.c = c
item1 = thing(1,2,3)
item2 = thing(2,3,4)
item3 = thing(3,5,7)
test_list = [item1, item2, item3]
def func(this): # this is the test_list
result = list()
for item in this:
if item.a % 2:
result.append(item)
return result
In example 2, func simulates return items in test_list WHERE (item.a % 2) == 1 which returns objects where object.a is odd.
In both examples, a collection of objects is scanned for certain attributes that meet certain conditions and return those objects to be processed.
You could check in your loop if item is int, like
isinstance(item, int)
You can use __dict__ of an instance to access all the instance variables.
If you need to apply certain conditions like being int or something else you can use instainceof or similar.
class particle(object):
def __init__(self):
self.height = 100
particles = [particle() for _ in range(2)]
for item in particles:
item.height -= 1
for item in particles:
for key in item.__dict__.keys():
item.__dict__[key] = 5
item.__dict__[key] = 5 => Here I access the instance variables and change the value. If you need to add condition you can add here.
Hacky code ahead (better solution below)
You can implement __iter__ in a away that it will return all the attributes of the instance, then you will be able to pretty much achieve what you are looking for:
class particle:
def __init__(self):
self.x = 90
self.y = 70
def __iter__(self):
yield from self.__dict__
particles = [particle() for _ in range(2)]
for particle in particles:
for attribute in particle:
current_val = getattr(particle, attribute)
if isinstance(current_val, int):
setattr(particle, attribute, current_val - 1)
for particle in particles:
print(particle.x, particle.y)
Outputs
89 69
89 69
The better solution
However I do not think this is the correct approach. A better way may be to implement the attribute modification inside the class itself.
class particle:
def __init__(self):
self.x = 90
self.y = 70
def change_all_attributes(self, attr_type, change_by):
for attr_name, attr_value in self.__dict__.items():
if isinstance(attr_value, attr_type):
setattr(self, attr_name, attr_value + change_by)
particles = [particle() for _ in range(2)]
for particle in particles:
particle.change_all_attributes(int, -1)
print(particle.x, particle.y)
Outputs
89 69
89 69
What you get:
More idiomatic code. The change in the attribute is done by the class, not by arbitrary nested loop somewhere in your code (easier to debug).
A builtin way to control the type of the modified attributes and by how much they change
This can be taken a step further by using properties' getters and setters.

How to decrypted [<__main__.Food object at 0x1097ba828>

I am a python newbie. I want display actual names,values and calories instead of [<__main__.Food object at 0x1097ba828>, <__main__.Food object at 0x1097ba860>, <__main__.Food object at 0x1097ba898>] I know this question is very simple,but it would be a great help if you could let me know the answer!
class Food(object):
def __init__(self,n,v,w):
self.name = n
self.value = v
self.calories = w
def getValue(self):
return self.value
def getCal(self):
return self.calories
def density(self):
return self.getValue()/self.getCal()
def __str__(self):
return '<__main__.Food: '+self.name +' '+ self.value+' ' + self.calories
def buildMenu(self):
menu = []
for i in range(len(values)):
menu.append(Food(self.name[i], self.value[i], self.calories[i]))
return menu
names=['burger','fries','coke']
values=[1,2,3]
calories=[100,200,300]
if __name__ == '__main__':
new = Food(names, values, calories)
print(new.buildMenu())
Thank you!
I made two code changes to get what I think you're looking for. The first is to convert values to strings in your str function. The second is to use that.
def __str__(self):
return '<__main__.Food: '+ str(self.name) +' '+ str(self.value)+' ' + str(self.calories)
and
print (str(new)) #instead of print(new.buildMenu())
Now the output is:
<main.Food: ['burger', 'fries', 'coke'] [1, 2, 3] [100, 200, 300]
This is how I would do it, noting that we've created two classes: a separate Food and Menu class. The Menu class has an add method that appends to its foodItems property, though I don't feel like that's really necessary since we can just do direct property assignment:
m.foodItems = < some list of Food objects >
I've removed the confusing buildMenu method from the Food class, and defined __str__ methods for both classes:
class Food(object):
def __init__(self,n,v,w):
self.name = n
self.value = v
self.calories = w
def getValue(self):
return self.value
def getCal(self):
return self.calories
def density(self):
return self.getValue()/self.getCal()
def __str__(self):
return '\t'.join([self.name, str(self.value), str(self.calories)])
class Menu(object):
def __init__(self):
self.foodItems = []
def add(self, foodItem):
self.foodItems.append(foodItem)
def __str__(self):
"""
prints the food items
"""
s = 'Item\tValue\tCalories\n'
s += '\n'.join(str(f) for f in self.foodItems)
return s
names=['burger','fries','coke']
values=[1,2,3]
calories=[100,200,300]
m = Menu()
items = list(Food(n,v,c) for n,v,c in zip(names,values,calories))
m.foodItems = items
print(m)
And outputs like:
The issue you have is that you're printing a list of Food instances, not a single instance at a time. The list type's __str__ operator calls repr on the items the list contains, not str, so your __str__ method does not get run.
A simple fix is to just rename your __str__ method to __repr__.
I'd note that it's a bit strange that you're building a Food instance with lists of values for name, value and calories, just so that you can call a method on it to make a list of Food instances with the individual values. A more Pythoic approach would be to pass the lists to a classmethod that returns the list of instances, without the intermediate instance needing to exist:
#classmethod
def buildMenu(cls, names, values, calories):
menu = []
for i in range(len(values)): # consider using zip instead of looping over indexes
menu.append(cls(names[i], values[i], calories[i]))
return menu
You'd call it on the class:
if __name__ == '__main__':
print(Food.buildMenu(names, values, calories))

Add elements into a sorted array in ascending order

I have the following program which implements a sorted bag. It adds elements
successfully in sorted order (ascending order) when giving a list.
When I created a new sorted bag with the argument of another bag, it's not in sorted order (instead in descending order). See below
Thanks for the help
# Array Class
#----------------------------------------------------
class Array(object): # Represents an array.
DEFAULT_CAPACITY = 5
def __init__ (self, capacity, fillValue = None):
'''Capacity = static size of array. fillValue is placed at each element'''
self._items = list()
self._capacity = capacity
self._logicalSize = 0
self._fillValue = fillValue
for count in range(capacity):
self._items.append(fillValue)
def __getitem__(self, index): return self._items[index]
def __setitem__(self, index, newItem):
self._items[index] = newItem
# ArraySortedBag Class
#----------------------------------------------------
class ArraySortedBag(object):
'''An array-based bag implementation'''
def __init__(self, sourceCollection = None):
'''Sets the initial state of self, which includes the contents
of sourceCollection, if it's present'''
self._items = Array(10)
self._size = 0
if sourceCollection:
for item in sourceCollection:
self.add(item)
def __len__(self): return self._size
def __iter__(self):
cursor = 0
while cursor < len(self):
yield self._items[cursor]
cursor += 1
def add(self, item):
'''Adds item to self.'''
insertIndex = 0
# First found the index where the item will be inserted at
for i in range(self._size):
if self._items[i] > item:
insertIndex = i
break
# Then, shift items down by one position until the insertIndex,
for i in range (self._size, insertIndex, -1):
self._items[i] = self._items[i-1]
# Last, assign value to _items[insertIndex]
self._items[insertIndex] = item
self._size += 1
# Test Driver
#----------------------------------------------------
if __name__ == "__main__":
b1 = ArraySortedBag([2000, 2, 1000])
print ("Display bag b1")
for i in b1: # <------ Correct order, ascending order
print (i)
b2 = ArraySortedBag(b1)
print ("\nDisplay bag b2")
for i in b2: # <----- Wrong order, descending order
print (i)
In the second instantiation of class ArraySortedBag, you are passing a sorted list. The ArraySortedBag.init() method adds the items using the add() method. When add() is called the item to be added is never less than the existing list. Therefore insertIndex remains equal to zero. Therefore the new item is added to the beginning of the list.
# First found the index where the item will be inserted at
for i in range(self._size):
if self._items[i] > item: # item is never less than self._items[i]
insertIndex = i
break
For adding items into a sorted list so that one maintains the list sorted, I recommend using the bisect library's insort_left or insort_right.
import bisect
list = [10, 20, 30]
bisect.insort(list, 25)
bisect.insort(list, 15)
print list

AttributeError for recursive method in python

I have a recursive method problem with python, the code is this:
class NodeTree(object):
def __init__(self, name, children):
self.name = name
self.children = children
def count(self):
# 1 + i children's nodes
count = 1
for c in self.children:
count += c.count()
return count
def create_tree(d):
N = NodeTree(d['name'], d['children'])
print N.count()
d1 = {'name':'musica', 'children':[{'name':'rock', 'children':[{'name':'origini','children':[]},
{'name':'rock&roll','children':[]},
{'name':'hard rock', 'children':[]}]},
{'name':'jazz', 'children':[{'name':'origini', 'children':[{'name':'1900', 'children':[]}]},
{'name':'ragtime', 'children':[]}, {'name':'swing', 'children':[]}]}]}
tree = create_tree(d1)
The error is this:
count += c.count()
AttributeError: 'dict' object has no attribute 'count'
I tried anything but it doesn't work.
Anyway, any suggestions?
Thanks!
That's because Python dictionaries do not have a count method.
It'll help if we go over line by line what your code is actually doing.
def count(self):
# 1 + i children's nodes
count = 1
for c in self.children: ## self.children is a list of dictionaries, so each c is a dictionary
count += c.count() ## We are getting .count() of c--which is a dictionary
return count
This is because we passed d1['children'] as self.children, which is a list of dictionaries: [<dict>, <dict>, <dict>, ... ].
Rather than count(), what you should do is call len on the dictionary, to get the number of keys it has, thus becoming:
for c in self.children:
count += len(c)
d['children'] is a list of dict, as you can see in d1 dict.
Now, when you iterate over your children, in NodeTree, which is essentially d['children'] only, you will get dictionary as each element: -
for c in self.children: // c is a `dict` type here
count += c.count() // there is not attribute as `count` for a `dict`.
And hence you got that error.
Well, for once, the create_tree function does not build the tree recursively. So you just add a Node on zero level and the the children are just dictionaries.
The following (modified) code (although quickly typed and sloppy) should do a recursive
build of the tree. Didn't check your count code, but assuming it is correct, it should work.
class NodeTree(object):
def __init__(self, name, children):
self.name = name
self.children = children
def count(self):
# 1 + i children's nodes
count = 1
for c in self.children:
count += c.count()
return count
def deep_create_tree(d):
if len(d["children"]) > 0:
chlds = []
for i in d["children"]:
chlds.append(deep_create_tree(i))
else:
chlds = []
n = NodeTree(d["name"], chlds)
return n
d1 = {'name':'musica', 'children':[{'name':'rock', 'children':[{'name':'origini','children':[]},{'name':'rock&roll','children':[]},{'name':'hard rock', 'children':[]}]},{'name':'jazz', 'children':[{'name':'origini', 'children':[{'name':'1900', 'children':[]}]},{'name':'ragtime', 'children':[]}, {'name':'swing', 'children':[]}]}]}
def scan_tree(tr):
print tr.name, tr.count()
for i in tr.children:
scan_tree(i)
tr = deep_create_tree(d1)
scan_tree(tr)
The best (and the only desirable) way is to have a recursive creation of your node tree. This can be done in two different ways, either make your NodeTree.__init__() recursively init all of the children (which again recursively init all of their children, etc) or you can make your create_tree() function recursive.
I'd personally use recursive __init__(), but it's your call.
Recursive __init__() for creating tree structure:
def __init__(self, name, children=[]):
self.name = name
self.children = []
for c in children:
self.children.append(
self.__class__(c['name'], c['children']))
This way self.children will contain other NodeTrees instead of dicts. Also you no longer need to declare empty children list, instead of:
d2 = {'name':'sterile', 'children':[]}
do
d2 = {'name':'sterile'}
And the initializer will automatically set children to []
If you want to use a recursive create_tree() function, it's also possible, and not a bad idea either. However you will still have to edit the __init__() -method, it no longer takes children as parameter. Or as I did here, it does, but you hardly ever use it.
# NodeTree
def __init__(self, name, children=None):
self.name = name
if children:
self.children = children
def create_tree(d):
N = NodeTree(d['name'])
for c in d['children']:
N.children.append(create_tree(c))
return N
This will have basically the same results.

Categories

Resources