Lexical cast from string to type
Asked Answered
J

5

48

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?

Johnettajohnette answered 2/8, 2012 at 10:17 Comment(6)
Perhaps you should use a different format than text to store your information? Python has many data persistence options available.Staphylococcus
Adding to the above, you could store your data by pickling it. docs.python.org/library/pickle.htmlBadly
One possibility is to use predefined dictionary with strings as keys and types as values. Although I recommend to refactor the code. Dynamic script loading ( which is basically what you do ) is always a problem.Demmer
@Codemonkey: pickle is listed on the page I link to, first option. :-)Staphylococcus
@MartijnPieters: I assumed as much, but I was already typing the comment and I'm lazy :-PBadly
@MartijnPieters I would like to do so, but I have to adapt to an already existing Xml format actually; moreover, it's a requirement that files must be readable and modifiable by humans. Otherwise, it would have done so.Johnettajohnette
H
71

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>
Happygolucky answered 23/4, 2015 at 18:29 Comment(2)
Hum, that's an interesting one, even though it only works with fully-qualified names (which isn't a problem per se). It seems poorly documented though. Do you have any link providing more information about it?Johnettajohnette
Wow, it is poorly documented! The closest link I could find was this: epydoc.sourceforge.net/stdlib/pydoc-module.html. I recommend just looking at the source instead.Happygolucky
S
15

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)
Shirashirah answered 2/8, 2012 at 16:19 Comment(2)
I'm not confused at all about what I'm trying to do, I was just looking for a fast standard way to do a lexical_cast (name of C++ Boost function for that kind of routines) between a string and a type. I didn't know about the module __builtin__ though since I hardly ever use the reflective features of Python.Johnettajohnette
The closest thing you can get to casting string to type in python is to find the class by name. __builtin__ is not involved in that case. If what you want is to get builtin variables by name, then getattr(__builtin__) is it. In any case, your question doesn't match the chosen answer.Shirashirah
G
13

Why not just use a look-up table?

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

var_type = known_types['int']
Glabrescent answered 2/8, 2012 at 15:58 Comment(1)
Imho that's the proper way to go for user defined types. One can put the lookup table as a static var into a class, add a method from_dict(self, d) and/or from_name(self, typename) and thus define a factory class. This can easily be extended/amended/overriden by sublasses.Nez
M
5

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)
Modernistic answered 2/8, 2012 at 11:4 Comment(3)
It seems to work, except it returns something else than the type if you give for example the name of a built-in function, say next. Too bad there isn't a dedicated method to do the cast in the standard library. Thanks!Johnettajohnette
@Morwenn: a completely general solution would be useless, since even if you could get the type, you still need to know per type how to construct an instance using it. E.g. dict("{}") raises an exception.Valuate
@Morwenn: Please note that next is not the name of a type, although it is a builtin variable. It is a function, and has a type of builtin_function_or_method.Shirashirah
I
2

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")
Ingram answered 15/3, 2023 at 16:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.