Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatically Type Cast Parameters In Python

Background:
I mostly run python scripts from the command line in pipelines and so my arguments are always strings that need to be type casted to the appropriate type. I make a lot of little scripts each day and type casting each parameter for every script takes more time than it should.

Question:
Is there a canonical way to automatically type cast parameters for a function?

My Way:
I've developed a decorator to do what I want if there isn't a better way. The decorator is the autocast fxn below. The decorated fxn is fxn2 in the example. Note that at the end of the code block I passed 1 and 2 as strings and if you run the script it will automatically add them. Is this a good way to do this?

def estimateType(var):
    #first test bools
    if var == 'True':
            return True
    elif var == 'False':
            return False
    else:
            #int
            try:
                    return int(var)
            except ValueError:
                    pass
            #float
            try:
                    return float(var)
            except ValueError:
                    pass
            #string
            try:
                    return str(var)
            except ValueError:
                    raise NameError('Something Messed Up Autocasting var %s (%s)' 
                                      % (var, type(var)))

def autocast(dFxn):
    '''Still need to figure out if you pass a variable with kw args!!!
    I guess I can just pass the dictionary to the fxn **args?'''
    def wrapped(*c, **d):
            print c, d
            t = [estimateType(x) for x in c]
            return dFxn(*t)
    return wrapped

@autocast
def fxn2(one, two):

   print one + two 

fxn2('1', '2')      

EDIT: For anyone that comes here and wants the updated and concise working version go here:

https://github.com/sequenceGeek/cgAutoCast

And here is also quick working version based on above:

def boolify(s):
    if s == 'True' or s == 'true':
            return True
    if s == 'False' or s == 'false':
            return False
    raise ValueError('Not Boolean Value!')

def estimateType(var):
    '''guesses the str representation of the variables type'''
    var = str(var) #important if the parameters aren't strings...
    for caster in (boolify, int, float):
            try:
                    return caster(var)
            except ValueError:
                    pass
    return var

def autocast(dFxn):
    def wrapped(*c, **d):
            cp = [estimateType(x) for x in c]
            dp = dict( (i, estimateType(j)) for (i,j) in d.items())
            return dFxn(*cp, **dp)

    return wrapped

######usage######
@autocast
def randomFunction(firstVar, secondVar):
    print firstVar + secondVar

randomFunction('1', '2')
like image 666
sequenceGeek Avatar asked Aug 10 '11 23:08

sequenceGeek


2 Answers

If you want to auto-convert values:

def boolify(s):
    if s == 'True':
        return True
    if s == 'False':
        return False
    raise ValueError("huh?")

def autoconvert(s):
    for fn in (boolify, int, float):
        try:
            return fn(s)
        except ValueError:
            pass
    return s

You can adjust boolify to accept other boolean values if you like.

like image 137
Ned Batchelder Avatar answered Oct 08 '22 17:10

Ned Batchelder


You could just use plain eval to input string if you trust the source:

>>> eval("3.2", {}, {})
3.2
>>> eval("True", {}, {})
True

But if you don't trust the source, you could use literal_eval from ast module.

>>> ast.literal_eval("'hi'")
'hi'
>>> ast.literal_eval("(5, 3, ['a', 'b'])")
(5, 3, ['a', 'b'])

Edit: As Ned Batchelder's comment, it won't accept non-quoted strings, so I added a workaround, also an example about autocaste decorator with keyword arguments.

import ast

def my_eval(s):
    try:
        return ast.literal_eval(s)
    except ValueError: #maybe it's a string, eval failed, return anyway
        return s       #thanks gnibbler

def autocaste(func):
    def wrapped(*c, **d):
        cp = [my_eval(x) for x in c]
        dp = {i: my_eval(j) for i,j in d.items()} #for Python 2.6+
        #you can use dict((i, my_eval(j)) for i,j in d.items()) for older versions
        return func(*cp, **dp)

    return wrapped

@autocaste
def f(a, b):
    return a + b

print(f("3.4", "1")) # 4.4
print(f("s", "sd"))  # ssd
print(my_eval("True")) # True
print(my_eval("None")) # None
print(my_eval("[1, 2, (3, 4)]")) # [1, 2, (3, 4)]
like image 30
utdemir Avatar answered Oct 08 '22 16:10

utdemir