it-swarm.dev

为什么现代Perl默认会避免使用UTF-8?

我想知道为什么使用Perl构建的大多数现代解决方案都没有启用 UTF-8 默认情况下。

我知道核心Perl脚本存在许多遗留问题,可能会破坏它们。但是,从我的角度来看,在21日st 世纪,大型新项目(或具有大视角的项目)应该使他们的软件UTF-8从头开始证明。我仍然没有看到它发生。例如, Moose 启用严格和警告,但不启用 UnicodeModern :: Perl 也减少了样板,但没有UTF-8处理。

为什么?是否有一些理由在2011年的现代Perl项目中避免使用UTF-8?


评论@tchrist太长了,所以我在这里添加它。

似乎我没有说清楚。让我尝试添加一些东西。

tchrist 我看到情况非常类似,但我们的结论完全是相反的。我同意,Unicode的情况很复杂,但这就是为什么我们(Perl用户和编码人员)需要一些层(或编译指示),这使得UTF-8处理变得像现在一样容易。

tchrist 指出要涵盖的许多方面,我会阅读并思考它们几天甚至几周。不过,这不是我的观点。 tchrist 试图证明没有一种方法“启用UTF-8”。我没有太多的知识可以与之争辩。所以,我坚持住实例。

我和 Rakudo一起玩 和UTF-8就在那里 因为我需要 。我没有任何问题,它只是奏效了。也许在某些地方存在一些限制,但一开始,我测试的所有工作都按照我的预期进行。

这不应该是现代Perl 5的目标吗?我更强调它:我不是建议将UTF-8作为核心Perl的默认字符集,我建议可以触发它 使用snap 对于那些开发 new projects的人。

另一个例子,但更负面的语气。框架应该使开发更容易。几年前,我尝试过Web框架,但只是把它们扔掉了,因为“启用UTF-8”是如此模糊。我没有找到如何以及在何处挂钩Unicode支持。这是非常耗时的,我发现它更容易走老路。现在我在这里看到有一个赏金来处理同样的问题 Mason 2:如何让Mason2 UTF-8干净?。因此,它是一个非常新的框架,但使用UTF-8需要深入了解其内部。这就像一个大红色标志:停止,不要使用我!

我真的很喜欢Perl。但处理Unicode是痛苦的。我仍然发现自己在墙上奔跑。某种方式 tchrist 是正确的并回答我的问题:新项目不会吸引UTF-8,因为它在Perl 5中太复杂了。

551
w.k

处理Unicode文本有两个阶段。第一个是“如何输入并输出它而不丢失信息”。第二个是“我如何根据当地语言惯例处理文本”。

tchrist的帖子涵盖了两者,但第二部分是他的帖子中99%的文本来自。大多数程序甚至不能正确处理I/O,因此在您开始担心规范化和整理之前理解这一点非常重要。

这篇文章旨在解决第一个问题

当您将数据读入Perl时,它并不关心它是什么编码。它分配一些内存并将字节存放在那里。如果你说print $str,它只是将那些字节闪存到你的终端,这可能设置为假设写入它的所有内容都是UTF-8,并且你的文本显示出来。

奇妙。

除此之外,事实并非如此。如果您尝试将数据视为文本,您会看到发生了一些不好的事情。你只需要length就可以看到Perl对你的字符串的看法,以及你对你的字符串不一致的看法。写一个单行,如:Perl -E 'while(<>){ chomp; say length }'并输入文字化け,你得到12 ...不正确答案,4。

那是因为Perl认为你的字符串不是文本。你必须告诉它它之前的文本会给你正确的答案。

这很容易; Encode模块具有执行此操作的功能。通用入口点是Encode::decode(当然还是use Encode qw(decode))。该函数从外部世界获取一些字符串(我们称之为“八位字节”,一种说“8位字节”的方式),并将其转换为Perl将理解的一些文本。第一个参数是字符编码名称,如“UTF-8”或“ASCII”或“EUC-JP”。第二个参数是字符串。返回值是包含文本的Perl标量。

(还有Encode::decode_utf8,它假设编码为UTF-8。)

如果我们重写我们的单行:

Perl -MEncode=decode -E 'while(<>){ chomp; say length decode("UTF-8", $_) }'

我们输入文字化け并得到“4”作为结果。成功。

那就是Perl中99%的Unicode问题的解决方案。

关键是,无论何时任何文本进入您的程序,您都必须对其进行解码。 Internet无法传输字符。文件无法存储字符。您的数据库中没有字符。只有八位字节,你不能将八位字节视为Perl中的字符。您必须使用Encode模块将编码的八位字节解码为Perl字符。

问题的另一半是从程序中获取数据。这很容易;你只需要说use Encode qw(encode),决定你的数据编码是什么(UTF-8到了解UTF-8的终端,UTF-16用于Windows上的文件等),然后输出encode($encoding, $data)的结果而不是输出$data

此操作将Perl的字符转换为可由外部使用的八位字节,这是您的程序操作的字符。如果我们可以通过互联网或终端发送字符会容易得多,但我们不能:仅限八位字节。所以我们必须将字符转换为八位字节,否则结果是未定义的。

总结一下:编码所有输出并解码所有输入。

现在我们将讨论三个使这有点挑战的问题。首先是图书馆。他们是否正确处理文本?答案是......他们试试。如果您下载网页,LWP将以文本形式返回您的结果。如果你在结果上调用正确的方法,那就是(并且恰好是decoded_content,而不是content,它只是从服务器获得的八位字节流。)数据库驱动程序可能是不稳定的;如果你只使用带有Perl的DBD :: SQLite,它会解决,但如果其他一些工具将文本存储为数据库中除UTF-8之外的某些编码......那么...它将无法正确处理直到你编写代码来正确处理它。

输出数据通常更容易,但是如果你看到“打印中的宽字符”,那么你就知道你在某处弄乱了编码。这个警告意味着“嘿,你试图将Perl角色泄露给外界并且没有任何意义”。您的程序似乎正常工作(因为另一端通常正确处理原始Perl字符),但它非常破碎,可能随时停止工作。用明确的Encode::encode修复它!

第二个问题是UTF-8编码的源代码。除非你在每个文件的顶部说use utf8,否则Perl不会假设你的源代码是UTF-8。这意味着每次你说my $var = 'ほげ'之类的东西时,你都会在你的程序中注入垃圾,这会完全破坏一切。您不必“使用utf8”,但如果不这样做,您 必须 不要在程序中使用任何非ASCII字符。

第三个问题是Perl如何处理过去。很久以前,没有Unicode这样的东西,Perl认为一切都是Latin-1文本或二进制文件。因此,当数据进入您的程序并开始将其视为文本时,Perl会将每个八位字节视为Latin-1字符。这就是为什么当我们要求“文字化け”的长度时,我们得到了12. Perl假设我们使用的是Latin-1字符串“æååã”(这是12个字符,其中一些是非打印字符)。

这被称为“隐式升级”,这是一个非常合理的事情,但如果你的文字不是Latin-1,那就不是你想要的了。这就是明确解码输入的关键所在:如果你不这样做,Perl就会这样做,而且它可能会做错。

人们遇到麻烦,其中一半的数据是正确的字符串,有些仍然是二进制的。 Perl将解释仍然是二进制的部分,就好像它是Latin-1文本,然后将它与正确的字符数据结合起来。这将使它看起来像处理你的角色正确地破坏你的程序,但实际上,你只是没有足够的修复它。

这是一个例子:你有一个程序读取一个UTF-8编码的文本文件,你将Unicode PILE OF POO添加到每一行,然后打印出来。你写它像:

while(<>){
    chomp;
    say "$_ ????";
}

然后运行一些UTF-8编码数据,例如:

Perl poo.pl input-data.txt

它在每行末尾用便便打印UTF-8数据。完美,我的程序有效!

但不,你只是做二进制连接。您正在从文件中读取八位字节,使用chomp删除\n,然后添加PILE OF POO字符的UTF-8表示中的字节。当您修改程序以解码文件中的数据并对输出进行编码时,您会注意到垃圾(“ð©”)而不是poo。这将使您相信解码输入文件是错误的。不是。

问题是,poo被隐式升级为latin-1。如果你use utf8使文字文本而不是二进制文件,那么它将再次工作!

(这是我在帮助人们使用Unicode时遇到的头号问题。他们确实做了正确的事情而且打破了他们的计划。这对于未定义的结果感到遗憾:你可以长时间使用一个工作程序,但是当你开始修复它时,不要担心;如果你要将编码/解码语句添加到你的程序中它会中断,这只意味着你还有更多工作要做。下一次,当你从头开始设计Unicode时,它将是更容易!)

这就是你需要了解的关于Perl和Unicode的所有信息。如果您告诉Perl您的数据是什么,它在所有流行的编程语言中都具有最佳的Unicode支持。如果您认为它会神奇地知道您正在提供哪种文本,那么您将不可撤销地丢弃您的数据。仅仅因为您的程序今天在您的UTF-8终端上运行并不意味着它将在明天的UTF-16编码文件上运行。所以现在就把它安全吧,让自己省去丢弃用户数据的麻烦!

处理Unicode的简单部分是编码输出和解码输入。困难的部分是找到所有输入和输出,并确定它是什么编码。但这就是为什么你得到了大笔钱:)

95
jrockway

我们都同意这是一个困难的问题,原因很多,但这正是试图让每个人都更容易理解的原因。

CPAN上有一个最近的模块, utf8 :: all ,它试图“打开Unicode。所有这些”。

正如已经指出的那样,你不能神奇地使整个系统(外部程序,外部Web请求等)也使用Unicode,但我们可以共同制作合理的工具,使常见问题更容易。这就是我们是程序员的原因。

如果utf8 :: all没有做你认为应该做的事情,让我们改进它以使其更好。或者让我们一起制作额外的工具,以便尽可能满足人们的各种需求。

`

47
Randy Stauner

我认为你误解了Unicode及其与Perl的关系。无论您以何种方式存储数据,Unicode, ISO-8859-1 或许多其他内容,您的程序必须知道如何解释它作为输入(解码)获得的字节以及如何表示它想要的信息输出(编码)。得到错误的解释,你搞砸了数据。你的程序中没有一些神奇的默认设置可以告诉你程序之外的东西如何行动。

你认为很难,很可能,因为你习惯了所有的ASCII。您应该考虑的所有内容都被编程语言和它必须与之交互的所有内容所忽略。如果一切都只使用UTF-8而你别无选择,那么UTF-8也会如此简单。但并非所有东西都使用UTF-8。例如,你不希望你的输入句柄认为它正在获得UTF-8八位字节,除非它实际上是,并且如果从它们读取的东西可以处理UTF-8你不希望你的输出句柄是UTF-8 。 Perl无法知道这些事情。这就是你是程序员的原因。

我不认为Perl 5中的Unicode太复杂了。我认为这很可怕,人们会避免它。有区别。为此,我将Unicode放入其中 学习Perl,第6版,还有很多Unicode内容 有效的Perl编程。您必须花时间学习和理解Unicode及其工作原理。否则你无法有效地使用它。

34
brian d foy

在阅读这个主题时,我经常会觉得人们使用“ UTF-8 ”作为“ Unicode ”的同义词。请区分Unicode的“代码点”,它是ASCII代码的扩大相对代码和Unicode的各种“编码”。其中有一些,其中UTF-8, UTF-16UTF-32 是当前的,还有一些是过时的。

请UTF-8(以及所有其他 编码 )存在并且仅在输入或输出中有意义。在内部,从Perl 5.8.1开始,所有字符串都保存为Unicode“Code-points”。没错,你必须启用一些以前令人敬佩的功能。

28
MeirG

在野外有一个真正令人恐怖的古代代码,其中大部分都是常见的CPAN模块。我发现如果我使用可能受其影响的外部模块,我必须非常小心地启用Unicode,并且我仍然试图在我经常使用的几个Perl脚本中识别并修复一些与Unicode相关的故障(特别是 iTiVo 由于转码问题而无法解决任何非7位ASCII的问题。

10
geekosaur

您应该启用unicode字符串功能,如果您使用v5.14,这是默认设置;

你不应该真的使用unicode标识符esp。对于通过utf8的外来代码,因为它们在Perl5中是不安全的,只有cperl才能做到这一点。参见例如 http://Perl11.org/blog/unicode-identifiers.html

关于文件句柄/流的utf8:您需要自己决定外部数据的编码。一个库无法知道,因为即使libc不支持utf8,也很少有正确的utf8数据。还有更多wtf8,utf8的窗户像差。

BTW:Moose并不是真正的“现代Perl”,他们只是劫持了这个名字。 Moose是完美的Larry Wall风格的后现代Perl混合了Bjarne Stroustrup风格的一切,伴随着适当的Perl6语法的折衷差异,例如:使用字符串作为变量名,可怕的字段语法,以及一个非常不成熟的天真实现,比正确的实现慢10倍。 cperl和Perl6是真正的现代perls,其中form跟随函数,并且实现被减少和优化。

1
rurban