0

Given a percent--for example, 5.43%--call its:

  • numeric form -> 5.43
  • decimal form -> 0.0543

The logic for converting between the two would be as follows:

input form      output form         operation
----------      -----------         ---------
numeric         numeric             multiply by 1.
decimal         decimal             multiply by 1.
numeric         decimal             multiply by 0.01
decimal         numeric             multiply by 100.

I'm interested in a more pythonic alternative to the following dict lookup. Because I'll be calling this conversion a number of times I'd prefer *not* to use logical operators. (I think...)

convert = {('num', 'num') : 1.,
           ('dec', 'dec') : 1.,
           ('num', 'dec') : 0.01,
           ('dec', 'num') : 100.
          }

def converter(num, input_form='num', output_form='dec'):
    return num * convert[(input_form, output_form)]

num = 5.43
print(converter(num))

Is this question too broad? Comment and let me know, and I'll try to hone in on what I'm looking for, but frankly I'm just interested in seeing other implementations. Basically, I currently have a class-based implementation where I want to establish a numeral and decimal form of self at instantiation and then also use the function within methods as well.

Brad Solomon
  • 38,521
  • 31
  • 149
  • 235
  • Nah, you're good. You _could_ try using lambdas instead, so you'd do `return convert[(input_form, output_form)](num)`, but I wouldn't advise it. You could, however, use nested dicts, so in fact `convert[input_form][output_form]` would be called instead, I'd consider that cleaner than tuple indexing. – cs95 Jul 14 '17 at 19:54
  • 1
    The current tuple based implementation gives: `1000000 loops, best of 3: 258 ns per loop`. The nested dict `convert2 = {'num': {'num': 1., 'dec': 0.01}, 'dec': {'num': 100., 'dec': 1.}}` gives `1000000 loops, best of 3: 197 ns per loop`. So the nested is slightly faster. – Oleg Medvedyev Jul 14 '17 at 20:05

3 Answers3

1

You could also use properties:

class Convertible:
    def __init(self, value, form='numeric'):
        if form == 'numeric':
            self._numeric = value
        elif form == 'decimal':
            self._numeric = value * 100
        else:
            raise ValueError("form must be 'numeric' or 'decimal'")

    @property
    def numeric(self):
        return self._numeric

    @numeric.setter
    def numeric(self, value):
        self._numeric = value

    @property
    def decimal(self):
        # for Python 2 compatibility, divide by a float
        # to avoid integer division if we have an integer 'numeric' value
        return self._numeric / 100.0

    @decimal.setter
    def decimal(self, value):
        self._numeric = value * 100

So, you can set either the numeric or decimal form of your number, and access either one transparently. Internally, we store only one of the forms.

You can use it like this:

val = Convertible()

val.numeric = 5.43
print(val.numeric)
# 5.43
print(val.decimal)
# 0.054299999999999994

val.decimal = 0.42
print(val.numeric)
# 42.0
print(val.decimal)
# 0.42

You can find more information on properties here, for example

Thierry Lathuille
  • 23,663
  • 10
  • 44
  • 50
1

I currently have a class-based implementation where I want to establish a numeral and decimal form of self at instantiation and then also use the function within methods as well.

If you're already using a class-based approach, then it seems most natural to wrap the conversion up into properties of the class rather than dealing with a separate conversion dictionary.

class DecimalPercent:
    def __init__(self, value, form='decimal'):
        self._value = value/100 if form == 'percent' else value

    @property
    def decimal(self):
        return self._value

    @decimal.setter
    def decimal(self, value):
        self._value = value

    @property
    def percent(self):
        return self._value * 100

    @percent.setter
    def percent(self, value):
        self._value = value / 100
Jared Goguen
  • 8,772
  • 2
  • 18
  • 36
1

An alternative to using properties in this case—just so you're aware—would be to define two float subclasses and use them as shown below:

class Num(float):
    pass

class Dec(float):
    pass

def converter(value):
    arg_type = Num if isinstance(value, Num) else Dec if isinstance(value, Dec) else None
    return value * (0.01 if arg_type is Num else 100 if arg_type is Dec else float('nan'))

print(converter(Num(5.43)))  # -> 0.0543
print(converter(Dec(0.12)))  # -> 12.0
print(converter(0.1618))     # -> nan
print(converter(42))         # -> nan
martineau
  • 119,623
  • 25
  • 170
  • 301