【问题标题】:Implement f-string like magic to pimp Django's format_html()像魔术一样实现 f-string 来拉皮条 Django 的 format_html()
【发布时间】:2021-06-24 15:20:05
【问题描述】:

我想拉皮条 format_html() 的 Django。

它已经很好地工作了,但是我的 IDE (PyCharm) 认为这些变量没有被使用,并将它们涂成浅灰色:

AFAIK f-strings 使用了一些神奇的重写。

有没有办法实现这一点,让 IDE 知道变量被使用了?

相关:Implement f-string like syntax, with Django SafeString support

这是我当前的实现:

def h(html):
    """
    Django's format_html() on steroids
    """
    def replacer(match):
        call_frame = sys._getframe(3)
        return conditional_escape(
            eval(match.group(1), call_frame.f_globals, call_frame.f_locals))
    return mark_safe(re.sub(r'{(.*?)}', replacer, html))

有人提出了安全问题:我不打算创建用户可以编辑这些模板的 CMS。这些模板 h-strings 仅供开发人员方便地创建 HTML。

在写答案之前,请确保您了解conditional_escape() 的魔力

【问题讨论】:

  • 那些电话看起来不对 - 你不应该这样做format_html('<span>{foo}</span>', foo=foo)吗?
  • 这不是“魔法”——f-strings 是一种实际的语言功能,因此 JetBrains 的开发人员在 IDE 中实现了它的逻辑(请参阅youtrack.jetbrains.com/issue/PY-18972 和一系列其他相关问题给它)。我猜想,去寻找任何可能在给定范围内引用变量以将其标记为未使用的框架黑客的使用,这将被认为超出了他们的责任。
  • 我相信您的选择是: 1. 禁用未使用的局部变量检查。 2. 为 PyCharm 编写一个插件,当你使用你的函数时抑制这个检查。 (可能通过添加一个特殊的注释并使用com.intellij.lang.inspectionSuppressor 扩展点来实现) 3. 编写一个插件,找出模板中使用的内容,并仅将这些变量标记为已使用。 4. 传递locals() 作为第二个参数,如h('{a}', locals()) 以在这种情况下使用PyCharm 异常。对嵌套函数和globals() 没有帮助
  • 我用 VSCode 尝试过这个,突出显示效果如你所料。不知道为什么 PyCharm 会有所不同
  • 例如,string.Formatter().parse 可以为您解析格式模板并正确处理转义结构,例如{{

标签: python django pycharm f-string


【解决方案1】:

由于您似乎不喜欢使用肮脏的黑客,所以这里有一个比问题中的更肮脏的黑客:

class _escaper(dict):
    def __init__(self, other):
        super().__init__(other)
        
    def __getitem__(self, name):
        return conditional_escape(super().__getitem__(name))

_C = lambda value: (lambda: value).__closure__[0]
_F = type(_C)
try:
    type(_C(None))(None)
except:
    pass
else:
    _C = type(_C(None))

def h(f):
    if not isinstance(f, _F):
        raise TypeError(f"expected a closure, a {type(f).__name__} was given")
    closure = None
    if f.__closure__:
        closure = tuple(
            _C(conditional_escape(cell.cell_contents))
            for cell in f.__closure__
        )
    fp = _F(
        f.__code__, _escaper(f.__globals__),
        f.__name__, f.__defaults__, closure
    )
    return mark_safe(fp())

h 函数采用闭包,对于每个关闭的变量,它会创建另一个变量的转义副本,并修改闭包以捕获该副本。 globals dict 也被包装以确保对 globals 的引用同样被转义。然后立即执行修改后的闭包,其返回值被标记为安全并返回。因此,您必须传递h 一个不接受任何参数的普通函数(例如,没有绑定方法),最好是一个返回 f 字符串的 lambda。

你的例子就变成了:

foo = '&'
bar = h(lambda: f'<span>{foo}</span>')
assert h(lambda: f'<div>{bar}</div>') == '<div><span>&amp;</span></div>'

不过,有一件事。由于这是如何实现的,您只能直接引用插值点内的变量;没有属性访问,没有项目查找,没有别的。 (您也不应该将字符串文字放在插值点。)否则,它可以在 CPython 3.9.2 甚至在 PyPy 7.3.3 中使用。我没有声称它曾经在任何其他环境中工作,或者以任何方式面向未来。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-24
    • 2012-03-24
    • 2011-07-06
    • 2015-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多