I am stuck on applying rules in a class like forcing some values to change if some rule is present and so on. However I am unable to pass rules to the class. Here is my code, and what i require:
class Item:
valid_item_dict = {"a":20, "b":30, "c":40, "d":50}
def __init__(self, item_id):
self.item_id = item_id
self.item_cost = Item.valid_item_dict.get(self.item_id)
class checks:
def __init__(self):
self.content = list()
def cheque(self, item):
self.content.append(item)
def totals(self):
self.total = sum([self.item_counter().get(itm)*Item.valid_item_dict.get(itm) for\
itm in list(self.item_counter().keys())])
return self.total
def item_counter(self):
self.item_count_list = [itms.item_id for itms in self.content]
self.item_count_dict = dict((item, self.item_count_list.count(item)) for item in
self.item_count_list)
return self.item_count_dict
# Adding items to the list
item1 = Item("a")
item2 = Item("a")
item3 = Item("a")
item4 = Item("b")
# instatiance of class
cx = checks()
cx.cheque(item1)
cx.cheque(item2)
cx.cheque(item3)
cx.cheque(item4)
cx.totals()
>>> 90 (20*3 (from a) + 1*30 (from b))
In normal cases this works fine but I have a ton of rules which I need to add and I was earlier thinking of adding if-else rules in totals method of "checks" class. But is their a more generalized way to add these rules. Rule is something like if we have 3 types of product a, then the value of 'a' reduces from 20 to 10.
I did go over this question and am trying to use this, but any help would be wonderful. (Python how to to make set of rules for each class in a game)
You may want to use a more direct loop to implement these rules and make your code clearer. I find it easier to maintain complex logic than trying to code golf a 1-line result:
from collections import Counter, namedtuple
Rule = namedtuple("Rule", ["threshold", "newvalue"])
"""rule: if count is greater than or equal to threshold, replace with newvalue"""
class Item:
rules = {'a': Rule(3, 10)}
...
class checks:
...
def totals(self):
counts = Counter(self.content)
self.total = 0
for count in counts:
value = Item.valid_item_dict[count]
rule = Item.rules.get(count, Rule(0, value))
if counts[count] >= rule.threshold:
value = rule.newvalue
self.total += value*counts[count]
return self.total
I'm assuming you want the result of your sample to be 60 and not 90.
Related
I have a class Foo with its instances having a "balance" attribute. I'm designing it in such a way that Foo can track all the balances of its active instances. By active I mean instances that are currently assigned to a declared variable, of part of a List that is a declared variable.
a = Foo(50) # Track this
b = [ Foo(20) for _ in range(5) ] # Track this
Foo(20) # Not assigned to any variable. Do not track this.
Another feature of Foo is that is has an overloaded "add" operator, where you can add two Foo's balances together or add to a Foo's balance by adding it with an int or float.
Example:
x = Foo(200)
x = x + 50
y = x + Foo(30)
Here is my code so far:
from typing import List
class Foo:
foo_active_instances: List = []
def __init__(self, balance: float = 0):
Foo.foo_active_instances.append(self)
self.local_balance: float = balance
#property
def balance(self):
"""
The balance of only this instance.
"""
return self.local_balance
def __add__(self, addend):
"""
Overloading the add operator
so we can add Foo instances together.
We can also add more to a Foo's balance
by just passing a float/int
"""
if isinstance(addend, Foo):
return Foo(self.local_balance + addend.local_balance)
elif isinstance(addend, float | int):
return Foo(self.local_balance + addend)
#classmethod
#property
def global_balance(cls):
"""
Sum up balance of all active Foo instances.
"""
return sum([instance.balance for instance in Foo.foo_active_instances])
But my code has several issues. One problem is when I try to add a balance to an already existing instance, like:
x = Foo(200)
x = x + 50 # Problem: This instantiates another Foo with 200 balance.
y = Foo(100)
# Expected result is 350, because 250 + 100 = 350.
# Result is 550
# even though we just added 50 to x.
print(Foo.global_balance)
Another problem is replacing a Foo instance with None doesn't remove it from Foo.foo_active_instances.
k = Foo(125)
k = None
# Expected global balance is 0,
# but the balance of the now non-existing Foo still persists
# So result is 125.
print(Foo.global_balance)
I tried to make an internal method that loops through foo_active_instances and counts how many references an instance has. The method then pops the instance from foo_active_instance if it doesn't have enough. This is very inefficient because it's a loop and it's called each time a Foo instance is made and when the add operator is used.
How do I rethink my approach? Is there a design pattern just for this problem? I'm all out of ideas.
The weakref module is perfect for this design pattern. Instead of making foo_active_instances a list, you can make it a weakref.WeakSet. This way, when a Foo object's reference count falls to zero (e.g., because it wasn't bound to a variable), it will be automatically removed from the set.
class Foo:
foo_active_instances = weakref.WeakSet()
def __init__(self, balance: float = 0) -> None:
Foo.foo_active_instances.add(self)
...
In order to add Foo objects to a set, you'll have to make them hashable. Maybe something like
class Foo:
...
def __hash__(self) -> int:
return hash(self.local_balance)
You can use inspect to check if the __init__ or __add__ methods have been called as part of an assignment statement. Additionally, you can keep a default parameter in __init__ to prevent increasing your global sum by the value passed to it when creating a new Foo object from __add__:
import inspect, re
def from_assignment(frame):
return re.findall('[^\=]\=[^\=]', inspect.getframeinfo(frame).code_context[0])
class Foo:
global_balance = 0
def __init__(self, balance, block=False):
if not block and from_assignment(inspect.currentframe().f_back):
Foo.global_balance += balance
self.local_balance = balance
def __add__(self, obj):
if from_assignment(inspect.currentframe().f_back) and not hasattr(obj, 'local_balance'):
Foo.global_balance += obj
return Foo(getattr(obj, 'local_balance', obj), True)
a = Foo(50)
b = [Foo(20) for _ in range(5)]
Foo(20)
print(Foo.global_balance) #150
x = Foo(200)
x = x + 50
y = Foo(100)
print(Foo.global_balance) #350
it does not work. I want to split data as in code in lines attribute.
class movie_analyzer:
def __init__(self,s):
for c in punctuation:
import re
moviefile = open(s, encoding = "latin-1")
movielist = []
movies = moviefile.readlines()
def lines(movies):
for movie in movies:
if len(movie.strip().split("::")) == 4:
a = movie.strip().split("::")
movielist.append(a)
return(movielist)
movie = movie_analyzer("movies-modified.dat")
movie.lines
It returns that:
You can use #property decorator to be able to access the result of the method as a property. See this very simple example of how this decorator might be used:
import random
class Randomizer:
def __init__(self, lower, upper):
self.lower = lower
self.upper = upper
#property
def rand_num(self):
return random.randint(self.lower, self.upper)
Then, you can access it like so:
>>> randomizer = Randomizer(0, 10)
>>> randomizer.rand_num
5
>>> randomizer.rand_num
7
>>> randomizer.rand_num
3
Obviously, this is a useless example; however, you can take this logic and apply it to your situation.
Also, one more thing: you are not passing self to lines. You pass movies, which is unneeded because you can just access it using self.movies. However, if you want to access those variables using self you have to set (in your __init__ method):
self.movielist = []
self.movies = moviefile.readlines()
To call a function you use movie.lines() along with the argument. What you are doing is just accessing the method declaration. Also, make sure you use self as argument in method definitions and save the parameters you want your Object to have. And it is usually a good practice to keep your imports at the head of the file.
import re
class movie_analyzer:
def __init__(self,s):
for c in punctuation:
moviefile = open(s, encoding = "latin-1")
self.movielist = []
self.movies = moviefile.readlines()
#property
def lines(self):
for movie in self.movies:
if len(movie.strip().split("::")) == 4:
a = movie.strip().split("::")
self.movielist.append(a)
return self.movielist
movie = movie_analyzer("movies-modified.dat")
movie.lines()
I need help with updating my __init__ attributes, I need to dynamically update them. So farm my __init__ looks like this:
class Thief(Hero):
def __init__(self, chance_to_steal=0.3, chance_to_crit=0.3):
super().__init__(hero_name="Thief", stamina=100)
self.chance_to_steal = chance_to_steal
self.chance_to_crit = chance_to_crit
self.armor = 0
self.backpack = Backpack()
I would like to update this values as the program goes forward
The function i am trying to use looks like this:
def add_item(self, item_name, item_type, modifier_amount):
self.backpack.add_new_item(item_name, item_type, modifier_amount)
if item_name in ["Armor", "Weapon"]:
value, statistic = modifier_amount.split(" ")
statistic_dict = {"Armor": self.armor, "Weapon": self.attack_dmg}
plus_minus = {"+": True, "-": False}
operator = value[0]
if plus_minus[operator]:
statistic_dict[item_name] += int(value[1:])
if not plus_minus[operator]:
statistic_dict[item_name] -= int(value[1:])
is there any way that i can modify self attributes while using dict like that?
statistic_dict = {"Armor": self.armor, "Weapon": self.attack_dmg}
At the moment dict values are the values of that attributes but i would like to modify them without having to hard code it with lots of if's
Thanks for help
I am not sure if I got exactly what you want, but maybe you are just missing the getattr and setattr callables.
They allow you to change an instance's attribute given the attribute name as a string. As in setattr(self, "armor", statistic_dict["Armor"]), or, as will be useful here: setattr(self, item_name, statistic_dict["Armor"]) (the attribute name is now used from the variable item_name and don't need to be hardcoded)
(Both callables are part of Python builtins and you don't need to import them from anywhere, just use them - also, besides getting and setting, you can use hasattr and delattr)
Besides that you have some issues with the way you stract your operator and value contents and use then - if one passes -5 in a string, say inside a amount variable, just doing my_value += int(amount) works - no need to manually check the "operator" as both "+" and "-" are valid prefixes when converting integer numbers in strings to ints.
Your main problem seems to be lack of comfort with Boolean expressions:
plus_minus = {"+": True, "-": False}
operator = value[0]
if plus_minus[operator]:
statistic_dict[item_name] += int(value[1:])
if not plus_minus[operator]:
statistic_dict[item_name] -= int(value[1:])
Instead:
if value[0] == '+':
statistic_dict[item_name] += int(value[1])
else:
statistic_dict[item_name] -= int(value[1])
Or, using a ternary operator (conditional expression):
bonus = int(value[1])
statistic_dict[item_name] += bonus if value[0] == '+' else -bonus
This updates the stat by either +bonus or -bonus, depending on the operator.
Note that this modifies only the values in your dict; it does not modify the instance attributes (not class attributes). If you want to modify those, instead, I recommend that you move the applicable attributes to a dict as part of your __init__ function, and then operate on that self.capabilities dict as the target of your add_item method.
Here's something that should get you a bit further.
Item is its own class and can have several modifiers, in a dictionary. If a modifier's key matches an existing attribute on the Hero, then it gets added to the Hero's.
As you go further, you may want to have you Modifier values be a class in its own right. A Vorpal may have an absolute 70% chance to crit, regardless of who is using it. Or a ring of thieving may multiply your base chance_to_steal by 1.3 (i.e .30 * 1.3 = .39 now).
A separate statistics dict could allow you to separate out the current values from the hero's underlying attributes so that you could take away items.
Never mind what I did to your poor Hero and Backpack classes, they don't matter for this problem, so I basically commented them out.
# class Thief(Hero):
class Thief:
def __repr__(self):
return f"{self.__class__.__name__}.chance_to_steal={self.chance_to_steal}, chance_to_crit={self.chance_to_crit}"
def __init__(self, chance_to_steal=0.3, chance_to_crit=0.3):
# super().__init__(hero_name="Thief", stamina=100)
self.chance_to_steal = chance_to_steal
self.chance_to_crit = chance_to_crit
self.armor = 0
self.backpack = {}
def add_item(self, item):
self.backpack[item.name] = item
for attrname,mod in item.modifiers.items():
#do we have something to modify, on this Hero?
val = getattr(self,attrname, None)
if val is None:
continue
else:
#sum the previous value and the modifier
setattr(self, attrname, val + mod)
class Item:
def __init__(self, name, modifiers=None):
self.name = name
if modifiers:
self.modifiers = modifiers.copy()
thief = Thief(chance_to_crit=0.4)
print(thief)
item = Item("vorpal", dict(chance_to_crit=0.2))
thief.add_item(item)
print(thief)
item = Item("cursed ring", dict(chance_to_crit=-0.1, chance_to_steal=-0.2, cast_spell=-0.2))
thief.add_item(item)
print(thief)
output
Thief.chance_to_steal=0.3, chance_to_crit=0.4
Thief.chance_to_steal=0.3, chance_to_crit=0.6000000000000001
Thief.chance_to_steal=0.09999999999999998, chance_to_crit=0.5000000000000001
So i've been trying to code a section in my cash-register class. Im trying to make it so that it keeps track of the total price as an integer. so for example 18.66 would be 1866. But i still want it to be considered as 18.66 if that makes sense. this is so that it avoids the accumulation of roundoff errors. But i dont want to change the public interface of the class.
#!/usr/local/bin/python3
class CashRegister :
def __init__(self):
self._itemCount = 0
self._totalPrice = 0.0
def addItem(self, price):
self._itemCount = self._itemCount + 1
self._totalPrice = self._totalPrice + price
def getTotal(self):
return self._totalPrice
def getCount(self) :
return self._itemCount
def clear(self) :
self._itemCount = 0
self._totalPrice = 0.0
wouldn't i simply change the 0.0's to 0's? or would i go along the way of doing a split method and concatenate them back together?
You should keep the data as is.
You can achieve what you want by mean of formatting. If i were you i will add a new function like this:
def getTotalAsString(self):
return "%d" % (self._totalPrice * 100)
This approach will give you more flexibility in the future
I am fairly new to python. I have tried to define a class, I then want to create an instance from a file, then refer to specific pieces of it, but cannot seem to. This is Python 3.3.0
Here's the class....
class Teams():
def __init__(self, ID = None, Team = None, R = None, W = None, L = None):
self._items = [ [] for i in range(5) ]
self.Count = 0
def addTeam(self, ID, Team, R=None, W = 0, L = 0):
self._items[0].append(ID)
self._items[1].append(Team)
self._items[2].append(R)
self._items[3].append(W)
self._items[4].append(L)
self.Count += 1
def addTeamsFromFile(self, filename):
inputFile = open(filename, 'r')
for line in inputFile:
words = line.split(',')
self.addTeam(words[0], words[1], words[2], words[3], words[4])
def __len__(self):
return self.Count
Here's the code in Main
startFileName = 'file_test.txt'
filename = startFileName
###########
myTestData = Teams()
myTestData.addTeamsFromFile(startFileName)
sample data in file
100,AAAA,106,5,0
200,BBBB,88,3,2
300,CCCC,45,1,4
400,DDDD,67,3,2
500,EEEE,90,4,1
I think I am good to here (not 100% sure), but now how do I reference this data to see... am i not creating the class correctly? How do I see if one instance is larger than another...
ie, myTestData[2][2] > myTestData[3][2] <----- this is where I get confused, as this doesn't work
Why don't you create a Team class like this :
class Team():
def __init__(self, ID, Team, R=None, W = 0, L = 0)
# set up fields here
Then in Teams
class Teams():
def __init__(self):
self._teams = []
def addTeam (self, ID, Team, R=None, W = 0, L = 0)
team = Team (ID, Team, R=None, W = 0, L = 0)
self._teams.append (team)
Now If i got it right you want to overwrite the > operator's behaviour.
To do that overload __gt__(self, other) [link]
So it will be
class Team ():
# init code from above for Team
def __gt__ (self, otherTeam):
return self.ID > otherTeam.ID # for example
Also be sure to convert those strings to numbers because you compare strings not numbers. Use int function for that.
The immediate problem you're running into is that your code to access the team data doesn't account for your myTestData value being an object rather than a list. You can fix it by doing:
myTestData._items[2][2] > myTestData._items[3][2]
Though, if you plan on doing that much, I'd suggest renaming _items to something that's obviously supposed to be public. You might also want to make the addTeamsFromFile method convert some of the values it reads to integers (rather than leaving them as strings) before passing them to the addTeam method.
An alternative would be to make your Teams class support direct member access. You can do that by creating a method named __getitem__ (and __setitem__ if you want to be able to assign values directly). Something like:
def __getitem__(self, index):
return self._items[index]
#Aleksandar's answer about making a class for the team data items is also a good one. In fact, it might be more useful to have a class for the individual teams than it is to have a class containing several. You could replace the Teams class with a list of Team instances. It depends on what you're going to be doing with it I guess.