1

I would like to use decorators to convert numbers in string format to int/float value, here is how I am trying to do it

def str_to_int(func):
     """
        This wrapper converts string value into integer
     """
     def wrapper(*args, **kwargs):
         for arg in args:
             arg = int(arg)
         return func(*args, **kwargs)
     return wrapper
 @str_to_int
 def number_as_string(a, b, c):
     return a,b,c
 print (number_as_string('1', '2', '3'))

Output

('1', '2', '3')

However, I wanted something like below

(1, 2, 3)

The above output I could generate with the below code

def str_to_int(func):
     """
        This wrapper converts string value into integer
     """
     def wrapper(x, y, z):
         return int(x), int(y), int(z)
     return wrapper
 @str_to_int
 def number_as_string(a, b, c):
     return a,b,c
 print (number_as_string('1', '2', '3'))

But the above code defeats the purpose of using a decorator at first place, since I would like to convert the string values irrespective of the arguments the original function has.

Could any one suggest what is wrong with first program and how to resolve it.

shanu
  • 145
  • 6
  • 3
    You are not changing `args` in your first example. Changing `arg` in the loop doesn't do anything, other that making `arg` an `int`. You need to build up a new `args` list and pass it to `func` or you could do `return func(*map(int, args), **kwargs)` – AChampion Aug 20 '17 at 05:15
  • Thanks, it worked!! – shanu Aug 20 '17 at 05:19
  • Do you understand why it worked? And why your original code didn't work? – AChampion Aug 20 '17 at 05:20
  • I understood it partially, I thought if I change each arg to int then the new args would be int. Could you please explain it more? – shanu Aug 20 '17 at 05:52

1 Answers1

1

The reason its not working as expected is because you never update the incoming args, you just keep overwriting the value in your loop.

Consider this revised version:

>>> def str_to_int(f):
...   def wrapper(*args, **kwargs):
...     args = map(int, args)
...     return f(*args, **kwargs)
...   return wrapper
...
>>> @str_to_int
... def foo(a,b,c):
...   return a,b,c
...
>>> foo('1','2','3')
(1, 2, 3)

The key is list line:

args = map(int, args)

It makes sure that the original args tuple is replaced completely, in your loop you were silently discarding the value.

For Python3, you should use:

args = tuple(map(int, args))

As map will return a map object.

Of course I hope you realize that this decorator only works if you pass it things than can actually be converted to numbers, if you pass in a string then it will raise a ValueError:

>>> foo('hello')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in wrapper
ValueError: invalid literal for int() with base 10: 'hello'

If you want to prevent that, you have to ignore those values that cannot be converted to an integer:

def wrapper(*args, **kwargs):
   _temp = []
   for i in args:
     try:
       _temp.append(int(i))
     except ValueError:
       _temp.append(i)
   args = tuple(_temp)
   return f(*args, **kwargs)

Now it will simply ignore things it cannot convert:

>>> foo('hello', '1', '2')
('hello', 1, 2)

You can shorten your code a bit, as * will automatically expand the arguments, you can use that to your advantage:

 # args = tuple(_temp)
 return f(*_temp, **kwargs)
Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284
  • Why create `args = tuple(_temp)` just to unpack it in the `f()` call. Wouldn't `f(*_temp, ...)` be sufficient? Even the `tuple(map())` is unnecessary `*` will unpack a generator (`map()` in Py3). – AChampion Aug 20 '17 at 05:30
  • Yes, it would - but here I'm being explicit - possibly the person may not understand it well. Valid point though, and I should add it. – Burhan Khalid Aug 20 '17 at 05:32
  • Thanks, Now I understood. previously I also tried to built an array with update int values of args but it would not passed as tuple. Did not know about this property of * – shanu Aug 20 '17 at 06:13