【问题标题】:How to handle attribute access errors in Jinja2?如何处理 Jinja2 中的属性访问错误?
【发布时间】:2015-03-16 17:49:29
【问题描述】:

我有这个:

template = '{{invoice.customer.address.city}}'

而且效果很好。但有时 invoice.customer is Null or invoice.customer.address is Null 然后 jinja 抛出 jinja2.exceptions.UndefinedError: 'None' has no attribute 'address' 因为它无法到达 .city 部分。那么,如果它无法访问某个属性,我该如何告诉它静默失败呢?

谢谢!

【问题讨论】:

    标签: python python-3.x jinja2


    【解决方案1】:

    如果您经常这样做,而不是创建每个属性 过滤器,您可以概括 Vor 的答案以适用于任意嵌套 字典,像这样:

    import jinja2
    
    def filter_nested_dict(value, default, path):
        keys = path.split('.')
        for key in keys:
            try:
                value = value[key]
            except KeyError:
                return default
    
        return value
    
    
    env = jinja2.Environment()
    env.filters['nested_dict'] = filter_nested_dict
    
    template = env.from_string('''
      City: {{invoice|nested_dict('<none>', 'customer.address.city')}}''')
    

    鉴于上述情况,这是:

    print template.render(invoice={})
    

    给你:

    City: <none>
    

    还有这个:

    print template.render(invoice={'customer': {'address': {'city': 'boston'}}})
    

    给你:

    City: boston
    

    【讨论】:

    • 是的,这好多了,但还是太麻烦了。 {{invoice|nested_dict('', 'customer.address.city')}}''') 立即禁止使用 - 模板将变得不可读。
    • 我承认我认为这与您提出的方案在可读性上没有太大区别,但我很高兴您找到了适合您的解决方案。
    • 你是对的,我的解决方案没有你的解决方案那么可读,但它还有另一个优点 - 它可以处理可调用对象。有论据。这是一件好事:)
    【解决方案2】:

    我建议您创建一个自定义过滤器并将整个 invoice 对象传递给它,而不是尝试在 Jinja 中找到解决方法。

    例如:

    import jinja2 
    
    
    def get_city_from_invoice(invoice):
      try:
          return invoice['customer']['address']['city']
      except KeyError:
          return None
    
    env = jinja2.Environment()
    env.filters['get_city_from_invoice'] = get_city_from_invoice
    
    d = {'invoice': {'customer': {'address': {'city': 'foo'}}}}
    d1 = {'invoice': {'no-customers': 1 }}
    
    print "d: ", env.from_string('{{ invoice | get_city_from_invoice }}').render(d)
    print "d1: ", env.from_string('{{ invoice | get_city_from_invoice }}').render(d1)
    

    将打印:

    d:  foo
    d1:  None
    

    【讨论】:

    • 您打算为每个案例编写一个自定义过滤器?一个简单的发票模板需要十几个!
    【解决方案3】:

    好的,我想我明白了。答案似乎是使用全局变量,就像here 中描述的那样

    所以我尝试以此为基础,结果是这样的:

    def jinja_global_eval(c, expr):
        """Evaluates an expression. Param c is data context"""
        try:
            return str(eval(expr))
        except:
            return ''
    

    使用templating_env.globals['eval'] = jinja_global_eval 将它安装到我的模板环境后,我现在可以在我的模板中执行此操作:

    {{eval(invoice, 'c.customer.address.city')}}
    

    还有这个:

    {{eval(invoice, 'c.customer.get_current_balance()')}}
    

    在调试过程中它可能会咬我的裤子,但为了避免这种情况,可以在jinja_global_eval 中安装一个简单的日志记录。无论如何,感谢所有试图提供帮助的人。

    【讨论】:

      【解决方案4】:

      它需要进一步测试,因为它可能会破坏某些东西,但是如何扩展 Environment 类并像这样覆盖 gettatr(或 getitem)方法

      from jinja2 import Environment
      
      class SEnvironment(Environment):
          ERROR_STRING = 'my_error_string'
          def getattr(self, obj, attribute):
              """Get an item or attribute of an object but prefer the attribute.
                      Unlike :meth:`getitem` the attribute *must* be a bytestring.
                      """
              try:
                  return getattr(obj, attribute)
              except AttributeError:
                  pass
              try:
                  return obj[attribute]
              except (TypeError, LookupError, AttributeError):
                  return SEnvironment.ERROR_STRING # this lines changes
      

      如果你想处理错误,你可以创建像raise_errordislay_error这样的过滤器

      def raise_error(obj):
          if obj == SEnvironment.ERROR_STRING:
              raise Exception('an error occured')
          return obj
              
      
      def print_error(obj, _str='other error'):
          if obj == SEnvironment.ERROR_STRING:
              return _str
          return obj
      
      jinja_env = SEnvironment()
      jinja_env.filters['raise_error'] = raise_error
      jinja_env.filters['print_error'] = print_error
      jinja_env = jinja_env.from_string("""{{ test1.test2.test3 }}""") # -> my_error_string
      #jinja_env = jinja_env.from_string("""{{ test1.test2.test3|print_error('<none>') }}""") # -> <none>
      #jinja_env = jinja_env.from_string("""{{ test1.test2.test3|raise_error }}""") # -> Exception: an error occured
      res = jinja_env.render({
          'test1': {
              'test2': None
          }
      })
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-03
        • 2021-11-22
        • 1970-01-01
        • 1970-01-01
        • 2014-10-29
        相关资源
        最近更新 更多