Python中是否可以自定义字符串字面量前缀?

14

假设我有一个从 str 继承并实现/覆盖了一些方法的自定义类:

class mystr(str):
    # just an example for a custom method:
    def something(self):
        return "anything"

目前我必须手动创建mystr实例,通过在构造函数中传递一个字符串:

ms1 = mystr("my string")

s = "another string"
ms2 = mystr(s)

这还不错,但它引发了这样一个想法:使用类似于b'字节字符串'r'原始字符串'u'Unicode字符串'的自定义字符串前缀会很酷。

在Python中是否有可能创建/注册这样的自定义字符串字面量前缀,例如 m,以便文字量m'我的字符串'会导致创建mystr的新实例?或者这些前缀是硬编码到Python解释器中了吗?


很遗憾,不行,除非修改解释器并编译自己的代码。有趣的是,这是ECMAScript未来可以做到的事情 - Ilja Everilä
3个回答

25

这些前缀在解释器中是硬编码的,您无法注册更多前缀。


不过,您可以通过使用自定义源代码编解码器预处理Python文件。这是一种相当巧妙的技巧,需要您注册自定义编解码器并理解和应用源代码转换。

Python允许您在顶部使用特殊注释指定源代码的编码:

# coding: utf-8

在 Python 中,使用 # coding: utf-8 声明源代码采用 UTF-8 编码,并在解析之前进行相应的解码。Python 在 codecs 模块注册表中查找相应的编解码器。您也可以注册自己的编解码器。

pyxl 项目 使用这种技巧来从 Python 文件中解析出 HTML 语法,将其替换为实际的 Python 语法以构建该 HTML,所有操作都在“解码”步骤中完成。请参见该项目中的codec,其中register 模块注册了一个自定义 codec 搜索函数,在 Python 解析和编译源代码之前对其进行变换。在您的 site-packages 目录下安装了一个自定义 .pth 文件,以在 Python 启动时加载此注册步骤。另一个执行类似操作以解析出 Ruby 风格字符串格式的项目是interpy

那么,您只需要构建这样一个编解码器,它会解析 Python 源文件(可能使用 tokenize 模块 进行令牌化),并使用自定义前缀替换字符串字面值,生成 mystr(<string literal>) 调用。您要解析的任何文件都标记为 # coding: yourcustomcodec

我将把这部分留给读者自己练习。祝您好运!

请注意,此转换的结果编译为字节码,并缓存;每个源代码修订版本只需运行一次您的变换,使用您的编解码器导入模块的所有其他模块都将加载缓存的字节码。


1
哇,我不确定那是一个好主意 - 但黑客因素很棒。 - Christian Sauer
嘿,这是一个很酷的方法。肯定不是我们想要在生产代码中使用的,但肯定很有趣。我只是担心简单地编写 mystr("my string") 更容易... ;) 我会坚持使用它。谢谢! - Byte Commander
@ByteCommander:我非常确定Dropbox在生产中使用pyxl编解码器。注册编解码器非常轻量级,解码-标记化-转换步骤只需要在编译时进行。结果被编译为字节码并缓存。这一步骤直到源代码更改,使字节码缓存失效才会重复执行。 - Martijn Pieters
是的,当然。我的意思是,在我这种简单情况下,只需要一个自定义字面量,它可以简单地替换构造函数调用,但这有点复杂。这是一些人可能会发现非常有用的功能,但对于其他人来说,它只会使代码更加难懂。这取决于“编解码器”改变了多少。太多会让它变得混乱,太少则没有必要。 - Byte Commander

11

可以使用运算符重载将str隐式转换为自定义类

class MyString(str):
    def __or__( self, a ):
        return MyString(self + a)

m = MyString('')
print( m, type(m) )
#('', <class 'MyString'>)
print m|'a', type(m|'a')
#('a', <class 'MyString'>)

这样做有效地避免了使用括号并模拟了一个具有一个额外字符前缀的字符串 - 我选择的是|,但也可以是&或其他二进制比较运算符。

1

虽然上述解决方法非常好,但它们可能是危险的。黑客攻击您的Python真的不是一个好主意。虽然您无法以其他方式进行前缀,但可以执行以下操作:

class MyString(str):
    def something(self):
        return MyString("anything")

m = MyString

# The you can do:
m("hi")
# Rather than:
# m"hi"

这可能是您能找到的最好的安全解决方案。 输入两个括号并不需要太多打字,而且对于您的代码读者来说也不会造成太多困惑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接