Whilst having 20+yrs in IT I'm brand new to Python programming...

I'm trying to reference an object in a different class as a lookup...

I have a class which describes items and their prices
I have a class which describes a currency code and a conversion rate
I'm trying to auto-calculate the local price based on currency conversion lookup.
I can't seem to find the best way of linking the two.
Any ideas please?

class Currency(object):
   def __init__(self, currency, conversion_rate):
      self.currency = currency
      self.conversion_rate = conversion_rate
   def getconversionrate(self, currency):
      return self.conversion_rate

class Stock_Detail(object):
   def __init__(self, item, cost, local_cost, currency):
      self.item = item
      self.local_cost = local_cost
      self.currency = currency
      self.cost = self.local_cost * Currency.getconversionrate(self.currency)

Recommended Answers

All 15 Replies

You need to create an instance of the Currency object before using it and calling its methods. The way you have it done, you are trying to call a static method of the Currency class in the very last line of posted code (and it probably gives you an error stating that getconversionrate() expects 2 params, but only 1 given).

class Currency(object):
   def __init__(self, currency, conversion_rate):
      self.currency = currency
      self.conversion_rate = conversion_rate
   def getconversionrate(self, currency):
      return self.conversion_rate

class Stock_Detail(object):
   def __init__(self, item, cost, local_cost, currency):
      self.item = item
      self.local_cost = local_cost
      self.currency = currency
      # Create an instance of Currency object.
      # The constructor (the __init__ method) accepts 2 parameters.
      # The 1.2 conversion rate is made up, you pass your own (correct) one.
      currencyObject = Currency(currency, 1.2)
      self.cost = self.local_cost * currencyObject.getconversionrate(self.currency)

Here are couple things you might want to consider:
1) Why pass the cost to Stock_Detail constructor, if you are calculating that value and not using what is passed in?
2) Why not pass the Currency object into the Stock_Detail constructor? That way, if you have many Stock_Detail objects, the same Currency object can be reused.

If I understand what you are trying to do, maybe the following code will help:

# This class describes the currency in terms of its name and conversion rate.
class Currency(object):
   def __init__(self, name, rate):
      self.name = name
      self.rate = rate

# This class describes a Stock by its name/item and price (local_cost in your code).
class Stock(object):
   def __init__(self, item, price):
      self.item = item
      self.price = price

   # Returns the cost of this Stock in given currency.
   def getcost(self, currency):
      return self.price * currency.rate


### Here is the code that uses classes defined above.

# Create Currency objects (I don't know the exact rates).
c1 = Currency("Canadian Dollars", 0.9)
c2 = Currency("Russian Rubles", 2.5)

# Couple stocks
s1 = Stock("Microsoft", 30.5)
s2 = Stock("IBM", 25.8)
s3 = Stock("Sun", 35.0)

# Print original prices of stocks
print s1.price, s2.price, s3.price

# Print cost in Canadian Dollars
print s1.getcost(c1), s2.getcost(c1), s3.getcost(c1)

# Print cost in Russian Rubles
print s1.getcost(c2), s2.getcost(c2), s3.getcost(c2)

Hi, thanks for the code examples. The trouble I'm having is that the currency codes (USD/GBP/EUR etc) are given within the Stock items along with the local pricing. What I need is to be able to auto-retrieve 'cost' with the local pricing * conversion_rate as listed in the Currency table with a key of USD or GBP or EUR - without me specifying the currency used manually but referring to the enclosed currency field.

currency_table = []
stock_table = []

class Currency(object):
   def __init__(self, currency, conversion_rate):
      self.currency = currency
      self.conversion_rate = conversion_rate
   def getconversionrate(self, currency):
      return self.conversion_rate

class Stock(object):
   def __init__(self, item, local_cost, currency):
      self.item = item
      self.local_cost = local_cost
      self.currency = currency
      self.cost = self.local_cost * currency_table.getconversionrate(self.currency)

you are defining currency the way that has little added value. Your init looks fine in style. Consider though that i have not much OOP experience myself. Here my 5c anyway.

I would define currency converter which would have base currency. The rates would be fed in that currency.
Stock would have instance of the currency converter as init parameter instead of currency symbol. Maybe the currency rates should be class variables not instance variables.
Accessor getcurrencyrate is unnecessary in Python in your choice of objects. I would simply use say dollar.conversionrate. Also you have list but you use it as object currency_table. The method used is not for table but one currency.

Thank you for your input. I'm trying to intentionally overuse OOP. I understand I could put all stock and conversion rates in lists and run simple code between the lists to update the appropriate local prices.
My aim is to automate the lookup between objects to negate the requirement for any intervening code.

Stock will have a calculated GBP price based on the local price and local currency conversion rate. Any time I view/use/access the GBP price then what actually happens is the identifier of the local currency (EUR/USD etc) autocollects the current currency conversion rate from the linked currency object then calculates and returns the GBP price as part of the stock object. This enables the currency conversion rate to change whenever international rates change and I know then that I will always be presented with the current GBP price without having to manually run an update routine to recalculate - I simply re-read the GBP price.

I do not know how proper practise this is but here is some code I did based on my usage idea for currency objects:

class Currency(object):
    conversion_rate=1
    base_currency='GDP'

    def __init__(self, currency, conversion_rate,amount=1):
      self.currency = currency
      self.conversion_rate = conversion_rate
      self.amount=amount

    def convert_from(self,amount=None):
      if not amount: amount = self.amount
      return amount*self.conversion_rate

    def convert_to(self,amount):
      return amount/self.conversion_rate

    def __str__(self):
        return str(self.amount) + ' ' + self.currency + ' = ' + str(self.convert_from()) + ' ' + self.base_currency   

def get_rate(rates,cur):
   for amount1,cur1,_,amount2,cur2 in rates:
      if _=='=' and amount1=='1.00':
         if cur==cur1:
            return float(amount2)

def parse_rates(rates):
   rates= rates.split('\n')
   return [ i.split() for i in rates]

## result from pickup from net
rates = """1.00 EUR 	= 	0.825118 GBP
1.00 USD 	= 	0.688727 GBP"""

rates=parse_rates(rates)

am=40
usd=Currency('USD',get_rate(rates,'USD'))
eur= Currency('EUR',get_rate(rates,'EUR'))

usd.amount=20

print usd
#print usd.amount,usd.currency,' = ',usd.convert_from(),usd.base_currency

print am,usd.currency,' = ',usd.convert_from(am),usd.base_currency
print am,usd.base_currency,' = ',usd.convert_to(am),usd.currency
              
## update from pickup from net

newrates = """1.00 EUR 	= 	0.77118 GBP
1.00 USD 	= 	0.88727 GBP"""

rates=parse_rates(newrates)
usd.conversion_rate=get_rate(rates,usd.currency)
eur.conversion_rate=get_rate(rates,eur.currency)

print am,usd.currency,' = ',usd.convert_from(am),usd.base_currency

print usd.amount,usd.currency,' = ',usd.convert_from(),usd.base_currency

eur.amount=34

print eur
#print eur.amount,eur.currency,' = ',eur.convert_from(),eur.base_currency

Let's see if I understand this right.

You want each Stock to have a currency object associated with it to be used for calculating cost in GBP currency. Then, you want to be able to change the conversion rate of the Currency object and have any Stock that is associated with that Currency object to be able to return the cost according to the new conversion rate?

If that is right...

# This class describes the currency in terms of its name and conversion rate.
class Currency(object):
   def __init__(self, name, rate):
      self.name = name
      self.rate = rate

# This class allows for easy currency management.
class Market(object):
   def __init__(self):
   	  self.data = {}
   
   # Adds given currency.
   def add(self, currency):
      self.data[currency.name] = currency
   
   # Returns currency object with given name or None if not found.
   def get(self, name):
      if name in self.data.keys():
         return self.data[name]
      return None
   
   # Returns rate of currency with given name or 1 if not found.
   def getrate(self, name):
      cur = self.get(name)
      if cur:
         return cur.rate
      return 1.0
   
   # Sets the rate of currency with given name.
   def setrate(self, name, rate):
      cur = self.get(name)
      if cur:
         cur.rate = rate

# This class describes a Stock by its name and price.
# The currency is an instance of the Currency class.
class Stock(object):
   def __init__(self, name, price, currency):
      self.name = name
      self.price = price
      self.currency = currency   #link the currency object to this stock

   # Returns the cost of this Stock in GBP currency because
   # all conversion rates are specified in relation to GBP.
   def getcost(self):
      # Use the currency associated with this Stock to calculate the cost
      return self.price * self.currency.rate

### Here is the code that uses classes defined above.

# Utility function that prints prices of given stocks.
def printstocks(stocklist):
   for stock in stocklist:
      print "%-4s: %s: %s, GBP: %s" % \
            (stock.name, stock.currency.name, stock.price, stock.getcost())
   print "-"*30

# Create Currency objects.
# Conversion rates are relative to the GBP.
market = Market()
market.add( Currency("USD", 0.9) )
market.add( Currency("EUR", 1.1) )

# Couple stocks.
stocks = []
stocks.append( Stock("MSFT", 30.5, market.get("USD")) )
stocks.append( Stock("IBM", 25.8, market.get("USD")) )
stocks.append( Stock("SUN", 35.0, market.get("EUR")) )
stocks.append( Stock("BP", 19.9, market.get("EUR")) )

# Print prices.
print "Before any changes to currency:"
printstocks(stocks)

# Change currency rates.
market.setrate("USD", 0.8)
market.setrate("EUR", 1.2)

# Print prices again.
print "After changes to currency:"
printstocks(stocks)

Thank you - that last post was a great help.

Hopefully - a simple final question...

I'm trying to find the sum of all stock value without having to use a 'for' loop to count up the values - again, in an attempt to maximise OOP use.

class Stock(object):
   def __init__(self, item, cost, quantity):
      self.item = item
      self.cost = cost
      self.quantity = quantity
   def getvalue(self):
      return self.quantity * self.cost

I'd like something similar to:

Stock_Value = sum(Stock[:].getvalue)

Which should be 'getvalue' in all objects of class Stock - but I can't get the syntax right...assuming it's possible.

I'm trying to avoid...

mytotal = 0
   for stock in stock_table:
      mytotal += stock.getvalue()
   print mytotal

I don't think that sum can help here. You may resort to functional style:

Stock_Value = functools.reduce(lambda x, y: x + y.getvalue(), stock_table, 0)

I corrected some strange indentition, but I could not get sum to work only single elements addition:

# This class describes the currency in terms of its name and conversion rate.
class Currency(object):
    def __init__(self, name, rate):
        self.name = name
        self.rate = rate

# This class allows for easy currency management.
class Market(object):
    def __init__(self):
        self.data = {}

    # Adds given currency.
    def add(self, currency):
        self.data[currency.name] = currency

    # Returns currency object with given name or None if not found.
    def get(self, name):
        if name in self.data.keys():
            return self.data[name]
        return None
   
    # Returns rate of currency with given name or 1 if not found.
    def getrate(self, name):
         cur = self.get(name)
         if cur:
             return cur.rate
         return 1.0
   
    # Sets the rate of currency with given name.
    def setrate(self, name, rate):
        cur = self.get(name)
        if cur:
            cur.rate = rate

# This class describes a Stock by its name and price.
# The currency is an instance of the Currency class.
class Stock(object):
    def __init__(self, name, price, currency):
        self.name = name
        self.price = price
        self.currency = currency   #link the currency object to this stock

    # Returns the cost of this Stock in GBP currency because
    # all conversion rates are specified in relation to GBP.
    def getvalue(self):
        # Use the currency associated with this Stock to calculate the cost
        return self.price * self.currency.rate

    def __str__(self):
        return "%-4s: %s: %s, GBP: %s" % (
            self.name,
            self.currency.name,
            self.price,
            self.getvalue()
            )
    
    def __repr__(self):
        return "stock( %s, %s, %.2f, %.2f)" % (
            self.name,
            self.currency.name,
            self.price,
            self.getvalue()
            )

    def __add__(self,y):
        return self.getvalue()+y.getvalue()
    
### Here is the code that uses classes defined above.

# Utility function that prints prices of given stocks.
def printstocks(stocklist):
    for stock in stocklist:
        print stock
    print "-"*30

# Create Currency objects.
# Conversion rates are relative to the GBP.
market = Market()
market.add( Currency("USD", 0.9) )
market.add( Currency("EUR", 1.1) )

# Couple stocks.
stocks = []
stocks.append( Stock("MSFT", 30.5, market.get("USD")) )
stocks.append( Stock("IBM", 25.8, market.get("USD")) )
stocks.append( Stock("SUN", 35.0, market.get("EUR")) )
stocks.append( Stock("BP", 19.9, market.get("EUR")) )

# Print prices.
print "Before any changes to currency:"
printstocks(stocks)

# Change currency rates.
market.setrate("USD", 0.8)
market.setrate("EUR", 1.2)

# Print prices again.
print "After changes to currency:"
printstocks(stocks)

print stocks

## works
print 'Sum value of stocks: ',sum((s.getvalue() for s in stocks))
print stocks[0]+stocks[1]

## does not work, I do not know why?
print sum((stocks))

tonyjv, I fixed your code so it does work.
There are some intricacies of magic methods that are tricky.

# This class describes the currency in terms of its name and conversion rate.
class Currency(object):
    def __init__(self, name, rate):
        self.name = name
        self.rate = rate

# This class allows for easy currency management.
class Market(object):
    def __init__(self):
        self.data = {}

    # Adds given currency.
    def add(self, currency):
        self.data[currency.name] = currency

    # Returns currency object with given name or None if not found.
    def get(self, name):
        if name in self.data.keys():
            return self.data[name]
        return None
   
    # Returns rate of currency with given name or 1 if not found.
    def getrate(self, name):
         cur = self.get(name)
         if cur:
             return cur.rate
         return 1.0
   
    # Sets the rate of currency with given name.
    def setrate(self, name, rate):
        cur = self.get(name)
        if cur:
            cur.rate = rate

# This class describes a Stock by its name and price.
# The currency is an instance of the Currency class.
class Stock(object):
    def __init__(self, name, price, currency):
        self.name = name
        self.price = price
        self.currency = currency   #link the currency object to this stock

    # Returns the cost of this Stock in GBP currency because
    # all conversion rates are specified in relation to GBP.
    def getvalue(self):
        # Use the currency associated with this Stock to calculate the cost
        return self.price * self.currency.rate

    def __str__(self):
        return "%-4s: %s: %s, GBP: %s" % (
            self.name,
            self.currency.name,
            self.price,
            self.getvalue()
            )
    
    def __repr__(self):
        return "stock( %s, %s, %.2f, %.2f)" % (
            self.name,
            self.currency.name,
            self.price,
            self.getvalue()
            )

    def __add__(self,y):
        if hasattr(y,"getvalue"):
            return self.getvalue()+y.getvalue()
        return self.getvalue() + y

    __radd__ = __add__
    
### Here is the code that uses classes defined above.

# Utility function that prints prices of given stocks.
def printstocks(stocklist):
    for stock in stocklist:
        print stock
    print "-"*30

# Create Currency objects.
# Conversion rates are relative to the GBP.
market = Market()
market.add( Currency("USD", 0.9) )
market.add( Currency("EUR", 1.1) )

# Couple stocks.
stocks = []
stocks.append( Stock("MSFT", 30.5, market.get("USD")) )
stocks.append( Stock("IBM", 25.8, market.get("USD")) )
stocks.append( Stock("SUN", 35.0, market.get("EUR")) )
stocks.append( Stock("BP", 19.9, market.get("EUR")) )

# Print prices.
print "Before any changes to currency:"
printstocks(stocks)

# Change currency rates.
market.setrate("USD", 0.8)
market.setrate("EUR", 1.2)

# Print prices again.
print "After changes to currency:"
printstocks(stocks)

print stocks

## works
print 'Sum value of stocks: ',sum((s.getvalue() for s in stocks))
print stocks[0]+stocks[1]

## works now, too
print sum((stocks))

tonyjv - The indentation was simply 3 spaces instead of "regular" 4 spaces or a tab. Python is happy with that as long as its consistent.

And why would you guys complicate the code with all the "magic" methods for somebody who is just learning to code in python?

Anyway, back to the problem...
Why are you trying to avoid the for loop to get the sum? If you ever what a sum of some values, somewhere along the line there is going to be a loop that calculates the sum. It is true that it would be faster if the loop is in the C code vs python, but is that very significant for your project?

My suggestion is "make it work first, and then optimize it".


And why would you guys complicate the code with all the "magic" methods for somebody who is just learning to code in python?

Whoops.
I agree, magic methods here aren't required.

He wanted to get the sum working, same time I put those others to see if it helped the issue. I am just learning like the original poster, though I have known programming from 1981 (TI-58C calculator) as you can see from my efforts not succeeding.

The indention made it difficult to change the code, as auto indention came different for the code I inserted in my editor, even it should pick up the codes indention from the first lines. Your code is nice, don't take offence.

Personally I find it easier to have __str__ method instead of specialised syntax I have to remember for each class.

I agree basically with you other guys and also think your organizing of objects is more clever than my try with the currency conversion. There is still work there to connect the update of values for currency from net, which I little experimented with one conversion site in net and did cut and paste of multiline string, instead of direct values for rates.

Thanks for teaching us the tricks anyway, you object guys!

I tried for example this, but it was not enough:

def __add__(self,y):
        if type(y) in (int,float):
            return self.getvalue()+y
        else:
            return self.getvalue()+ y.getvalue()

May be it also worked with

__radd__ = __add__

UPDATE: it does work when I took out the test print added this line.

I have known programming from 1981

Wow! That's more than a decade before I was born!

__radd__ = __add__

UPDATE: it does work when I took out the test print added this line.

Yep, you have __add__, but don't forget __radd__ (add from the right side).
You have __sub__, __mul__, __div__, __xor__, and a few others all have an addition +r form.

All the posts have been extremely helpful in understanding Python as a language and OOP essentials.

I've completed my code now though unfortunately I can't post the final version because it is part of my PostGrad assignment studies that still need to be marked. I will add it however after my course is complete. I will make a 'Reference' note to this thread as an acknowledgement to all your assistance. The answers you gave were never used verbatim as answers for my course - I made sure that my questions here provided a reasonable degree of separation that still required me to think through the ideal answers.

Thank you

Paul.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.