twelfth
twelfth

Reputation: 69

Class method not returning value in Python

I am having some trouble passing a variable into a function and having it return a desired value. For clarity, I am obtaining a record from a PostgreSQL table as a tuple and then passing the relevant index of that tuple into a class variable. Everything else seems to work but this and I have no idea why.

The code is as follows:

from datetime import date
from decimal import Decimal
from config import config
import psycopg2


conn = None
try:
    params = config()
    conn = psycopg2.connect(**params)
    cur = conn.cursor()
    cur.execute('SELECT * FROM testdb.vitals ORDER BY "vitalsID"')

except (Exception, psycopg2.DatabaseError) as error:
    print(error)

row = cur.fetchone()


class Vitals:

    birth = date(1990, 12, 12)
    today = date.today()
    years = today.year - birth.year
    age = today.year - birth.year - ((today.month, today.day) < (birth.month, birth.day))
    weight = row[2]
    height = row[3]
    activity = row[4]
    goal = row[5]
    fatRatio = row[6]
    carbohydrateRatio = row[7]
    proteinRatio = row[8]

    def __init__(self):
        # connect and initialise vitals
        pass

    # display on gui

    @classmethod
    def activityText(cls):
        if cls.activity == Decimal(1.20):
            return testActivity * 2

    @classmethod
    def bmr(cls):
        return (Decimal(10) * cls.weight) + (Decimal(6.25) * cls.height) - (Decimal(5) * cls.age) + Decimal(5)

    @classmethod
    def tdee(cls):
        return Decimal(outputBmr * cls.activity)

    @classmethod
    def net(cls):
        if cls.goal == 1:
            return outputTdee - (outputTdee * Decimal(0.1))
        else:
            return outputTdee


importVitals = Vitals

testActivity = importVitals.activity
print(testActivity)

testActivityfunc = importVitals.activityText()
print(testActivityfunc)

outputBmr = round(importVitals.bmr())
print(outputBmr)

outputTdee = round(importVitals.tdee())
print(round(outputTdee))

outputNet = round(importVitals.net())
print(outputNet)

if importVitals.activity == Decimal(1.20):
    print('Hello')
else:
    print("\n")
    print(importVitals.activity)
    print(type(importVitals.activity))

The key class method is here:

    @classmethod
    def activityText(cls):
        if cls.activity == Decimal(1.20):
            return testActivity * 2

Output (following alteration of the final if statement in the module) is as follows:

1.20
<class 'decimal.Decimal'>
None
1751
2101
1891


1.20
<class 'decimal.Decimal'>

Process finished with exit code 0

For readability, here are the print statements:


testActivity = importVitals.activity
print(testActivity)
print(type(importVitals.activity))

testActivityfunc = importVitals.activityText()
print(testActivityfunc)

outputBmr = round(importVitals.bmr())
print(outputBmr)

outputTdee = round(importVitals.tdee())
print(round(outputTdee))

outputNet = round(importVitals.net())
print(outputNet)

if importVitals.activity == Decimal(1.20):
    print('Hello')
else:
    print("\n")
    print(importVitals.activity)
    print(type(importVitals.activity))

The class method in question always returns None. Also note the if statement at the end of the module. It always runs the else branch which, at least to me, bizarrely prints 1.20 and <class 'decimal.Decimal'>. So if Python recognises the instanced class variable of importVitals.activity as having the value 1.20 and the variable type of <class 'decimal.Decimal'>, then why is the if statement or the class method not returning what I would like it to? Is there something I am doing wrong here?

Just to reiterate, all of the other methods and print statements are working as expected so I am quite puzzled by this.

Thank you to anybody who took the time to read this. I would appreciate any help you can offer.

Upvotes: 1

Views: 288

Answers (2)

nagyl
nagyl

Reputation: 1644

Here is a more object oriented way to implement the above code:

import sys
import psycopg2

from datetime import date
from decimal import Decimal
from config import config

class Vitals:
    def __init__(self, row):
        self.birth    = date(1990, 12, 12)
        self.today    = date.today()
        self.years    = today.year - birth.year
        self.age      = today.year - birth.year - ((today.month, today.day) < (birth.month, birth.day))
        self.weight   = row[2]
        self.height   = row[3]
        self.activity = Decimal(float(row[4]))
        self.goal     = row[5]
        self.fatRatio = row[6]
        self.carbohydrateRatio = row[7]
        self.proteinRatio = row[8]

    def activityText(self):
        if self.activity == Decimal(1.20):
            return self.activity * 2
        
    def bmr(self):
        return (Decimal(10)   * self.weight) +
               (Decimal(6.25) * self.height) -
               (Decimal(5)    * self.age)    +
                Decimal(5)

    def tdee(self):
        return Decimal(self.bmr() * self.activity)

    def net(self):
        if self.goal == 1:
            return self.tdee() - (self.tdee() * Decimal(0.1))
        else:
            return self.tdee()

def getDataFromDatabase(sql):
    conn = None
    try:
        params = config()
        conn   = psycopg2.connect(**params)
        cur    = conn.cursor()
        cur.execute(sql)
    except (Exception, psycopg2.DatabaseError) as error:
        print(error)
        return None
    return cur.fetchone()

def main():
    sql = 'SELECT * FROM testdb.vitals ORDER BY "vitalsID"'
    row = getDataFromDatabase(sql)
    vitals = Vitals(row)
    #Here you can do your stuff with vitals
    
main()

You can create more objects, and fill them with different values. I suggest cast the values of row in the constructor to proper types, like activity to Decimal.
I cannot guarantee this version would solve your initial problem, I tried another solution, but since the question has a proper answer, that wasn't my main goal.


I saw the post that you said a one calorie difference is acceptable, so I suggest creating a function, that has a treshold of one calorie, and if the abs of the difference between 2 objects is less than the treshold, return true.

Upvotes: 1

szatkus
szatkus

Reputation: 1395

Looks like you have numeric errors:

>>> Decimal(1.20)
Decimal('1.1999999999999999555910790149937383830547332763671875')
>>> Decimal("1.20")
Decimal('1.20')

Use strings as an input for your decimals and everything should be fine. Floats are good for fast computations, but they are not a good fit for accuracy.

Upvotes: 1

Related Questions