Class method uses an array not passed - python

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)

Related

Python classes and types

I think I'm misusing the concept of subclass. I'm working on a hobby project with Grids and Cells.
What I have, is the implementation of a Cell class, and its subclass HexCell which basically redefines many of the attributes/methods like so:
class Cell:
def __init__(self, row_loc, col_loc):
self.row = row_loc
self.col = col_loc
self.links = set()
self.neighbors = 4*[None]
def __repr__(self):
return f'Cell #({self.row},{self.col})'
def link(self, other, bidir = True):
self.links.add(other)
if bidir: other.links.add(self)
Then I have a subclass that is the HexGrid which follows a similar structure with new parameters.
class HexCell(Cell):
def __init__(self, r_out, th_around):
# I'm indexing Hex cells around a center cell
# instead of by rows and columns; Prefixed hex
# as they follow the hexagon, and not regular polar coordinates.
self.hex_r = r_out
self.hex_th = th_around
self.neighbors = 6*[None]
self.links = set()
def __repr__(self):
return f"HexCell #[{self.hex_r}, {self.hex_th}]"
def bind(self, other, to_dir):
to_dir = to_dir % 6
if (self.neighbors[to_dir] is None):
self.neighbors[to_dir] = other
other.neighbors[to_dir - 3] = self
# Hexagonal grids share neighbors.
other_1 = other.neighbors[to_dir - 2]
if (self.neighbors[to_dir - 1] is None) & (other_1 is not None):
self.bind(other_1, to_dir - 1)
other_5 = other.neighbors[to_dir - 4]
if (self.neighbors[to_dir - 5] is None) & (other_5 is not None):
self.bind(other_5, to_dir - 5)
In this case, the method self.link(other) is shared, but other attributes change from rectangular grid to hexagonal like the locaion from (row, col) to (hex_r, hex_th), or neighbors as a 4-list or 6-list. Thus I'd like these attributes to be dependent on a another cell-type attribute and transferred down to the subclass.
Correct use of subclassing needs to obey the following substitution principle:
If there are some objects x_1 of type T_1 and x_2 of type T_2 such that issubclass(T_2, T_1) == True, then any property that applies to x_1 must also apply for x_2.
In other words, you expect subclassing to implement new behaviours, not to change existing behaviours.
In you example, the change of coordinate system itself is a change of behaviour and thus HexCell should not inherit from Cell.
What you can do is create a base class BaseCell that encapsulates the common behaviour between Cell and HexCell and inherit from it.
class BaseCell:
def __init__(self):
self.links = set()
self.neighbors = []
def add_neighbor(self, other):
self.neighbors.append(other)
def link(self, other, bidirectional=True):
self.links.add(other)
if bidirectional:
other.link(self, bidirectional=False)
class Cell(BaseCell):
def __init__(self, row_loc, col_loc):
self.row = row_loc
self.col = col_loc
super().__init__()
def __repr__(self):
return f'Cell #({self.row},{self.col})'
class HexCell(Cell):
def __init__(self, r_out, th_around):
self.hex_r = r_out
self.hex_th = th_around
super().__init__()
def __repr__(self):
return f"HexCell #[{self.hex_r}, {self.hex_th}]"
def bind(self, other, to_dir):
...
Your Cell class is in fact not an abstract "Cell", but a square cell in two-dimensional space (has exactly 4 neighbours, has "row" and "col" position). Such cell may not be subclassed by a hex cell, because hex cell is just a different type of cell : )
As you noticed, the only common things are link() method and links attribute. If you insist on subclassing, you could create something like:
class LinkedObject():
def __init__(self):
self.links = set()
def link(self, other, bidir = True):
self.links.add(other)
if bidir: other.links.add(self)
class SquareCell(LinkedObject):
# "Cell" class here
class HexCell(LinkedObject):
# HexCell here

Aligning Python Class & Super-Class

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

Python - Classes- Definition

I programmed a class aswell as a definition with the backthought the when you,
set everytime a value using class variable you can always recall the total amount by using the definition
class Hand():
def __init__(self, Hand=0):
self.Hand = Hand
def getHand(self, neue_Hand):
self.Hand = neue_Hand
def set_hand(self):
return self.Hand
c = Hand()
def Aufruf():
Total = 0
Total += c.getHand(0)
return Total
c.getHand(12)
Aufruf()
It changes the value each time, but doesn't accumulate it as it supposed to be.
You've mixed up the functionality in your getters and setters. The getter should return the variable and the setter should set the value.
class Hand():
def __init__(self,Hand=0):
self.Hand = Hand
def getHand(self):
return self.Hand
def set_hand(self, neue_Hand):
self.Hand = neue_Hand
def increment_hand(self, neue_Hand_incremenet):
self.Hand += neue_Hand_incremenet
c = Hand(10)
c.getHand()
>> 10
c.set_hand(20)
c.getHand()
>> 20
def Aufruf():
Total = 0
Total += c.getHand()
return Total
Aufruf()
>> 20
c.increment_hand(10)
Aufruf()
>> 30
Also as a side note:
If you look closely, you will realise your method Aufruf is actually an exact duplicate (logically) of the getHand() method. When you instantiate the variable total = 0 inside the method block of code, this value will ALWAYS be set to 0 when the method is called, meaning the value from c.getHand() will ALWAYS just be the value that's returned
Use method addition for changing value, and don't use capital letters or camelCase inside your class.
class Hand:
def __init__(self, 0):
self.hand = hand
def get_hand_bigger(self, addition):
self.hand += addition
Your class method getHand does not return any value, yet it is being called as such. Try this:
def getHand(self, neue_Hand):
self.Hand = neue_Hand
return self.Hand

Classes, objects and lists in python

so I'm trying to create a program but I still have difficulties with classes. The program (which isn't finished of course) will print a random number of Villages. Each village(second Class) will have a random number of Clans(the first Class). Anyway my problem is the Village class. How do I make sure to add the areas and family size into the Village class? How do I insert the counter from the Clan class to the Village class? As you can see when I've randomized a number of Clans the areas and family sizes should add up into the Village class. What should I do? What is wrong with my Village class?
class Clan:
counter = 0
def __init__(self):
r = random.randrange(20, 101)
self.area = r
s = random.randrange(1, 6)
self.familySize = s
Clan.counter += 1
self.counter = Clan.counter
def getArea(self):
return self.area
def getFamilySize(self):
return self.familySize
class Village:
counter = 0
def __init__(self):
self.clan_list = []
for i in range(random.randrange(3, 7)):
self.clan_list += [Clan()]
def getclan_list(self):
return self.clan_list
def getArea(self):
return self.area
def getPopulation(self):
pass
So, you want the village class to calculate how many families are in the village?
families = 0
for clan in self.clan_list
families += clan.getFamilySize()
Since the values of area and population are dependent on the clans in clan_list you should compute these values each time they are needed. The alternative is much more complicated -- having to control how clans are added and removed from the village and how the area and family size of a clan can be changed and having those changes reflected in the village.
Below is an example of how you might compute both village area and population. The first using a getter method, and the second using a more python-esque property.
import random
class Clan:
counter = 0
def __init__(self):
r = random.randrange(20, 101)
self.area = r
s = random.randrange(1, 6)
self.familySize = s
Clan.counter += 1
self.counter = Clan.counter
# removed getters
# Unless your getter or setter is doing something special,
# just access the attribute directly.
class Village:
def __init__(self):
self.clan_list = []
for i in range(random.randrange(3, 7)):
self.clan_list.append(Clan())
# for a village, area is a computed value, so use a getter
def getArea(self):
total_area = 0
for clan in self.clan_list:
total_area += clan.area
return total_area
# the prefered alternative to getters (and setters) are properties
# note that the function is named as if it was an attribute rather than function
#property
def population(self):
# use sum and generator rather than a for loop
return sum(clan.familySize for clan in self.clan_list)
# How to use a village instance
v = Village()
# get area
print("area:", v.getArea())
# get population
print("population:", v.population)
# note how population is accessed as if it was an attribute rather than called
# like a function

Initializing instance variable: Idiomatic way

In Python what is the idiomatic way for initializing Python's instance variable:
class Test:
def __init__(self, a, b, c, d):
self.a = a
self.b = b
self.c = c
self.d = d
Or
class Test2:
def __init__(self, data):
self.a = data[0]
self.b = data[1]
self.c = data[2]
self.d = data[3]
UPDATE: I have around 20 instance variables for a class named Link:
self.street
self.anode
self.bnode
self.length
self.setbackA
self.setbackB
self.bearingA
self.bearingB
self.ltype
self.lanesAB
self.leftAB
self.rightAB
self.speedAB
self.fspdAB
self.capacityAB
self.lanesBA
self.leftBA
self.rightBA
self.speedBA
self.fspdBA
self.capacityBA
self.use
Each variable is related to the class Link. Is there a recommended way of refactoring this?
The former, since it's more explicit about what the parameters are and what the object requires.
If you do need to pass in your data as a tuple, there's a shortcut you can use. Instead of doing the latter, or something like:
test = Test(data[0], data[1], data[2], data[3])
You can instead unpack the list/tuple and do:
test = Test(*data)
If you need to pass in a bunch of data (more then 4-5), you should look into either using optional/keyword arguments, creating a custom object to hold some of the data, or using a dictionary:
config = Config(a, b, c, d)
test = Test(e, f, config, foo=13, bar=True)
I would probably refactor your Link class to look like this:
class Node(object):
def __init__(self, node, setback, bearing):
self.node = node
self.setback = setback
self.bearing = bearing
class Connection(object):
def __init__(self, lanes, left, right, speed, fspd, capacity):
self.lanes = lanes
self.left = left
self.right = right
self.speed = speed
self.fspd = fspd
self.capacity = capacity
class Link(object):
def __init__(self, street, length, ltype, use, a, b, ab, ba):
self.street = street
self.length = length
self.ltype = ltype
self.use = use
self.a = a
self.b = b
self.ab = ab
self.ba = ba
I saw that you had some duplicate data, so pulled those off into a separate object. While this doesn't reduce on the number of fields you have, overall, it does make the parameters you need to pass in smaller.
Having a large number of fields isn't bad, but having a large number of parameters generally is. If you can write your methods in a way that they don't need a huge amount of parameters by bundling together data, then it doesn't really matter how many fields you have.
Unpacking an array into a bunch of named variables suggests you should have started with named variables in the first place - stick with the first one.
There is only one reason you might want the second one here - you have something that is inconsiderately producing lists rather than objects. If that does happen:
data = get_data_in_list_form()
actual_data = Test(*data)
Can you group some of your data:
self.street
self.ltype
self.use
self.length
# .a and .b can be instances of NodeConnection
self.a.setback
self.a.bearing
self.b.setback
self.b.bearing
self.b.node
# .ab and .ba can be a separate class, "UniDirectionalLink
self.ab.lanes
self.ab.left
self.ab.right
self.ab.speed
self.ab.fspd
self.ab.capacity
self.ba.lanes
self.ba.left
self.ba.right
self.ba.speed
self.ba.fspd
self.ba.capacity
There's no need to do everything in a constructor here:
link = (
Link(street=..., ltype=..., use=..., length=...)
.starting_at(node_a, bearing=..., setback=...)
.finishing_at(node_b, bearing=..., setback=...)
.forward_route(lanes, left, right, speed, fspd, capacity)
.reverse_route(lanes, left, right, speed, fspd, capacity)
)

Categories

Resources