An easy solution to this is to construct your url dynamically based on user input (you can use str.format() to do that). For example:

#!/usr/bin/env python

from requests import get
from bs4 import BeautifulSoup
import sys

v1 = sys.argv[1]
v2 = sys.argv[2]
amount = sys.argv[3]

# check if the values passed are valid
# and construct the url like so:
currency_page = 'http://www......../convert/?Amount={}&From={}&To={}'.format(amount,v1,v2)

currency = get(currency_page).text
currency_data = BeautifulSoup(currency, 'html.parser')

USD = currency_data.find('span', attrs={'class': 'uccResultUnit'})
USD_PKR = USD.text.strip()
print(USD_PKR)

Results:

$ ./test.py EUR PKR 1                           
1 EUR = 125.790 PKR

The other solution, as also mentioned in the comments, is to use

  1. an API or
  2. a module instead.
Answer from coder on Stack Overflow
🌐
Stack Overflow
stackoverflow.com › questions › 50040093 › currency-converter-in-python
Currency Converter in Python - Stack Overflow
currency = [580,700,400,650,450,200] symbols = ["USD", "GBP", "CAD", "EUR","CHF","YEN"] print('''Welcome to your currency converter App For USD conversion to Naira type 0, For GBP conversion to Naira type 1, For CAD conversion to Naira type 2, For EUR conversion to Naira type 3, For CHF conversion to Naira type 4, For YEN conversion to Naira type 5,*''') signs = int(input('Type the currency you want to convert to: ')) amount = float(input('Enter the amount you want to convert: ')) if signs ==0 or signs ==1 or signs ==2 or signs ==3 or signs ==4 or signs ==5: conversion = amount * currency[sign
🌐
Stack Overflow
stackoverflow.com › questions › 65881998 › how-make-this-currency-converter-in-python-works
function - How make this Currency Converter in Python works? - Stack Overflow
def currency_converter(): conversion_rate = 8.09647 error_message = "Error: your input should be a positive number" isnotnumber = True while isnotnumber: try: # checks if the input can be converted to float # input imports string variable even if you input a number # so if it can be changed to float it will calculate YEN # if not, so the input is not number and with while # it will continously ask for a number EUR = float(input("Enter a value in EUR to be converted to YEN: ")) YEN = EUR * conversion_rate isnotnumber = False print("Your input is equal to {output} stones".format(output=YEN)) ret
🌐
Stack Overflow
stackoverflow.com › questions › 50358082 › creating-a-currency-converter-in-python
tkinter - Creating a currency converter in Python - Stack Overflow
DateofOrder = StringVar() value0 = StringVar() convert = DoubleVar() currency = DoubleVar() def ConCurrency(): if value0.get() == "USA": convert1 = float (convert.get() * 1.52) convert2 = "USA Dollars", str('£.2f' '£'(convert1)) currency.set(convert2) elif value0.get() == "Kenya": convert1 = float(convert.get() * 156.21) convert2 = "Kemyan Shilling", str('£.2f' '£'(convert1)) currency.set(convert2) elif value0.get() == "Brazil": convert1 = float(convert.get() * 5.86) convert2 = "Brazilian Real", str('£.2f' '£'(convert1)) currency.set(convert2) elif value0.get() == "Canada": convert1 = fl
Top answer
1 of 1
3

Code Style Improvements

  • "Flat is better than nested". You can make an early exit in case of invalid input:

    import sys
    
    try:
        amount = float(input("Enter amount: "))
    except ValueError:
        print("Invalid input. Please enter only numbers.")
        sys.exit(1)
    

    That will allow you to remove the else: part and continue on the top-level. Or, you can let the user retry the input until it is valid

  • on the same topic of decreasing nestedness depth - add more early exists. For instance, if input currencies are invalid, throw an error and exit. Then, remove the else: and continue with your "positive case" logic on the same level. This should improve overall readability
  • define the constants, like the list of currencies, as per PEP8 - in upper case (reference)
  • put the main execution logic to under if __name__ == '__main__':
  • you can simplify if output_cur != '': with just if output_cur:
  • I'm not sure why you are importing datetime from _datetime (with underscore). I would expect the import to be from datetime import datetime
  • don't put comment for obvious parts of the code. For example, "importing required libraries" does not provide any useful information.
  • organize imports per PEP8 - stdlib libraries, then a newline, third-parties, a new line and then your "local" dependencies, all sorted alphabetically:

    from datetime import datetime
    
    from babel import numbers
    import pycountry
    import requests
    

Other High-level ideas

  • define custom exceptions. Instead of using the error_sev and error_inp functions where you print errors, define custom exceptions like InvalidCountryValueError. Throw it with your custom message inside
  • since you are posting it on github, consider organizing the project properly - add requirements.txt with the list of dependencies, add more documentation, tests - see more at Open Sourcing a Python Project the Right Way
  • on the related topic: currently, there is only one way to use your program. Consider someone who wants to use your library as an API - not going through the standard in inputs, but calling a function asking for currency rates. Thinking about your program this way may help you to re-design it a bit, apply "Extract Method" and other refactoring methods. Also, if you would try to add tests, you will quickly realize that there is no easy way to unittest the program - usually a red flag when designing clean and modular APIs

Performance notes

  • I'd use a set to keep the supported list of currencies. Since you check the input currencies to be valid with in, this should have a positive impact on performance:

    CURRENCIES = {
        'USD', 'JPY', 'BGN', 'CZK', 'DKK', 'GBP', 'HUF', 'PLN', 'RON', 'SEK', 'CHF', 'NOK', 'HRK', 'RUB', 'TRY',
        'AUD', 'BRL', 'CAD', 'CNY', 'HKD', 'IDR', 'ILS', 'INR', 'KRW', 'MXN', 'MYR', 'NZD', 'PHP', 'SGD', 'THB',
        'ZAR', 'ISK'
    }
    
🌐
Stack Overflow
stackoverflow.com › questions › 48729095 › python-currency-converter
Python Currency Converter - Stack Overflow
The following code is a simple currency converter. Everything works but the last if statement, which causes the code to break. Whenever I put a number less than .25 in an if statement, it breaks ...
🌐
Stack Overflow
stackoverflow.com › questions › 66939233 › currency-converter-without-using-if-else
python - Currency converter without using if else - Stack Overflow
April 4, 2021 - So I managed to program a currency converter using a dictionary. The disadvantage of doing with if-else is that you'll go hectic typing hundreds of nested if-else in all the exchange rates. First, we need to have one canonical currency or base currency, to which all the other currencies are compared (from_currency -> base_currency -> to_currency).
Find elsewhere
🌐
Stack Overflow
stackoverflow.com › questions › 36189937 › python-currency-converter-using-functions-with-two-functions
Python currency converter using functions with two functions - Stack Overflow
Releases Keep up-to-date on features we add to Stack Overflow and Stack Internal. ... Find centralized, trusted content and collaborate around the technologies you use most. Learn more about Collectives ... Bring the best of human thought and AI automation together at your work. Explore Stack Internal ... print('Please choose from the menu.') print('============================') print('1: Convert between USD and EUR') print('2: Convert between USD and Canada') print('3: Convert between USD and UK (GBP)') print('4: Convert between USD and China') print('5: Quit') print('============================') menu = int(input('Enter your Choice: '))
Top answer
1 of 1
4

First, there's very little point in using a class like you are here. You're essentially just using the constructor as a function to ask for input. You're also needlessly making some variables attributes of the object, like self.features, self.x, and self.d. The only use of features is to be iterated over within the constructor, and the only use of the latter two are inside of input_process. Even if this were an appropriate use of a class, that data doesn't need to be retained in the object itself. That just wastes memory.

To remedy this, I'd take everything in __init__, and move it into a regular function. To store the data, I'd use a NamedTuple returned from the function.


Your prints are also needlessly verbose. You're using print("") as a way to print newlines. Newlines can simply be added using "\n" though.

print("")
print(Fore.GREEN + "-" * 76)
print("")

Can simply be:

print("\n" + (Fore.GREEN + "-" * 76) + "\n")

And since you use (Fore.GREEN + "-" * 76) multiple times, I'd save that into a string at the top of the file:

SEPARATOR = "\n" + (Fore.GREEN + "-" * 76) + "\n"

Your loop over features is also unnecessary. It can be done simply using join:

print("\n".join(features))

Your names are also poor. c1, c2, c, x and d are very vague and give little information about what they hold. I would make those names much longer.



After making the mentioned changes, and I ended up with:

from currency_converter import CurrencyConverter
from datetime import date
from colorama import Fore
import datetime
from typing import NamedTuple, Optional, Tuple

# These are constants. They should be defined externally, and have capitalized names
FEATURES = ["1. Convert using the last available rate.", "2. Convert using specific dates."]
SEPARATOR = "\n" + (Fore.GREEN + "-" * 76) + "\n"

class ConversionInformation(NamedTuple):  # A simple class to hold information
    feature_type: str
    amount: str
    source_currency: str
    target_currency: str
    conversion_date: Optional[Tuple]

def ask_for_input() -> ConversionInformation:
    print(SEPARATOR)
    print("\n".join(FEATURES))

    feature_type = input("Select a feature: ")

    print(SEPARATOR)

    amount = input("Amount: ")
    source_cur = input("Currency1: ")
    target_cur = input("Currency2: ")

    conversion_date = None

    if feature_type == "2":
        conversion_date = (int(input("Year: ")),
                           int(input("Month: ")),
                           int(input("Day: ")))

    return ConversionInformation(feature_type, amount, source_cur, target_cur, conversion_date)

def process_input(info: ConversionInformation):
    conv = CurrencyConverter()  # This wasn't needed when asking for input

    if info.feature_type == "1":
        # Note the more descriptive names
        result = round(conv.convert(info.amount, info.source_currency, info.target_currency), 2)
        cur_date = datetime.datetime.now()

        print(f"{info.amount} {info.source_currency} is {result} {info.target_currency} on {cur_date}")

    elif info.feature_type == "2":
        result = round(conv.convert(info.amount, info.source_currency, info.target_currency,
                                    date=date(*info.conversion_date)))

        print(f"{info.amount} {info.source_currency} was {result} {info.target_currency} in" +
              "-".join(str(d) for d in info.conversion_date))

    else:
        print("Illegal Feature Type:", info.feature_type)  # Tell the user that they entered an illegal type


# Also a constant
CURRENCIES = ["USD", "SEK", "GBP", "EUR"]


if __name__ == '__main__':
    print(Fore.GREEN + f"This is a Currency converter, the main currencies are {', '.join(CURRENCIES)}")
    print(SEPARATOR)

    while True:
        info = ask_for_input()
        process_input(info)

        print(SEPARATOR)

        if input("Finished. Do another? (Y/N)\n").lower() != 'y':
            break

It's far more verbose, but I believe it's much cleaner and easier to understand.

My main issue with what I've done here is package all the data into ConversionInformation just so it can be passed into process_input. You may find that it ends up being cleaner to make this all one large function.

🌐
Stack Overflow
stackoverflow.com › questions › 21988811 › how-can-i-fix-this-currency-converter-for-python › 21989402
debugging - how can i fix this currency converter for python? - Stack Overflow
currencynum = 0 yenpound=0.0059 yendoler=0.0098 yeneuro=0.0071 yenyen=1 yeneuro=0.0071 yenyen=1 poundyen=170.55 pounddoler=1.67 poundeuro=1.21 poundpound=1 dolpound=0.60 doleuro=0.73 doldol=1 dolyen=102.37 europound=0.83 eurodol=1.38 euroyen=140.79 euroeuro=1 tipe= input ("input what curontsy you have. yen y,us dollrus d, euro e, pound p.") if tipe == "y": print ("you have choson yen") elif tipe == "e": print("you have choson euro") elif tipe == "d": print ("you have choson us dollrus") elif tipe == "p": print ("you have choson pound") howmuch = float(input("how much do you have")) otheramount
Top answer
1 of 5
1

If i understand correctly, the user will input either "0", "1" or "2" as a input, for the convertion for dollar, euro or yen respectively. So you need to change the initial values of dollar, euro and yen. Changing that, the version with integer input will work.

dollar = 0
euro = 1
yen = 2

If the input is a string such as "dollar", "euro" or "yen", the variables need to be changed to these respective strings, so firstly you need to set the variable as:

dollar = "dollar"
euro = "euro"
yen = "yen"

Then change the input type for raw_input, as python requires this type of input to recognize a String input. So change convert_to to:

convert_to = raw_input ("What currency do you want to convert to? ")

If you try to pass a string to a input variable it will always return 0. And since the initial values for all the variable are 0, the if statements returns true when comparing convert_to to dollar , euro or yen. Because the comparsion to the dollar is hapening first, it always goes to that case. These are the changes necessary for the code to run.

Extra: You also don't need the variables in this case, since you're only using them in one field. So on your if statements, you can just compare them to the specific strings, as:

if convert_to == "dollars":
    amount *= 1.3
elif convert_to == "euros":
     amount *=  1.17
elif convert_to == "yen":
    amount *= 133.66
else:
    print "You must pick either dollar, euro or yen." 

Hope it helps.

2 of 5
1

Please try to change to this:

if convert_to == "dollar":
    amount = float(amount) * 1.3
elif convert_to == "euro":
     amount = float(amount) * 1.17
elif convert_to == "yen":
    amount = float(amount) * 133.66

There are 2 changes. First one is to set currencies (dollar , euro , yen) to string cause that way it will be possible to compare it with the user's input which will be also a string. Second, amount that is entered by user is string again so we have to convert it to float in order to calculate the converted amount.

Top answer
1 of 2
3

Sorry to hear that; at a first glance the code looks okay, naming and splitting into functionality looks good, as well as the use of appropriate libraries, but there's a number of things that could be improved too.

For starters, on the GitHub README.md you're listing a couple of libraries that have to be installed - so it would be easy enough to add a requirements.txt for pip to install them (or an equivalent setup via virtualenv etc.). As an interviewer the smoother the whole thing goes, from downloading to seeing the expected result, the better! Consider next time how someone else runs your project, make it as easy as possible for them, while not going overboard (like, unless you're going to provide a Docker image for them (which is obviously an option ...) for a Python project: Requiring them to have Python 3.X installed is okay, same for pip, but the specific libraries not; give them a Makefile perhaps such that make dependencies runs pip install -r requirements.txt or so).

Next, the README.md says Run a main.py file..., but instead you're using __main__.py, so the documentation should be updated; like I'd rather have it say "Run PYTHONPATH=. python3 -m pyapi." since I might not be familiar with all variants of how Python programs can be invoked (e.g. I had to play with a few different variants till I figured out how to run it ... an interviewer likely has less patience and time than me.)


Okay, so I run pyapi, then curl the examples from the description page (not yours mind you, it'd be a good idea to have it all on your repository too so it doesn't depend on someone else's files!), the first example fails, the second one looks okayish:

0 ~ % curl "http://localhost:5000/currency_converter?amount=0.9&input_currency=¥&output_currency=AUD"
Currency not recognized%
0 ~ % curl "http://localhost:5000/currency_converter?amount=10.92&input_currency=£"
{
    "input": {
        "amount": 10.92,
        "currency": "\u00c2\u00a3"
    },
    "output": {
        "ALL": null
    }
}%

Though, well, the output is empty. Can't really tell why except it looks like I'm inputting the wrong currencies? Using EUR actually works and gives me output:

0 ~ % curl "http://localhost:5000/currency_converter?amount=10.92&input_currency=EUR"
{
    "input": {
        "amount": 10.92,
        "currency": "EUR"
    },
    "output": {
        "ALL": 1380.78
    }
}%

Now CLI, much better, first example works out of the box, second and third don't, same problem, the currency unit isn't converted somehow. Though if I do it myself it works (JPY/GBP). Ah, right, there's some where the currency unit isn't unique, that's fine I guess.

However, if the output currency isn't specified I read the problem statement such that it should then return the conversion to all other currencies ("if output_currency param is missing, convert to all known currencies"), but that's not what's happening:

0 kiwi-currencies % PYTHONPATH=. python3 -m pycli --amount 10.92 --input_currency EUR
INFO: FUNC: create_currencies_dict
INFO: FUNC: sign_to_abbreviation parameters: curr:EUR
INFO: FUNC: sign_to_abbreviation parameters: curr:None
INFO: FUNC: create_json parameters: inp:EUR out:None am=10.92
INFO: FUNC: convert parameters: inp:EUR out:ALL am=10.92
INFO: FUNC: contact_api parameters: inp:EUR out:ALL
INFO: FUNC: contact_api Loading from CACHE: True
INFO: FUNC: convert rate: {'EUR_ALL': 126.445488}
{
    "input": {
        "amount": 10.92,
        "currency": "EUR"
    },
    "output": {
        "ALL": 1380.78
    }
}

What does ALL mean, "Albanian Leak"?


Now to the actual code, both of the __main__.py look mostly okay to me, but, this isn't production quality code: There are no error checks or fallbacks. If an expected argument is missing e.g. the HTTP API crashes (the CLI is better since the command line argument parser checks for it).

test.py looks pretty good! Though I'd suggest not to use assert like this. It could trivially be disabled when optimisations are turned on:

0 kiwi-currencies % python3 test.py
INFO: FUNC: create_currencies_dict
INFO: TEST: test_contact_api <MagicMock name='contact_api' id='140590866107192'>
INFO: TEST: simple_contact_api
.INFO: TEST: test_sign_to_abbreviation
INFO: FUNC: sign_to_abbreviation parameters: curr:€
INFO: FUNC: sign_to_abbreviation parameters: curr:£
.INFO: TEST: test_unknown_input
INFO: FUNC: convert parameters: inp:USD out:EgUR am=45.2
test.py:54: DeprecationWarning: Please use assertEqual instead.
  "Currency not recognized")
.
----------------------------------------------------------------------
Ran 3 tests in 0.007s

OK

0 kiwi-currencies % python3 -O test.py
INFO: FUNC: create_currencies_dict
INFO: TEST: test_contact_api <MagicMock name='contact_api' id='139676823006232'>
.INFO: TEST: test_sign_to_abbreviation
.INFO: TEST: test_unknown_input
INFO: FUNC: convert parameters: inp:USD out:EgUR am=45.2
test.py:54: DeprecationWarning: Please use assertEqual instead.
  "Currency not recognized")
.
----------------------------------------------------------------------
Ran 3 tests in 0.018s

OK

Notice how the second run doesn't exercise some of the tests!


Now for service.py.

  • You have comments, good, though I also imagine having some of those as docstrings (contact_api) might be nicer.
  • Logging is on, great, that's helpful once you've read the code.
  • sign_to_abbreviation, the for loop should be a dictionary lookup? Also the assignment to curr can be removed and directly returned.
  • create_json, dict = {} is overwritten immediately after. Same with dict["output"] = {}. Also not x == y should be x != y.
  • create_currencies_dict, it might make sense to check for the inverse and return early to keep the indendation shorter, e.g. if currencies_symbols: return and if response.status_code != 200: return None. else: pass at the end of the loop can also be omitted.
  • return None can also be omitted in general, or even just return. If you want to be explicit that's fine though.

So overall there are priorities: make it work, show the user how (not just by test cases, by writing down the exact invocation for them to use), then make it nicer. You're on the right way though!

2 of 2
1

First of all, many thanks for your reply. It has brought many interesting information to my attention resulting in an increased quality of the project.


To start with, I must say there were few mistakes in a code resulting in an improper functionality. All the calls without a stated output_currency should have given a different output, the problem was in a wrong indentation of the last line of a code snippet below (it was on the same level as the second-to-last line and therefore always being rewritten instead of added):
service.py, create_json:

..
output_currencies = output_currency.split(",")
    for curr in output_currencies:
        if curr != input_currency:
            if "output" not in dict:
                dict["output"] = {}
            dict["output"].update({curr: convert(input_currency, curr, amount)})
..


To continue, here is an updated version of README.md:

Currency Converter

A practical task for a position of Junior Python Developer. Task entry: https://gist.github.com/MichalCab/c1dce3149d5131d89c5bbddbc602777c

Prerequisites

python 3.4

Requirements

Required libraries can be found in requirements.txt and installed via pip3 install -r requirements.txt

Run application

Run a __main__.py file, either in pyapi or pycli folder depending on a desired form of usage.

Parameters

  • amount - amount which we want to convert - float
  • input_currency - input currency - 3 letters name or currency symbol
  • output_currency - requested/output currency - 3 letters name or currency symbol

Note that a single currency symbol can represent several currencies:
- in case this happens with output_currency, convert to all known currencies with such symbol
- in case this happens with input_currency, conversion is not performer. Rather, an info message with currencies having such symbol is shown, so a user can specify input_currency more precisely

Output Possibilities:

  • json with a following structure:

Single input and output currency: { "input": { "amount": <float>, "currency": <3 letter currency code> } "output": { <3 letter currency code>: <float> } }

Single input and multiple output currencies (in case a currency sign represents more currencies): { "input": { "amount": <float>, "currency": <3 letter currency code> } "output": { <corresponding 3 letter currency code>: <float> <corresponding 3 letter currency code>: <float> . . } }

Single input and no output currency - convert to all known currencies: { "input": { "amount": <float>, "currency": <3 letter currency code> } "output": { <3 letter currency code>: <float> <3 letter currency code>: <float> <3 letter currency code>: <float> . . } }

  • Info message:

Multiple input currencies (in case a currency sign represents more currencies):
"Input currency not clearly defined. Possible currencies with such symbol: <possible currencies>"

Unknown input currency: "Input currency not recognized"

Unknown output currency: "Output currency not recognized"

Examples

CLI

./currency_converter.py --amount 100.0 --input_currency EUR --output_currency CZK { "input": { "amount": 100.0, "currency": "EUR" }, "output": { "CZK": 2561.78 } }

./currency_converter.py --amount 0.9 --input_currency € --output_currency AUD { "input": { "amount": 0.9, "currency": "EUR" }, "output": { "AUD": 1.46 } }

./currency_converter.py --amount 10.92 --input_currency zł { "input": { "amount": 10.92, "currency": "PLN" }, "output": { "HRK": 18.84, "UZS": 24006.34, "RUB": 196.93, "BOB": 20.64, . . . } }

./currency_converter.py --amount 10.92 --input_currency EUR --output_currency £ { "input": { "amount": 10.92, "currency": "EUR" }, "output": { "GBP": 9.79, "FKP": 9.77, "LBP": 19462.11, "SHP": 16.97, "SYP": 6617.36, "EGP": 230.18, "GIP": 9.77 }
}

./currency_converter.py --amount 10.92 --input_currency Nonsense_curr Input currency not recognized

API

Note: When using curl, currencies symbols are not decoded properly and therefore not recognised. A recommended tool is Postman.

GET /currency_converter?amount=4.5&input_currency=₱&output_currency=VEF HTTP/1.1 { "input": { "amount": 4.5, "currency": "PHP" }, "output": { "VEF": 20633.77 } }

GET /currency_converter?amount=10.92&input_currency=£ HTTP/1.1 Input currency not clearly defined. Possible currencies with such symbol: SHP,FKP,EGP,LBP,SYP,GIP,GBP

GET /currency_converter?amount=10.92&input_currency=₦ HTTP/1.1 { "input": { "amount": 10.92, "currency": "NGN" }, "output": { "HRK": 0.19, "UZS": 241.47, "RUB": 1.98, "BOB": 0.21, "TZS": 68.63, "GBP": 0.02, "GIP": 0.02, "GTQ": 0.23, . . . } }


Now, you mentioned "this isn't production quality code: There are no error checks or fallbacks. If an expected argument is missing e.g. the HTTP API crashes". These are the measures I came up with: pyapi/__main__.py:
(simple check if arguments are present)

def get():
    if 'amount' in request.args and 'input_currency' in request.args:
        if 'output_currency' in request.args:
            return service.create_json(service.sign_to_abbreviation(request.args['input_currency']),
                                       service.sign_to_abbreviation(request.args['output_currency']),
                                       request.args['amount'])
        else:
            return service.create_json(service.sign_to_abbreviation(request.args['input_currency']),
                                       "None",
                                       request.args['amount'])
    return "Missing arguments"

service.py, contact_api:
(exception if external site is unreachable (+logging of a time needed for a request))

# external converter service
def contact_api(inp, out):
    logging.info(" FUNC: contact_api parameters: inp:%s out:%s", inp, out)
    api_url_base = 'http://free.currencyconverterapi.com/api/v5/convert'
    conversion = inp + "_" + out
    payload = {"q": conversion, "compact": "ultra"}
    try:
        start_time = time.time()
        response = requests.get(api_url_base, params=payload, timeout=1)  # we have 1 sec to get a response
        logging.info(" FUNC: contact_api request elapsed time: %s", time.time() - start_time)
    except requests.exceptions.ConnectionError as e:
        logging.error(" FUNC: contact_api CONNECTION ERROR: ", e)
        return None

    logging.info(" FUNC: contact_api Loading from CACHE: %s", response.from_cache)
    if response.status_code == 200:
        return json.loads(response.content.decode('utf-8'))
    return None

I consider this to be simple and functional, could it be done better?


And to finish with, tests could get improved too but that is for another day. And for a reply to be complete, here is a link to the project: https://github.com/ciso112/kiwi-currencies

🌐
Stack Overflow
stackoverflow.com › questions › 70250099 › convert-any-currency-to-usd-using-python
pandas - Convert any currency to USD using Python - Stack Overflow
December 6, 2021 - currency value company date converted CAD 10 A 1/1/2020 130 AED 10 B 2/1/2020 500 GBP 10 C 2/1/2020 360 INR 10 A 1/1/2020 720 ... @user17242583 Exchange rates vary, so OP wants to look them up on the specific day. [I thought OP replied to you already but apparently not.] ... from_currency to_currency date CAD USD 1/1/2020 13 AED USD 2/1/2020 50 GBP USD 2/1/2020 36 INR USD 1/1/2020 72 Name: exc_rate, dtype: int64
🌐
GeeksforGeeks
geeksforgeeks.org › python › currency-converter-in-python
Currency Converter in Python - GeeksforGeeks
November 3, 2025 - Using Tkinter + forex_python for a GUI-based converter · This method requires forex API keys. Get you own API key from here. Install requests module to handle API calls using the following command: ... import requests class CurrencyConverter: def __init__(self, url): # Fetching real-time data from the API data = requests.get(url).json() self.rates = data["rates"] def convert(self, from_currency, to_currency, amount): """Convert amount from one currency to another.""" initial_amount = amount # Convert from non-EUR currency to EUR first if from_currency != 'EUR': amount = amount / self.rates[fr
🌐
Stack Overflow
stackoverflow.com › questions › 27336411 › currency-converter-issue
python - Currency converter Issue - Stack Overflow
The variable 'value', takes the user input, the formating is using string formating(the % sign), it takes the currency_to and currency_from. converted_value sends the 'value' and the 'ratio', and catchs the new value
Top answer
1 of 2
6

You might not have realised it yet, but all your unimplemented methods will very much look like real_to_dollar:

  • ask the user how much of the original currency he wants converted;
  • apply a specific conversion rate;
  • tell the user the result of the conversion in the desired currency.

So that should be a single function, parametrized by the elements that differ between various calls: name of the origin currency, name of the converted currency, exchange rate.

def convert_currency(from, to, rate):
    user_value = raw_input("How many {}? ".format(from))
    amount = float(user_value)
    conversion = amount * rate
    print " {} {} is equal to {} {}.".format(user_value, from, conversion, to)

Also note the use of format to simplify building a string with numbers.

You would then call it convert_currency("reais", "dollars", 0.26) or convert_currency(" dollars", "reias", 3.85).

However, having to use a bunch of ifs to know what parameters to use (like you would have used a bunch of ifs to know which function to call) is not that great and can be a burden to maintain and expand. You could use a second dictionary to map couples of currencies to exchange rates:

RATES = {
    ("BRL", "USD"): 0.26,
    ("USD", "BRL"): 3.85,
    # Expand as necessary
}

And just have to get the rate using RATES[(user_choice1, user_choice2)].

2 of 2
3

Exchange rates change every day, so your program would quickly become either obsolete or unmaintainable by having the conversion factor as a constant in the program. I suggest you to use an API (for example: http://fixer.io/).

You have a supported currencies dictionary, but you don't test if the user input is among the supported ones. This can be done with a while True: loop and then break if the user choice is ok.

I also believe that the user would be bummed for having to digit the currency abbreviation :p...

Taking those considerations in mind, here's an example code:

import requests


SUPPORTED_CURRENCIES = {
    "EUR": "European euro",
    "USD": "US dollar",
    "GBP": "Pound sterling",
    "BRL": "Brazilian real"
}


CURRENCY_CODES = {
    1: "EUR",
    2: "USD",
    3: "GBP",
    4: "BRL"
}


def get_exchange_rate(base_currency, target_currency):
    if not (base_currency in SUPPORTED_CURRENCIES.keys()):
        raise ValueError("base currency {} not supported".format(base_currency))
    if not (target_currency in SUPPORTED_CURRENCIES.keys()):
        raise ValueError("target currency {} not supported".format(target_currency))

    if base_currency == target_currency:
        return 1

    api_uri = "https://api.fixer.io/latest?base={}&symbols={}".format(base_currency, target_currency)
    api_response = requests.get(api_uri)

    if api_response.status_code == 200:
        return api_response.json()["rates"][target_currency]


if __name__ == '__main__':
    print("Welcome to Currency Converter")

    amount = float(input("Enter the amount you wish to convert: "))

    print("Choose a base currency among our supported currencies:")
    while True:
        for code, currency in CURRENCY_CODES.items():
            print("code {}: base {}".format(code, currency))
        base_currency_code = int(input("Please digit the code: "))
        if base_currency_code in CURRENCY_CODES.keys():
            break
        else:
            print("Invalid code")
    base_currency = CURRENCY_CODES[base_currency_code]

    print("Choose a target currency among our supported currencies:")
    while True:
        for code, currency in CURRENCY_CODES.items():
            print("code {}: target {}".format(code, currency))
        target_currency_code = int(input("Please digit the code: "))
        if target_currency_code in CURRENCY_CODES.keys():
            break
        else:
            print("Invalid code")
    target_currency = CURRENCY_CODES[target_currency_code]

    exchange_rate = get_exchange_rate(base_currency, target_currency)

    print("{} {} is {} {}".format(amount, base_currency, amount * exchange_rate, target_currency))

The code uses the requests library, which is awesome and I suggest you to check it out.

Top answer
1 of 2
1

Never assume that your user doesn't make mistakes. I ran your program with no arguments ... error message, fine. I ran it with a single argument of 4 ... error message, fine. I ran it with two arguments, 4 and 5 ...:

Traceback (most recent call last):
  File "test.py", line 17, in <module>
    rate = checker(args.curency_for_convert, args.curency_converted_to)
  File "test.py", line 9, in checker
    rate = content["rates"][symbol.upper()]
KeyError: 'rates'

For debugging purposes, I put print content above that line and ran again:

{'error': 'Invalid base'}

If I run the program with a valid base, but an invalid symbol:

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    rate = checker(args.curency_for_convert, args.curency_converted_to)
  File "test.py", line 8, in checker
    rate = content["rates"][symbol.upper()]
KeyError: '8'

You should catch these errors and display something that is a little more user-friendly.


I would recommend that you read PEP 8, the Python style guide. There are a couple things you aren't following:

Imports should usually be on separate lines, e.g.:

Yes:

import os
import sys

No:

import sys, os

It's okay to say this though:

from subprocess import Popen, PIPE

Don't use spaces around the = sign when used to indicate a keyword argument or a default parameter value.


Your whitespace is a little strange. I would have at least one blank line above and below the function definition. I would also put one after the if block and maybe even before it too. You have " =", " = ", and "=" used in just three lines. PEP 8 says that A Foolish Consistency is the Hobgoblin of Little Minds, but that doesn't mean never be consistent. It is only the foolish consitency which is a hobgoblin. In this case, you have nothing to lose.

Your naming is a little strange. n even shows up in the help messages. One-letter variable names are generally not recommended because they give very little idea of what the variable is for. curency_for_convert and curency_converted_to, besides mis-spelling "currency", are ungrammatical. You could say "for conversion" or "to convert", but "for convert"? I would suggest "convert_from" and "convert_to".

if len(sys.argv) > 3:
    parser.add_argument("n", type =int, help = "Amount of curency_for_convert", default=1)

Instead of manually parsing an argument, make it optional with nargs='?'

print rate if len(sys.argv) <= 3 else rate*args.n

Since our argument will always be present (either with the default of 1 or with a user-supplied number), we can now do print rate * args.n.

Full Program

#!/usr/bin/env python
import argparse
import ast
import requests
import sys

def checker(base, symbol):
    url = "http://api.fixer.io/latest?base=%s" % base.upper()
    r = requests.get(url)
    content = ast.literal_eval(r.content)
    rate = content["rates"][symbol.upper()]
    return rate

parser = argparse.ArgumentParser(description=
    "Currency converter. For instance: to convert 100 USD to RUB, it would be "
    "100 USD RUB. If you need only the rate: USD RUB"
)

parser.add_argument("amount", type=int, help="Amount of currency to convert", default=1, nargs='?')
parser.add_argument('convert_from', type=str, help="Currency to convert")
parser.add_argument('convert_to', type=str, help = "Currency of converted")

args = parser.parse_args()

try:
    rate = checker(args.convert_from, args.convert_to)
except KeyError:
    print "Invalid argument(s)"
else:
    print rate*args.amount
2 of 2
0

Style

Give your program some love. As it stand it is just a big pile of instructions. Add a bit of vertical spacing to separate logical sections, limit your line length so we can get most things at a glance.

You should also read and follow PEP8, the official Python's style guide:

  • each import statement on its own line;
  • consistent use of spaces around the = sign;
  • meaningful variable names (r is not).

The url variable isn't really one. You should extract it out of the function and give it a proper capitalized name as the constant it is. Also you should use str.format instead of % as it is the new standard.

Top-level code

Even if it is not that important for such "short" code, it is always a good practice to wrap your top-level code under an if __name__ == '__main__' statement. At the very least, it lets you import your code into an interactive session to test your function without triggering all its logic.

Requests

The responses from api.fixer.io are json strings, not Python dictionaries. You can convert back and forth between the two using the json module. However, requests provide a builtin hook for that so you don't even have to import the module:

content = r.json()

Argparse

You can totally get the help usage you’re looking for if you provide more than two parameters to your program. For instance:

$ python currency.py a b --help

However it is less than ideal. Argparse provide a way to have optional required arguments (sounds weird anyway):

  • set the nargs parameter to '?' to indicate it is optional;
  • set a default value.

Since there is only one optional parameter, argparse will figure out things pretty well base on the length of sys.argv. Now how do you figure it out? Easily enough, if default=1 then you perform rate * args.n blindlessly.

Oh, and type=str is useless as it is the default.

Proposed improvements

#!/usr/bin/env python

import requests
import argparse


URL = "http://api.fixer.io/latest?base={}"


def get_rate(base, symbol):
    url = URL.format(base.upper())
    content = requests.get(url).json()
    return content['rates'][symbol.upper()]


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
            description="Currency converter. For instance : 'Convert 100"
            " USD to RUB' Will be: 100 USD RUB. If You need only rate: "
            "USD RUB")
    parser.add_argument("n", type=int, nargs="?", default=1,
                        help="Amount of currency_for_convert")
    parser.add_argument("base_currency", help="Currency for convert")
    parser.add_argument("converted_currency", help="Currency that converted to")

    args = parser.parse_args()

    converted = args.n * get_rate(args.base_currency, args.converted_currency)
    print converted

You may also be interested into handling networking issues, conversions issues or bad currencies.