Morwenn
Morwenn

Reputation: 22552

Lexical cast from string to type

Recently, I was trying to store and read information from files in Python, and came across a slight problem: I wanted to read type information from text files. Type casting from string to int or to float is quite efficient, but type casting from string to type seems to be another problem. Naturally, I tried something like this:

var_type = type('int')

However, type isn't used as a cast but as a mechanism to find the type of the variable, which is actually str here.

I found a way to do it with:

var_type = eval('int')

But I generally try to avoid functions/statements like eval or exec where I can. So my question is the following: Is there another pythonic (and more specific) way to cast a string to a type?

Upvotes: 48

Views: 42156

Answers (5)

charelf
charelf

Reputation: 3815

For custom types that you defined yourself, you can replace eval by using the globals dict as follows:

class Hey():
    pass

hey_type = globals().get("Hey")

hey_instance_1 = Hey()
hey_instance_2 = hey_type()

print(type(hey_instance_1))
print(type(hey_instance_2))

printing

<class '__main__.Hey'>
<class '__main__.Hey'>

Maybe this also works for primitive types like int etc but they are not in the globals dict from what I can tell.


I can combine my answer with the other answer above to handle primitives:

def evaluate_type(name: str):
    t = globals().get(name)
    if t:
        return t
    else:
        try:
            t = getattr(__builtins__, name)
            if isinstance(t, type):
                return t
            else:
                raise ValueError(name)
        except:
            raise ValueError(name)


evaluate_type("int")

evaluate_type("Hey")

Upvotes: 2

dannymac
dannymac

Reputation: 1741

I like using locate, which works on built-in types:

>>> from pydoc import locate
>>> locate('int')
<type 'int'>
>>> t = locate('int')
>>> t('1')
1

...as well as anything it can find in the path:

>>> locate('datetime.date')
<type 'datetime.date'>
>>> d = locate('datetime.date')
>>> d(2015, 4, 23)
datetime.date(2015, 4, 23)

...including your custom types:

>>> locate('mypackage.model.base.BaseModel')
<class 'mypackage.model.base.BaseModel'>
>>> m = locate('mypackage.model.base.BaseModel')
>>> m()
<mypackage.model.base.BaseModel object at 0x1099f6c10>

Upvotes: 73

bukzor
bukzor

Reputation: 38462

You're a bit confused on what you're trying to do. Types, also known as classes, are objects, like everything else in python. When you write int in your programs, you're referencing a global variable called int which happens to be a class. What you're trying to do is not "cast string to type", it's accessing builtin variables by name.

Once you understand that, the solution is easy to see:

def get_builtin(name):
    return getattr(__builtins__, name)

If you really wanted to turn a type name into a type object, here's how you'd do it. I use deque to do a breadth-first tree traversal without recursion.

def gettype(name):
    from collections import deque
    # q is short for "queue", here
    q = deque([object])
    while q:
        t = q.popleft()
        if t.__name__ == name:
            return t
        else:
            print 'not', t

        try:
            # Keep looking!
            q.extend(t.__subclasses__())
        except TypeError:
            # type.__subclasses__ needs an argument, for whatever reason.
            if t is type:
                continue
            else:
                raise
    else:
        raise ValueError('No such type: %r' % name)

Upvotes: 16

Hugh Bothwell
Hugh Bothwell

Reputation: 56634

Why not just use a look-up table?

known_types = {
    'int': int,
    'float': float,
    'str': str
    # etc
}

var_type = known_types['int']

Upvotes: 13

Janne Karila
Janne Karila

Reputation: 25197

Perhaps this is what you want, it looks into builtin types only:

def gettype(name):
    t = getattr(__builtins__, name)
    if isinstance(t, type):
        return t
    raise ValueError(name)

Upvotes: 7

Related Questions