So here's one example. I have a module with multiple classes for computing properties, let's say, Density. Then, I have substances, whom I make inherit the properties, so I can call them afterwards:
class Density():
def __init__(self):
self.rho_method_1_A = None
self.rho_method_1_B = None
self.rho_method_2_A = None
self.rho_method_2_B = None
def density_method_1(self,T):
return self.rho_method_1_A*T + self.rho_method_1_B*T**2
def density_method_2(self,T,P):
return P*(self.rho_method_2_A/T + self.rho_method_1_B*log(T))
class Water(Density):
def __init__(self):
self.rho_method_1_A = 0.2
self.rho_method_1_B = 0.0088
self.rho_method_2_A = 1.9
self.rho_method_2_B = 10
Water.density_method_1(T=300)
Basically, I want the user to be able to set beforehand the method of choice. The problem is, depending on the model that he/she chose, it will either accept only T, or both T and P (in some other cases, maybe T won't even be accepted). Essentially:
density_method = density_method_2 # This is chosen by the user in an outer module
Water.density_method(code magically knows what to put here by detecting the arguments that
density_method_2 accepts)
To be clear: The user itself will know which arguments the method accepts, so if he chose method_2, P will be known on the outer module. I suspect **kwargs and/or decorators are part of the solution, but I can't quite figure it out. Any help is appreciated. Thanks and have a great weekend.
Update: It is relevant to notice that density_method will be called inside a temporal loop of tens of thousands of iterations. So, I'm trying to avoid if/else statements by completely defining density_method before starting the temporal loop.
Possible solution (tested with Python 3.8):
from math import log
class Density():
def __init__(self, method=1):
self.method = method
self.rho_method_1_A = None
self.rho_method_1_B = None
self.rho_method_2_A = None
self.rho_method_2_B = None
def density_method_1(self, T):
return self.rho_method_1_A*T + self.rho_method_1_B*T**2
def density_method_2(self, T, P):
return P*(self.rho_method_2_A/T + self.rho_method_1_B*log(T))
def density_method(self, *args, **kwargs):
if self.method == 1:
return self.density_method_1(*args, **kwargs)
elif self.method == 2:
return self.density_method_2(*args, **kwargs)
else:
raise ValueError("No density method found")
class Water(Density):
def __init__(self, method):
super(Water, self).__init__(method=method)
self.rho_method_1_A = 0.2
self.rho_method_1_B = 0.0088
self.rho_method_2_A = 1.9
self.rho_method_2_B = 10
class Air(Density):
def __init__(self, method):
super(Air, self).__init__(method=method)
self.rho_method_1_A = 0.2
self.rho_method_1_B = 0.0088
self.rho_method_2_A = 1.9
self.rho_method_2_B = 10
water = Water(method=1)
air = Air(method=2)
print(water.density_method(T=1))
print(air.density_method(T=1, P=2))
The solution can be improved but the general idea is to have a "wrapper" method density_method that calls the relevant density_method_* for each sub-class of Density.
Edit (following the comments to my answer):
You should look into python method overloading, like the example here:
https://www.geeksforgeeks.org/python-method-overloading/
from multipledispatch import dispatch
#passing one parameter
#dispatch(int,int)
def product(first,second):
result = first*second
print(result);
#passing two parameters
#dispatch(int,int,int)
def product(first,second,third):
result = first * second * third
print(result);
#you can also pass data type of any value as per requirement
#dispatch(float,float,float)
def product(first,second,third):
result = first * second * third
print(result);
#calling product method with 2 arguments
product(2,3,2) #this will give output of 12
product(2.2,3.4,2.3) # this will give output of 17.985999999999997
Related
I have this two classes:
class Extraction:
def extract(self):
df_final = pd.DataFrame()
for index in range(0, jsonPartidas.shape[0]):
jsonPartidasIndex = json.loads(list(jsonPartidas['data'])[index])
df_ratios = self.func(jsonPartidasIndex)
cif, denominacion = cif_denominacion(jsonPartidasIndex)
df_ratios['cif'] = cif
df_ratios['denominacion'] = denominacion
df_final = df_final.append(df_ratios)
ratios = df_final.drop_duplicates()
class Ratios(Extraction):
# func = Ratios.find_ratio
def __init__ (self, partidasRatios):
self.codigos = list(partidasRatios.values())
self.nombreColumnas = list(partidasRatios.keys())
self.func = Ratios.find_ratio
def find_ratio(self,jsonPartidasIndex):
self.listaRatios = list(findkeys(jsonPartidasIndex,'listaRatios'))
self.annosBalance = list(findkeys(jsonPartidasIndex,'annoBalance'))
df_ratios = self.parseRatios()
return df_ratios
def parseRatios(self):
self.dic_codes = {}
self.dic_codes['anno'] = self.annosBalance
self.getRatios()
df_ratios = pd.DataFrame(self.dic_codes)
return df_ratios
def getRatios(self):
for num, self.cod in enumerate(self.codigos):
self.ratios = []
self.findRatios()
self.dic_codes[self.nombreColumnas[num]] = self.ratios
def findRatios(self):
for ind in range(0,len(self.listaRatios)):
ratio = self.listaRatios[ind]
self.getValueFromRatio(ratio, ind)
def getValueFromRatio(self,ratio,ind):
for index in range(0,len(ratio)):
dictio = list(findkeys(ratio[index],'valor'))
if self.cod in dictio:
valor = [ x for x in dictio if isinstance(x, float) or isinstance(x,int)]
if valor:
self.ratios.append(valor[0])
else:
self.ratios.append(None)
break
elif index == len(ratio)-1 and len(self.ratios) < ind+1:
self.ratios.append(None)
I have a few classes with the method extract() that is why I create a class call Extraction that is being inherted by the other classes one of them is Ratio. I want to assign the a method that is created inside of my Ratio class call find_ratio, to the self.func so when I execute Ratios(partidasRatios).extract() (as you can see I do not want to pass any parameters to the extract method), it will run the code. I tried a lot of things and I think i got closer with the example above but I still get this error:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
C:\Users\ALVARO~1\AppData\Local\Temp/ipykernel_11044/3560029455.py in <module>
----> 1 ratios = Ratios(partidasRatios).extract()
C:\Users\ALVARO~1\AppData\Local\Temp/ipykernel_11044/1705348355.py in extract(self)
6 for index in range(0, jsonPartidas.shape[0]):
7 jsonPartidasIndex = json.loads(list(jsonPartidas['data'])[index])
----> 8 df_ratios = self.func(jsonPartidasIndex)
9 cif, denominacion = cif_denominacion(jsonPartidasIndex)
10 df_ratios['cif'] = cif
TypeError: find_ratio() missing 1 required positional argument: 'jsonPartidasIndex'
If you zoom in you can see that jsonPartidasIndex it is actually pass.
Your find_ratio is not a class method, but a "regular" instance method.
So your assignment in __init__ should look like this imho:
self.func = self.find_ratio
No offense, but you could improve the overall design though.
You can make the whole base class abstract or at least raise an error in base class and provide implementation in the class which inherits from Extraction.
class Extraction:
def extract(self):
func_arg = ... # get the argument any way you want
self.func(func_arg)
def func(self, *args, **kwargs):
raise NotImplementedError("Overload this with specific implementation!")
class Ratios(Extraction):
def func(self, func_argument):
... # your specific implementation goes here
ratios = Ratios()
ratios.extract()
Some off-topic tips:
python likes snake case, be consistent with it
use English in your code (if you ever have to share it to non-Spanish (that's my guess what it is, sorry if it's Portuguese) speakers it will be easier to understand and all external libraries use English too, so you end up with mix of your language and English, which doesn't look good and is harder to analyze
use type hints, they really help
don't use abbreviations
Long story short, I need to write a data analysing tool using mainly OOP principles. I'm not quite a beginner at python but still not the best. I wrote a function which returned true or false value based on what the user inputted (below):
def secondary_selection():
"""This prints the options the user has to manipulate the data"""
print("---------------------------")
print("Column Statistics [C]")
print("Graph Plotting [G]")
d = input(str("Please select how you want the data to be processed:")).lower()
# Returns as a true/false boolean as it's easier
if d == "c":
return True
elif d == "g":
return False
else:
print("Please enter a valid input")
This function works the way I want it to, but I then tried to import it into different file for use with a class (below):
class Newcastle:
def __init__(self, data, key_typed):
self.data = data[0]
self.key_typed = key_typed
def newcastle_selection(self):
# If function returns True
if self:
column_manipulation()
# If function returns False
if not self:
graph_plotting()
The newcastle_selection(self) function takes the secondary_selection() function as a argument, but the only way I got it to work was the if self statement. As writing something like if true lead to both the column_manipulation and the graph_plotting functions being printed.
I am wondering if there's any better way to write this as I am not quite a beginner at python but still relatively new to it.
Disclaimer: This is for a first year course, and I asked this as a last result.
I'm not sure I've understood really well your code structure, but it looks like a little factory could helps you:
def column_manipulation():
print("Column manipulation")
def graph_plotting():
print("Graph plotting")
class Newcastle:
def __init__(self, data, func):
self.data = data[0]
self._func = func
def newcastle_selection(self):
return self._func()
#classmethod
def factorize(cls, data, key_typed):
if key_typed is True:
return cls(data, column_manipulation)
elif key_typed is False:
return cls(data, graph_plotting)
else:
raise TypeError('Newcastle.factorize expects bool, {} given'.format(
type(key_typed).__name__
))
nc = Newcastle.factorize(["foo"], True)
nc.newcastle_selection()
nc = Newcastle.factorize(["foo"], False)
nc.newcastle_selection()
Output
Column manipulation
Graph plotting
The main idea is to define your class in a generic way, so you store a function given as __init__ parameter within self._func and just call it in newcastle_selection.
Then, you create a classmethod that takes your data & key_typed. This method is in charge of choosing which function (column_manipulation or graph_plotting) will be used for the current instance.
So you don't store useless values like key_typed in your class and don't have to handle specific cases everywhere, only in factorize.
Cleaner and powerful I think (and btw it answer your question "Is this pythonic", this is).
This is simple and basic example of secondary_selection method using class. This would help probably.
class castle:
def __init__(self):
self.data = ''
def get_input(self):
print("1: Column Statistics")
print("2: Graph Plotting")
self.data = input("Please select how you want the data to be processed: ")
def process(self):
if self.data == 1:
return self.column_manipulation()
else:
return self.graph_plotting()
def column_manipulation(self):
return True
def graph_plotting(self):
return False
c = castle()
c.get_input()
result = c.process()
print(result)
class LogicGate(object):
def __init__(self, n):
self.label = n
self.output = None # ????????????
def getOutput(self):
self.output = self.performGateLogic()
return self.output
def getLabel(self):
return self.label
class BinaryGate(LogicGate):
def __init__(self, n): # ?????????????????
LogicGate.__init__(self, n)
self.pinA = None # ??????????????
self.pinB = None # ??????????????
def getPinA(self):
return int(raw_input('Enter Pin A input for gate' + self.getLabel() + '-->'))
def getPinB(self):
return int(raw_input('Enter Pin A input for gate' + self.getLabel() + '-->'))
class UnaryGate(LogicGate):
def __init__(self, n): # ??????????????
LogicGate.__init__(self, n)
self.pin = None # ?????????????
def getPin(self):
return int(raw_input('Enter Pin input for gate' + self.getLabel() + '-->'))
class AndGate(BinaryGate):
def __init__(self, n): # ????????????
BinaryGate.__init__(self, n)
def performGateLogic(self):
a = self.getPinA()
b = self.getPinB()
if a == 1 and b == 1:
return 1
else:
return 0
This code belongs to Problem Solving with Algorithms and Date Structures.
When I remove the lines before the comment '# ????????', the code can run normally.
Why does the author write the code like this?
Whether is it a good code style?
Can I always remove these lines before the comment '# ????????' ?
The author writes the code like that because it is good practice to never have uninitialised members and class parents, static checkers moan if you do.
The reason that it is not good practice is for future maintainability - let us say that the base class, LogicGate, was to gain a new property - say propagation_delay and a new method that allowed simulations to called get_response_time which relied on the current output state and the required, possibly new, state. If all the code that was derived from that class did the correct initialisations then it would all work fine, without any changes. If you remove those lines and such a new method was introduced you would have to go back through all of the child classes adding them back in before your final class would work for that method, with the chance that you would miss one.
Daft as it sounds doing things properly now is actually future laziness - it only takes you seconds when you are creating a class to make sure everything is initialised - debugging an uninitialised class can take hours.
First:
The __init__ functions are the constructors of the classes, you can read about them here.
Second:
Your code will run without those lines but the question is why and is it ok to remove them?
For example if you remove the following init
class UnaryGate(LogicGate): # LogicGate is the superclass
def __init__(self, n):
LogicGate.__init__(self, n)
The constructor of the super-class LogicGate will be called directly.
Third:
Ok, so can we remove the self.xxx = None?
class BinaryGate(LogicGate):
def __init__(self, n):
LogicGate.__init__(self, n)
self.pinA = None
self.pinB = None
We could remove those 2 Lines too but consider this code
bg = BinaryGate("binaryGate1")
print bg.pinA
This would throw an error because pinA is undefined.
If you do not remove the self.pinA = None in __init__ the code will run and None will be printed.
I'm currently self-learning Python, and I'm reading 'Problem Solving with Algorithms and Data Structures' (Brad Miller, David Ranum). I've stumbled upon the basic example of inheritance. Although I can see what it does, I need an explanation, how it works actually. The Code is as follows:
class LogicGate:
def __init__(self,n):
self.name = n
self.output = None
def getName(self):
return self.name
def getOutput(self):
self.output = self.performGateLogic()
return self.output
class BinaryGate(LogicGate):
def __init__(self,n):
LogicGate.__init__(self,n)
self.pinA = None
self.pinB = None
def getPinA(self):
if self.pinA == None:
return int(input("Enter Pin A input for gate "+self.getName()+"-->"))
else:
return self.pinA.getFrom().getOutput()
def getPinB(self):
if self.pinB == None:
return int(input("Enter Pin B input for gate "+self.getName()+"-->"))
else:
return self.pinB.getFrom().getOutput()
def setNextPin(self,source):
if self.pinA == None:
self.pinA = source
else:
if self.pinB == None:
self.pinB = source
else:
print("Cannot Connect: NO EMPTY PINS on this gate")
class AndGate(BinaryGate):
def __init__(self,n):
BinaryGate.__init__(self,n)
def performGateLogic(self):
a = self.getPinA()
b = self.getPinB()
if a==1 and b==1:
return 1
else:
return 0
class OrGate(BinaryGate):
def __init__(self,n):
BinaryGate.__init__(self,n)
def performGateLogic(self):
a = self.getPinA()
b = self.getPinB()
if a ==1 or b==1:
return 1
else:
return 0
class UnaryGate(LogicGate):
def __init__(self,n):
LogicGate.__init__(self,n)
self.pin = None
def getPin(self):
if self.pin == None:
return int(input("Enter Pin input for gate "+self.getName()+"-->"))
else:
return self.pin.getFrom().getOutput()
def setNextPin(self,source):
if self.pin == None:
self.pin = source
else:
print("Cannot Connect: NO EMPTY PINS on this gate")
class NotGate(UnaryGate):
def __init__(self,n):
UnaryGate.__init__(self,n)
def performGateLogic(self):
if self.getPin():
return 0
else:
return 1
class Connector:
def __init__(self, fgate, tgate):
self.fromgate = fgate
self.togate = tgate
tgate.setNextPin(self)
def getFrom(self):
return self.fromgate
def getTo(self):
return self.togate
def main():
g1 = AndGate("G1")
g2 = AndGate("G2")
g3 = OrGate("G3")
g4 = NotGate("G4")
c1 = Connector(g1,g3)
c2 = Connector(g2,g3)
c3 = Connector(g3,g4)
print(g4.getOutput())
main()
I'm mostly doubted by the tgate.setNextPin(self) statement in Connector class __init__. Is it a method call? If it is, why is it called with just one parameter, when there are two actually required by the setNexPin function in UnaryGate and BinaryGate classes (self, source)? How does the fromgate variable ends up as the source arrgument? Does this statement 'initiliaze' anything actually, and what?
Next thing which troubles me is, for example, when I print(type(g4)) before declaring g4.getOutput(), I get <class '__main__.OrGate'>, but when the g4.getOutput() starts, and functions start calling each other, to the point of calling getPin function, if I put print (self.pinA) before return self.pinA.getFrom().getOutput(), I get the <__main__.Connector object at 0x2b387e2f74d0>, although self.Pin is a variable from g4 OrGate instance. How can one variable from one class instance can become object of another class, which isn't inheriting it? Does this have something with a work of setNextPin() function?
Can someone explain this to me as I am new to OOP and thorougly confused by this piece of code. Thank you.
Regarding your first question, tgate.setNextPin(self) is a method call. tgate is an object, presumably an instance of one of the gate types. When you access instance.method, Python gives you a "bound method" object, which works pretty much like a function, but which puts the instance in as the first argument when it is actually called. So, tgate.setNextPin(self) is really calling type(tgate).setNextPin(tgate, self)
Your second question seems to reflect a misunderstanding of what attributes are. There's no requirement that an object's attributes be of its own type. In the various LogicGate sub-classes, the pin/pinA/pinB attributes are either going to be None (signaling that the user should be prompted for an input value) or an instance of a Connector (or something else with a getFrom method). Neither of those values is a LogicGate instance.
As for where the Connector instance you saw came from, its going to be one of the c1 through c3 values you created. Connector instances install themselves onto a pin of their tgate argument, with the setNextPin call you were asking about in your first question. I can't really speak to the g4 gate you are looking at, as it seems to be different than the g4 variable created in your example code's main() function (it is a different type), but I suspect that it is working as designed, and it's just a bit confusing. Try accessing the pinX attributes via g4.pinA rather than inspecting them inside of the methods and you may have a bit less confusion.
Here's a bit of code with outputs that should help you understand things a bit better:
# create a few gates
g1 = AndGate("G1")
g2 = OrGate("G2")
# at this point, no connectors have been hooked up, so all pinX attrs are None:
print("G1 pins:", g1.pinA, g1.pinB) # "None, None"
print("G2 pins:", g2.pinA, g2.pinB) # "None, None"
# test that the gates work independently of each other:
print("G1 output:", g1.getOutput()) # will prompt for two inputs, then print their AND
print("G2 output:", g2.getOutput()) # will prompt for two inputs, then print their OR
# create a connection between the gates
c1 = Connector(g1, g2) # connects output of g1 to first available pin (pinA) of g2
# we can see that g2.pinA has changed
print("G2 pins after connection:", g2.pinA, g2.pinB)
# "<__main__.Connector object at SomeHexAddress>, None"
# now, if we get g2's output, it will automatically request g1's output via the Connector
print("G2 output:", g2.getOutput())
# will prompt for 2 G1 inputs, and one G2 input and print (G1_A AND G1_B) OR G2_B
If you want to play around with these classes more, you might want to add a __str__ (and/or __repr__) method to some or all of the classes. __str__ is used by Python to convert an instance of the class into a string whenever necessary (such as when you pass it as an argument to print or str.format). Here's a quick __str__ implementation for Connector:
def __str__(self):
return "Connector between {} and {}".format(self.fgate.name, self.tgate.name)
I have been subclassing an Python's random number generator to make a generator that doesn't repeat results (it's going to be used to generate unique id's for a simulator) and I was just testing to see if it was consistent in it's behavior after it has been loaded from a previours state
Before people ask:
It's a singleton class
No there's nothing else that should be using that instance (a tear down sees to that)
Yes I tested it without the singleton instance to check
and yes when I create this subclass I do call a new instance ( super(nrRand,self).__init__())
And yes according to another post I should get consistent results see: Rolling back the random number generator in python?
Below is my test code:
def test_stateSavingConsitantcy(self):
start = int(self.r.random())
for i in xrange(start):
self.r.random()
state = self.r.getstate()
next = self.r.random()
self.r.setstate(state)
nnext = self.r.random()
self.assertEqual(next, nnext, "Number generation not constant got {0} expecting {1}".format(nnext,next))
Any help that can be provided would greatly appreciated
EDIT:
Here is my subclass as requested
class Singleton(type):
_instances = {}
def __call__(self, *args, **kwargs):
if self not in self._instances:
self._instances[self] = super(Singleton,self).__call__(*args,**kwargs)
return self._instances[self]
class nrRand(Random):
__metaclass__ = Singleton
'''
classdocs
'''
def __init__(self):
'''
Constructor
'''
super(nrRand,self).__init__()
self.previous = []
def random(self):
n = super(nrRand,self).random()
while n in self.previous:
n = super(nrRand,self).random()
self.previous.append(n)
return n
def seed(self,x):
if x is None:
x = long(time.time()*1000)
self.previous = []
count = x
nSeed = 0
while count < 0:
nSeed = super(nrRand,self).random()
count -= 1
super(nrRand,self).seed(nSeed)
while nSeed < 0:
super(nrRand,self).seed(nSeed)
count -= 1
def getstate(self):
return (self.previous, super(nrRand,self).getstate())
def setstate(self,state):
self.previous = state[0]
super(nrRand,self).setstate(state[1])
getstate and setstate only manipulate the state the Random class knows about; neither method knows that you also need to roll back the set of previously-generated numbers. You're rolling back the state inherited from Random, but then the object sees that it's already produced the next number and skips it. If you want getstate and setstate to work properly, you'll have to override them to set the state of the set of already-generated numbers.
UPDATE:
def getstate(self):
return (self.previous, super(nrRand,self).getstate())
This shouldn't directly use self.previous. Since you don't make a copy, you're returning the actual object used to keep track of what numbers have been produced. When the RNG produces a new number, the state returned by getstate reflects the new number. You need to copy self.previous, like so:
def getstate(self):
return (self.previous[:], super(nrRand, self).getstate())
I also recommend making a copy in setstate:
def setstate(self, state):
previous, parent_state = state
self.previous = previous[:]
super(nrRand, self).setstate(parent_state)