可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Given a string as user input to a python function, I'd like to get a class object out of it if there's a class with that name in the currently defined namespace. Essentially, I want the implementation for a function which will produce this kind of result:
class Foo: pass str_to_class("Foo") ==>
Is this possible?
回答1:
This seems simplest.
>>> class Foo(object): ... pass ... >>> eval("Foo")
回答2:
This could work:
import sys def str_to_class(str): return getattr(sys.modules[__name__], str)
回答3:
You could do something like:
globals()[class_name]
回答4:
You want the class "Baz", which lives in module "foo.bar". With python 2.7, you want to use importlib.import_module(), as this will make transitioning to python 3 easier:
import importlib def class_for_name(module_name, class_name): # load the module, will raise ImportError if module cannot be loaded m = importlib.import_module(module_name) # get the class, will raise AttributeError if class cannot be found c = getattr(m, class_name) return c
With python
def class_for_name(module_name, class_name): # load the module, will raise ImportError if module cannot be loaded m = __import__(module_name, globals(), locals(), class_name) # get the class, will raise AttributeError if class cannot be found c = getattr(m, class_name) return c
Use:
loaded_class = class_for_name('foo.bar', 'Baz')
回答5:
import sys import types def str_to_class(field): try: identifier = getattr(sys.modules[__name__], field) except AttributeError: raise NameError("%s doesn't exist." % field) if isinstance(identifier, (types.ClassType, types.TypeType)): return identifier raise TypeError("%s is not a class." % field)
This accurately handles both old-style and new-style classes.
回答6:
I've looked at how django handles this
django.utils.module_loading has this
def import_string(dotted_path): """ Import a dotted module path and return the attribute/class designated by the last name in the path. Raise ImportError if the import failed. """ try: module_path, class_name = dotted_path.rsplit('.', 1) except ValueError: msg = "%s doesn't look like a module path" % dotted_path six.reraise(ImportError, ImportError(msg), sys.exc_info()[2]) module = import_module(module_path) try: return getattr(module, class_name) except AttributeError: msg = 'Module "%s" does not define a "%s" attribute/class' % ( module_path, class_name) six.reraise(ImportError, ImportError(msg), sys.exc_info()[2])
You can use it like import_string("module_path.to.all.the.way.to.your_class")
回答7:
Yes, you can do this. Assuming your classes exist in the global namespace, something like this will do it:
import types class Foo: pass def str_to_class(s): if s in globals() and isinstance(globals()[s], types.ClassType): return globals()[s] return None str_to_class('Foo') ==>
回答8:
In terms of arbitrary code execution, or undesired user passed names, you could have a list of acceptable function/class names, and if the input matches one in the list, it is eval'd.
PS: I know....kinda late....but it's for anyone else who stumbles across this in the future.
回答9:
Using importlib worked the best for me.
import importlib importlib.import_module('accounting.views')
This uses string dot notation for the python module that you want to import.
回答10:
If you really want to retrieve classes you make with a string, you should store (or properly worded, reference) them in a dictionary. After all, that'll also allow to name your classes in a higher level and avoid exposing unwanted classes.
Example, from a game where actor classes are defined in Python and you want to avoid other general classes to be reached by user input.
Another approach (like in the example below) would to make an entire new class, that holds the dict above. This would:
- Allow multiple class holders to be made for easier organization (like, one for actor classes and another for types of sound);
- Make modifications to both the holder and the classes being held easier;
- And you can use class methods to add classes to the dict. (Although the abstraction below isn't really necessary, it is merely for... "illustration").
Example:
class ClassHolder(object): def __init__(self): self.classes = {} def add_class(self, c): self.classes[c.__name__] = c def __getitem__(self, n): return self.classes[n] class Foo(object): def __init__(self): self.a = 0 def bar(self): return self.a + 1 class Spam(Foo): def __init__(self): self.a = 2 def bar(self): return self.a + 4 class SomethingDifferent(object): def __init__(self): self.a = "Hello" def add_world(self): self.a += " World" def add_word(self, w): self.a += " " + w def finish(self): self.a += "!" return self.a aclasses = ClassHolder() dclasses = ClassHolder() aclasses.add_class(Foo) aclasses.add_class(Spam) dclasses.add_class(SomethingDifferent) print aclasses print dclasses print "=======" print "o" print aclasses["Foo"] print aclasses["Spam"] print "o" print dclasses["SomethingDifferent"] print "=======" g = dclasses["SomethingDifferent"]() g.add_world() print g.finish() print "=======" s = [] s.append(aclasses["Foo"]()) s.append(aclasses["Spam"]()) for a in s: print a.a print a.bar() print "--" print "Done experiment!"
This returns me:
<__main__.classholder object="" at=""><__main__.classholder object="" at=""> ======= o o ======= Hello World! ======= 0 1 -- 2 6 -- Done experiment!
Another fun experiment to do with those is to add a method that pickles the ClassHolder so you never lose all the classes you did :^)