2013-07-01 22 views
0

我写了一个解析器,它采用JSON配置并从中创建对象。我首先创建一个众所周知的对象,并尝试动态地导入一个模块(可能来自一个用户),同时通过该模块的已定义creator方法加载它的类。奇怪的python 2.7字典行为:密钥没有在里面,而明显有

下面是一些测试代码:

import json 
import imp 
import os.path as path 
from lib.config.members import Member 
from lib.tasks.task import Task 


class Parser(object): 

    def __init__(self): 
     self._loadedMods = {"tasks": {}} 

    def _load_module(self, clazz, modPart): 
     """ 
     imports and caches a module. 

     :param clazz: the filename of the module (i.e email, ping...) 
     :param modPart: the folder of the module. (i.e services, parsers...) 
     :return: the imported/cached module, or throws an error if it couldn't find it 
     """ 
     mods = self._loadedMods[modPart] 
     if clazz in mods: 
      return mods["class"] 
     else: 
      #mod = __import__(clazz) 

      p = path.join("lib", modPart, clazz + ".py") 
      mod = imp.load_source(clazz, p) 
      mods[clazz] = mod 
      return mod 

    def replace_with_import(self, objList, modPart, items_func, class_check): 
     """ 
     replaces configuration dicts with their objects by importing and creating it in the first step. 
     In the second step the original list of json config dicts gets replaced by the loaded objects 

     :param objList: the list of objects which is iterated on 
     :param modPart: the folder from the module (i.e tasks, parsers) 
     :param items_func: function to get a pointer on the list of json-config-objects to replace. Takes one argument and 
     should return a list of 
     :param class_check: currently unsupported 
     """ 
     for obj in objList: 
      repl = [] 
      items = items_func(obj) 
      for clazzItem in items: 
       try: 

        clazz = clazzItem["class"] 
        mod = self._load_module(clazz, modPart) 
        item = mod.create(clazzItem) 
        if class_check(item): 
         repl.append(item) 
        else: 
         print " ignoring class " + clazzItem["class"] + "! It does not pass the class check!" 

       except ImportError, err: 
        print "could not import " + clazz + ": " + str(clazzItem) + "! reason:" 
        print str(err) 
       except KeyError, k: 
        print "Key " + str(k) + " not in classItem " + str(clazzItem) 
       except Exception, e: 
        print "Error while replacing class (" + clazz + " :" + str(e) + ")" 

      del items[:] 
      items.extend(repl) 

    def _create_raw_Object(self, jsonDict, msgName, creator): 
     """ 
     creates an Main object from the configuration, but just parses raw data and hands it to the object 

     :param jsonDict: the configuration file part as dict 
     :param msgName: name of object for error message 
     :param creator: function pointer which is taking two arguments: identifier of the object and arguments. 
     :should return an object 
     :return: a list of objects returned by creator 
     """ 
     items = [] 
     for key, val in jsonDict.items(): 
      try: 
       item = creator(key, val) 
       items.append(item) 
      except Exception, e: 
       print "ignoring " + msgName + ": " + key + "! reason:" 
       print str(e) 
     return items 

jsonFile = ''' 
{ 
    "members":{ 
     "homer":{ 
      "name": "Homer Simpson", 
      "comment": "Security Inspector", 
      "tasks": [{"class":"email", "type": "donut", "args": {"rcpt": "[email protected]"}}, 
      {"class":"email", "type": "do", "args": {"rcpt": "[email protected]"}}] 
     } 
    } 
} 
''' 

jsonDict = json.loads(jsonFile) 

parser = Parser() 

creator = lambda name, values: Member(name, **values) 
members = parser._create_raw_Object(jsonDict["members"], "Members", creator) 

items_func = lambda member: member.get_tasks() 
class_check = lambda task: isinstance(task, Task) 
parser.replace_with_import(members, "tasks", items_func, class_check) 

for d in members: 
    print d.__dict__ 

正如你所看到的,会员可以有arbitary任务的列表,以及一个应该导入其class属性定义,但只要两个他们先后为类相同的值(这不应该打破JSON我们定义它的方式),我得到一个奇怪的KeyError

Key 'class' not in classItem {u'args': {u'rcpt': u'[email protected]'}, u'type': u'do', u'class': u'email'} 

为什么会出现这种奇怪的错误?任何暗示,我给我一个线索,发生什么事情是非常受欢迎的,因为我感到绝望,调试了几个小时。

我认为会员和电子邮件/ Task类是无关的,但生病后他们的完整性:

的lib /配置/ members.py

class Member: 
    def __init__(self, id, name="", comment="", tasks=None): 
     self.id = id 
     self.name = name 
     self.tasks = [] 
     self.add_task(tasks) 
     self.comment = comment 

    def get_id(self): 
     return self.id 

    def add_task(self, task): 
     if task is None: 
      return 
     if isinstance(task, list): 
      self.tasks.extend(task) 
     else: 
      self.tasks.append(task) 

    def get_tasks(self): 
     return self.tasks 

的lib /任务/ [任务|邮件。 PY

class Task: 
    """ 
    Base class for all built-in Tasks. 
    """ 

    def set_task_type(self, taskType): 
     """ 
     sets the type of this task. 

     Be aware! this method can only get called once! 

     :param taskType: the type of this task 
     """ 
     if hasattr(self, "_taskType"): 
      raise Exception("taskType is only allowed to set once!") 
     self.taskType = taskType 

    def get_task_type(self): 
     """ 
     :return: the type set by set_type_task 
     """ 
     return self._taskType 

""" 
The email task. 
""" 

from lib.tasks.task import Task 


class EmailTask(Task): 
    def __init__(self, **kwargs): 
     self.set_task_type(kwargs["type"]) 
     self.recipient = kwargs["args"]["rcpt"] 

    def execute_task(self, msg): 
     pass 

def create(taskDict): 
    return EmailTask(**taskDict) 
+0

尝试将密钥转换为'unicode':'key = key.decode('utf-8')' –

+2

我会尝试打印出原始异常,而不是自己的'print'。只是为了确保你没有收到与你想象的不同的例外情况。 – Wessie

+0

@Wessie,你带来了启发!它是'_load_module'这个方法在'mod如果clazz'抛出: return mods [“class”]':)发布这个答案,我会接受 –

回答

1

看来你是在replace_with_import与自己的自定义print替换它吃了实际的异常。正如我在评论部分所述。

您通常希望让您的try块体小巧且非常可预测,准确了解可以提出的内容以及代码中当前应该处理的内容。您的try区块的复杂程度越低越好。