Aligning Python Class & Super-Class - python

Consider the following Python code snippet where we define a Portfolio, Company and Deposit class. A Portfolio object simply acts as a union of companies & deposits. And we can run metrics on the portfolio like Profit. Questions:
For every new metric I include in the Company or Deposit class I need to manually add a corresponding function in the Portfolio class; despite the fact that their behaviour is always the same: sum across all investments. Is there a way to improve this logic/construction of classes? What if we need to add 100 other metrics...
The Deposit class only has a Profit function, but not Loss (interest in a bank account is assumed to be guaranteed). Is there a way to treat "undefined" metrics as always returning 0? Or is there a cleaner/more correct to define these metrics? What if we need to cover 100 different investment types that may or may not have different metrics...
class Company():
def __init__(self, ItemsSold, ItemPrice, Expenses, Fines):
self.ItemsSold = ItemsSold
self.ItemPrice = ItemPrice
self.Expenses = Expenses
self.Fines = Fines
def Profit(self):
return self.ItemsSold * self.ItemPrice
def Loss(self):
return self.Expenses + self.Fines
def ProfitAndLoss(self):
return self.Profit() - self.Loss()
class Portfolio():
def __init__(self, Investments):
self.Investments = Investments
def Profit(self):
return sum([inv.Profit() for inv in self.Investments])
def Loss(self):
return sum([inv.Loss() for inv in self.Investments])
def ProfitAndLoss(self):
return sum([inv.ProfitAndLoss() for inv in self.Investments])
class Deposit():
def __init__(self, Notional, InterestRate, TimeHorizon):
self.Notional = Notional
self.InterestRate = InterestRate
self.TimeHorizon = TimeHorizon
def Profit(self):
return self.Notional * self.InterestRate * self.TimeHorizon
myComp1 = Company(100,2,50,20)
myComp2 = Company(200,2,100,80)
myDepos = Deposit(100,0.02,3)
myPortf = Portfolio([myComp1,myComp2,myDepos])
print(myPortf.Profit()) # Works fine
print(myPortf.ProfitAndLoss()) # Throws an error

The second question is easy: all you have to do is to create a Base class where each metrics is defined as a method returning 0. Then derive all your Invest classes (Company, Deposit, etc) from the Base class, so as all undefined metrics will call the corresponding method in the Base class.
The first question is a bit tougher as it requires some meta-programming. Your Portfolio class can also be derived from the Base class, then it looks in the method dictionary of the Base class (Base.__dict__) to retrieve all metrics names. Afterwards, for all these metrics, it creates a specific lambda method that calls this metrics for each item in your Investments list and sums up the results. Here is a skeleton code for this:
class Base(object):
def f1(self):
return 0
def f2(self):
return 0
class InvestA(Base):
def f2(self):
return 2
class InvestB(Base):
def f1(self):
return 1
class Portfolio(Base):
def __init__(self, invest):
self.invest = invest
for name in [n for n in Base.__dict__ if n[:2] != '__']:
self.__dict__[name] = lambda name=name: self.sum(name)
def sum(self, name):
return sum([i.__class__.__dict__[name](i) for i in self.invest
if name in i.__class__.__dict__])
A = InvestA()
print("A.f1 = %s, A.f2 = %s" % (A.f1(), A.f2()))
B = InvestB()
print("B.f1 = %s, B.f2 = %s" % (B.f1(), B.f2()))
P = Portfolio([A,A,B])
print('P.f1 = A.f1 + A.f1 + B.f1 =', P.f1())
print('P.f2 = A.f2 + A.f2 + B.f2 =', P.f2())
which produces the following output:
A.f1 = 0, A.f2 = 2
B.f1 = 1, B.f2 = 0
P.f1 = A.f1 + A.f1 + B.f1 = 1
P.f2 = A.f2 + A.f2 + B.f2 = 4
As you can see, A.f1, B.f2, P.f1 and P.f2 are not explicitely defined as methods, but they can be called thanks to inheritance and meta-programming

Related

Why do we pass arguments into a method instead of assigning them as attributes?

I am attempting to learn OOP in Python so I wanted to ask why do we pass parameters into the method when calling it, while the object is passed automatically by Python into the method as the first parameter and can be used to automatically identify and call its attributes instead of passing them when calling the method?
Why don't we do this:
class Item:
def calculate_total(self):
total = self.quantity * self.price
return total
item = Item()
item.price = 100
item.quantity = 5
item.calculate_total()
instead of this:
class Item:
def calculate_total(self, x, y):
total = x * y
return total
item = Item()
item.price = 100
item.quantity = 5
item.calculate_total(item.price, item.quantity)
The question is kinda flawed, as none of those should be seen in real-life code.
Attributes should be declared inside the class, preferrably at the moment of object initialisation.
class Item:
def __init__(self, price, quantity):
self.price = price
self.quantity = quantity
def calculate_total(self):
total = self.quantity * self.price
return total
item = Item(100, 5)
item.calculate_total()
This way we don't have a risk of self.price and self.quantity not being defined when we call calculate_total.
But, even with what you provided, why 2nd method would be worse quickly becomes apparent if you try to calculate things multiple times. Let's say you've got 3 slightly different totals (different currency maybe).
Would you rather write
item.calculate_total1(item.price, item.quantity)
item.calculate_total2(item.price, item.quantity)
item.calculate_total3(item.price, item.quantity)
or
item.calculate_total1()
item.calculate_total2()
item.calculate_total2()
?
As #Pranav Hosangadi mentions, if there are any parameters that do not have a place in the attributes of a class (e.g. discount, which can vary for different sales of the same item), that is where we would pass them to the method:
class Item:
def __init__(self, price, quantity):
self.price = price
self.quantity = quantity
def calculate_total(self, discount):
total = self.quantity * self.price * (1 - discount)
return total
item = Item(100, 5)
discount = 0.15
print(item.calculate_total(discount))

is there any way to get the object from its property?

I want to list the objects by its attribute, and get it with only the attributes that is in the list.
class foo:
def __init__(self,id):
self.id=id
a=foo(0)
b=foo(1)
ids=[a.id,b.id]
can I refer to a with only having ids ?
and if it is not possible this way, how can I ?
User a dictionary:
class foo:
def __init__(self,id):
self.id=id
a=foo(0)
b=foo(1)
ids={a.id:a, b.id:b}
print(ids[0])
An example without a dictionary
NOTE: This may be better achieved using a Meta-programming in Python, and your question may seem that can have an actual real world usage when creating Python Packages, Frameworks etc.
Still, in a clumsy way it does achieve this.
import random
class foo:
def __init__(self,id):
self.id=id
def create_counter():
count = 0
def internal():
nonlocal count
count += 1
return count
return internal
counter = create_counter()
def create_id():
"""Generate random id, uses a stateles Closure to keep track of counter"""
id_ = None
name = 'class_'
id_gen = str(hex(random.randrange(1000)))
id_ = name + str(counter()) + "_" + id_gen[2:]
return id_
def change_name_ref(inst_obj):
"""Change Instance Name to Instance ID"""
inst_obj.__name__ = inst_obj.id
a = foo(create_id()) # --> Assign a radnom Id
b = foo(create_id())
c = foo('class_1_15b')
change_name_ref(a)
change_name_ref(b)
change_name_ref(c)
ids = [a, b, c]
def get_instance(inst_list, target):
for idx, id_ in enumerate(inst_list):
if id_.__name__ == target:
inst = inst_list[idx]
print(f'Here The Class instance {inst}, ID: {inst.id}')
value = get_instance(ids, 'class_1_15b')
# Here The Class instance <__main__.foo object at 0x7f6988f016d0>, ID: class_1_15b

Product Inventory program that takes products with an ID, quantity, and price and uses an Inventory class to keep track of the products

The Product class seems to work fine but I'm trying to figure out how to get the Inventory class to separate each product into there specific categories. I feel like I'm close but whenever I try and print out the inventory it just shows where it's stored in memory and doesn't actually print anything out. The output i receive when running is at the bottom. I want it to print out the actual products and data, not the instance of it stored in memory.
class Product:
def __init__(self, pid, price, quantity):
self.pid = pid
self.price = price
self.quantity = quantity
def __str__(self):
#Return the strinf representing the product
return "Product ID: {}\t Price: {}\t Quantity: {}\n".format(self.pid, self.price, self.quantity)
def get_id(self):
#returns id
return self.pid
def get_price(self):
#returns price
return self.price
def get_quantity(self):
#returns quantity
return self.quantity
def increase_quantity(self):
self.quantity += 1
def decrease_quantity(self):
self.quantity -= 1
def get_value(self):
value = self.quantity * self.price
return 'value is {}'.format(value)
product_1 = Product('fishing', 20, 10)
product_2 = Product('apparel', 35, 20)
class Inventory:
def __init__(self, products):
self.products = products
self.fishing_list = []
self.apparel_list = []
self.value = 0
def __repr__(self):
return "Inventory(products: {}, fishing_list: {}, apparel_list: {}, value: {})".format(self.products, self.fishing_list, self.apparel_list, self.value)
def add_fishing(self):
for product in self.products:
if product.get_id() == 'fishing':
self.fishing_list.append(product)
return '{} is in the fishing section'.format(self.fishing_list)
def add_apparel(self):
for product in self.products:
if product.get_id() == 'apparel':
self.apparel_list.append(product)
return '{} is in the apparel section'.format(self.apparel_list)
inventory_1 = Inventory([product_1, product_2])
inventory_1.add_fishing()
print(inventory_1)
OUTPUT = Inventory(products: [<main.Product instance at 0x10dbc8248>, <main.Product instance at 0x10dbc8290>], fishing_list: [<main.Product instance at 0x10dbc8248>], apparel_list: [], value: 0)
You need to specify how an object of the class Inventory should be printed.
To do this you need to implement at least one of the following functions in your class.
__repr__
__str__
This answer helps, which of both you should use: https://stackoverflow.com/a/2626364/8411228
An implementation could look something like this:
class Inventory:
# your code ...
def __repr__(self):
return str(self.products) + str(self.fishing_list) + str(self.apparel_list) + str(self.value)
# or even better with formatting
def __repr__(self):
return f"Inventory(products: {self.products}, fishing_list: {self.fishing_list}, apparel_list: {self.apparel_list}, value: {self.value})
Note that I used in the second example f strings, to format the output string.

How to get rid of eval in subclass appending to superclass instance list?

I am using eval to run a generated string to append the newly created EggOrder instance to the list of the correct instance of the DailyOrders class. The day provided by EggOrder is used to used to append to the correct instance. This relies on eval and the variable name of the DailyOrders instance and so it would be great to get this removed. I know there must be a better way.
class DailyOrders:
PRICE_PER_DOZEN = 6.5
def __init__(self, day):
self.orders = []
self.day = day
def total_eggs(self):
total_eggs = 0
for order in self.orders:
total_eggs += order.eggs
return total_eggs
def show_report(self):
if self.total_eggs() < 0:
print("No Orders")
else:
print(f"Summary:\nTotal Eggs Ordered: {self.total_eggs()}")
print(f"Average Eggs Per Customer: {self.total_eggs() / len(self.orders):.0f}\n*********")
class EggOrder():
def __init__(self, eggs=0, name="", day=""):
if not name:
self.new_order()
else:
self.name = name
self.eggs = eggs
self.day = day
eval(f"{self.day.lower()}.orders.append(self)")
def new_order(self):
self.name = string_checker("Name: ")
self.eggs = num_checker("Number of Eggs: ")
self.day = string_checker("Date: ")
def get_dozens(self):
if self.eggs % 12 != 0:
dozens = int(math.ceil(self.eggs / 12))
else:
dozens = self.eggs / 12
return dozens
def show_order(self):
print(f"{self.name} ordered {self.eggs} eggs. The price is ${self.get_dozens() * DailyOrders.PRICE_PER_DOZEN}.")
if __name__ == "__main__":
friday = DailyOrders("Friday")
friday_order = EggOrder(12, "Someone", "Friday")
friday_order.show_order()
friday.show_report()
saturday = DailyOrders("Saturday")
saturday_order = EggOrder(19, "Something", "Saturday")
saturday_order = EggOrder(27, "Alex Stiles", "Saturday")
saturday.show_report()
DailyOrders isn't actually a superclass (it was in a earlier version), it acts like one and I suspect the answer might have some inheritance.

Class method uses an array not passed

So, I'm tring to program a variant of the knapsack problem but I'm really new to the use of Python and I stumbled on this.
I'm using Jupyter (Python 3)
class Gene(object):
def __init__(self, weight, price):
self.weight = weight
self.price = price
obj1 = Gene(10, 20)
obj2 = Gene(25, 5)
obj3 = Gene(5, 10)
genes = [obj1, obj2, obj3]
class Chromosomes(object):
def __init__(self, flagIndex_of_items_contained = []):
self.flagIndex_of_items_contained = flagIndex_of_items_contained
self.myWeight = self.Define_myWeight()
def Define_myWeight(self):
weight = 0
for index_flag in range(len(self.flagIndex_of_items_contained)):
if(self.flagIndex_of_items_contained[index_flag] == 1):
weight = weight + genes[index_flag].weight
return weight
chromosome1 = Chromosomes([1,0,1])
print("chromosome1 weight: ", chromosome1.myWeight)
Output
chromosome1 weight: 15
BUT
genes[index_flag].weight
How can this command work if i don't pass the array genes to the class?
The problem is that your variable genes lives on the same level as the classes, the module level. In the problematic line
weight = weight + genes[index_flag].weight
the interpreter simply sees that there is no local variable with the scope of the function Define_myWeight, hence it checks the global scope (module level). On this level genes exists and the interpreter can use it.
Besides that, issues because of which your code has been considered "badly written".
Use global variables only if they are absolutely necessary. Check some arbitrary tutorial on globals to understand this proposition.
You should never use a mutable object as default parameter. Lists are mutable objects in Python, that means they can be changed. Use immutable objects, like tuples, in such cases.
def func1(some_arg = []): # bad
def func1(some_arg = ()): # ok
Do not mix different format styles. Use either CamelCase or names_with_underscores. Check out the Python Style Guide for that.
Here is an idea to improve your code. Chromosomes are made up of different genes. The following code models this relation.
class Gene:
def __init__(self, weight, price):
self.weight = weight
self.price = price
class Chromosom:
def __init__(self):
self.genes = []
self.flag_idx = []
self.weight = 0
def add_gene(self, weight, price):
self.genes.append(Gene(weight, price))
def compute_weight(self, flags):
for i, flag in enumerate(flags):
if flag == 1:
self.weight += self.genes[i].weight
Usage:
ch = Chromosom()
ch.add_gene(10, 20)
ch.add_gene(25, 5)
ch.add_gene(5, 10)
ch.compute_weight((1, 0, 1))
print(ch.weight)

Categories

Resources