在运行时通过 String 从 python 访问模块和模块变量

Access modules and module variables at runtime from python via String

在我的 Python 程序中,我想动态加载模块并通过将字符串参数转换为变量名来访问模块的变量。

用例

我在 SD 卡上有不同的字体,它们是 python 个文件,还有一个显示功能,可以在需要显示字符时加载字体。

我的字体文件示例:

# arial14.py
# ch_(ASCII) = (widht), (height), [bitmask]
ch_33 = 3, 16, [0,0,0,0,0,0,0,0,0,1,1,1,1,1 ........
ch_34 = 5, 16, [0,0,0,0,0,0,0,0,0,0,0,0,0,0 ........
....

# arial20.py
ch_33 = 4, 22, [0,0,0,0,0,0,0,0,0,1,1,1,1,1 ........
ch_34 = 8, 22, [0,0,0,0,0,0,0,0,0,0,0,0,0,0 ........

此外,还有一个 Writer class 将字体渲染到显示器:

class Writer(object):
    def __init__(self):
       try:
           global arial14
           import arial14
           self.current_module = "arial14"
           self.imported_fonts = []
           self.imported_fonds.append(self.current_module)
      except ImportError:
           print("Error loading Font")
   def setfont(self, fontname, fontsize):
           modname = fontname+str(fontsize)
           if modname not in self.importedfonts:
               try:
                    exec("global" + modname)
                    exec("import" + modname)      #import the font (works)
                    self.importedfonts.append(modname)
               except ImportError:
                    print("Error loading Font")
                    return
           else:
               print(modname+" was already imported")
           self.current_module = modname
           print("Font is set now to: "+ self.current_module

## HERE COMES THE NON WORKING PART:
    def putc_ascii(self, ch, xpos, ypos):

           execline = "width, height, bitmap = "+ self.cur_mod+".ch_"+str(ch)
           print(execline) 
           #this example.: width, height,bitmap = arial14.ch_55


           width, height,bitmap = arial14.ch_32
           print(width, height, bitmap) # values of arial14.ch_32 -> correct

           exec (execline)
           print(width, height, bitmap) # values of arial14.ch_32
                                        # BUT VALUES OF arial14.ch_55 EXPECTED

有没有人知道我怎样才能将正确字体的查询字符的正确值保存到变量宽度、高度和位图中?

我只想在需要时动态加载字体,并提供通过将新的 .py 字体文件放入文件夹来添加新字体的可能性。

提前致谢。

编辑

OP 实际上使用的是 micropython,它没有实现 importlib...

可能的(未经测试的)解决方案(是的,使用 exec - 如果有人知道更好的解决方案,请加入)。

def import_module(name):
    name = name.strip().split(
    statement = "import {}"
    exec(statement, globals()) 
    return sys.modules[name]


class Writer(object):
    def __init__(self):
       # reference to the current module
       # WARNING : this is the `module` object itself, 
       # not it's name       
       self.current_module = None

       # {name:module} mapping
       # not sure it's still useful since we
       # now have a reference to the module itself
       # and `sys.modules` already caches imported 
       # modules... 
       self.imported_fonts = {}

       # set default font
       self.setfont("arial", "14")

    def setfont(self, fontname, fontsize):
        self.current_module = self._loadfont(fontname, fontsize)

    def _loadfont(self, fontname, fontsize):
        name = fontname+str(fontsize)
        if name not in self.imported_fonts:
            self.imported_fonts[name] = load_module(name)
        return self.imported_font[name]

    def putc_ascii(self, ch, xpos, ypos):
        width, height, bitmap = self._get_char(ch)
        print("{}.{}: {} - {} - {}".format(
            self.current_module, ch, width, height, bitmap
            )

    def _get_char(self, chrnum):
        # assume the font modules have been rewritten
        # using dicts (cf lower) and all chars are defined
        # in all fonts modules
        return self.current_module[ch]

        # alternate implementation using the current
        # fonts definitions
        # return getattr(self.current_module, "ch_{}".format(ch)) 

TL:DR :

你想要 importlib.import_module,最终 getattr()。但是你还是应该阅读更长的答案,真的,它会为你节省很多时间和挫败感。

更长的答案

关于您的 "font" 文件格式的第一点 - 这个:

ch_33 = 3, 16, [0,0,0,0,0,0,0,0,0,1,1,1,1,1 ........
ch_34 = 5, 16, [0,0,0,0,0,0,0,0,0,0,0,0,0,0 ........

是一种巨大的设计味道。您需要列表或字典(或者可能是有序的字典),即:

characters = {
    33: (3, 16,  [0,0,0,0,0,0,0,0,0,1,1,1,1,1...]),
    34: (5, 16,  [0,0,0,0,0,0,0,0,0,0,0,0,0,0...]),
    # etc
    }

作为一般规则,当您开始拥有一些 "var1"、"var2"、"var3" 等模式时,您就知道您需要某种容器。

第二点——你的错误处理,即:

 try:
       global arial14
       import arial14
       self.current_module = "arial14"
       self.imported_fonts = []
       self.imported_fonds.append(self.current_module)
  except ImportError:
       print("Error loading Font")

比没用还糟,实际上是有害的。首先是因为它不会在发生不可恢复的错误时停止程序执行(你希望你的程序在发生完全错误的情况下继续运行),然后因为它替换了所有非常有用的信息您要么从错误消息中获取信息,要么使用无用的 "An error happened" 消息进行回溯。

只捕获您可以通过某种方式正确管理的异常,让其他所有内容传播(也许调用堆栈上层的某个人 - 通常在 UI 部分 - 可能能够正确处理它)。

第三点:不要使用全局变量。我的意思是:不要改变或重新绑定全局变量(read-only(pseudo-constant)全局变量当然可以)。这意味着您真的不必在代码中使用 "global" 关键字。

当您需要在一组函数之间共享状态时,请使用 class,将状态存储为属性并创建您的函数方法。这就是对象的用途(好吧,不仅如此,而且这是存在理由的一部分)。

编辑这部分对于完整的Python实现仍然适用,对于micropython可能仍然部分适用,除了 对于未在 micropython 中实现的部分(如 importlib - 我不知道还缺少什么)

最后:永远不要使用 execeval你不需要它们,无论你想做什么,都有更好、更安全的特定解决方案。

在你的情况下,要按名称导入模块,你有 importlib.import_module, and to get an object's attribute by it's name you have getattr()(但如果你使用合适的容器,请参见第一点,你甚至不需要 getattr) .