Perl模块名称是文件路径 - 仅此而已

在Perl术语中,通常将“模块”和“包”视为同义词,实际上它们几乎指代同一事物。模块名称是文件路径的简称,但包名称指的是Perl符号表中的命名空间。很容易忘记这一点,因为模块名称和包名称使用相同的冒号分隔符号表示法,并且传统上我们给包命名时与模块文件路径相同。例如

require Test::More; # load Test/More.pm

Test::More::ok 1; # call the ok function in the Test::More namespace

在这个例子中,Test::More出现了两次,但实际上它指的是两件不同的事情;第一件是一个文件路径,第二件是一个符号命名空间。它们不必具有相同的名称。不幸的是,perlmod延续了这一神话

模块只是库文件中一组相关函数的集合,即与文件同名的Perl包。

演示

我将快速创建一个名为“ACME::Foo::Bar”的模块,lib/ACME/Foo/Bar.pm看起来像这样

package Whatever2;

our $VERSION = 0.01;

=head1 NAME

ACME::Foo::Bar - proof that module names and packages are not the same

=cut

sub me { __PACKAGE__ }

1;

请注意,包名称Whatever2与模块名称ACME::Foo::Bar完全不同。在终端,我可以测试它

$ perl -Ilib -MACME::Foo::Bar -E 'say Whatever2::me'
Whatever2

Perl愉快地加载了ACME::Foo::Bar模块和Whatever2命名空间(我最初使用Whatever作为包名称,但CPAN上已经有了这个名字的另一个包)。

作为一个发行版

通过添加makefile,我可以将其制作成一个可安装的发行版,Makefile.PL

use 5.008000;

use ExtUtils::MakeMaker;
WriteMakefile(
  NAME           => 'ACME::Foo::Bar',
  VERSION_FROM   => 'lib/ACME/Foo/Bar.pm',
  ABSTRACT_FROM  => 'lib/ACME/Foo/Bar.pm',
  AUTHOR         => 'David Farrell',
  LICENSE        => 'perl5',
  MIN_PERL_VERSION => "5.008000",
);

嘿,我们还可以添加一些测试,t/whatever.t

#!/usr/bin/perl
use Test::More;

BEGIN { use_ok 'ACME::Foo::Bar', 'import module' }

is Whatever2::me, 'Whatever2', 'me() returns package name';

done_testing;

安装很简单

$ perl Makefile.PL
Generating a Unix-style Makefile
Writing Makefile for ACME::Foo::Bar
Writing MYMETA.yml and MYMETA.json
$ make
cp README.pod blib/lib/ACME/Foo/README.pod
cp lib/ACME/Foo/Bar.pm blib/lib/ACME/Foo/Bar.pm
Manifying 2 pod documents
$ make test
PERL_DL_NONLAZY=1 "/home/dfarrell/.plenv/versions/5.22.0/bin/perl5.22.0" "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "undef *Test::Harness::Switches; test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/whatever.t .. ok
All tests successful.
Files=1, Tests=2,  0 wallclock secs ( 0.01 usr  0.00 sys +  0.01 cusr  0.00 csys =  0.02 CPU)
Result: PASS
$ make install
Manifying 2 pod documents
Installing /home/dfarrell/.plenv/versions/5.22.0/lib/perl5/site_perl/5.22.0/ACME/Foo/Bar.pm
Installing /home/dfarrell/.plenv/versions/5.22.0/lib/perl5/site_perl/5.22.0/ACME/Foo/README.pod
Installing /home/dfarrell/.plenv/versions/5.22.0/man/man3/ACME::Foo::README.3
Installing /home/dfarrell/.plenv/versions/5.22.0/man/man3/ACME::Foo::Bar.3
Appending installation info to /home/dfarrell/.plenv/versions/5.22.0/lib/perl5/5.22.0/x86_64-linux/perllocal.pod

现在我可以在终端测试已安装的版本

$ perl -MACME::Foo::Bar -E 'say Whatever2::me'
Whatever2

哇!一切顺利。

工具链问题

现在,我有一个包含具有不同包名称的模块的发行版,它与CPAN工具链配合得怎么样?我已经将发行版上传到CPAN,你可以在metacpan上查看它,并且其CPAN Testers 结果看起来很好。

但有一个大问题:PAUSE索引器。PAUSE是维护CPAN数据和其包列表的服务器。该索引器要求发行版中有一个与包名称匹配的模块。这很有道理,因为它会阻止用户将冲突的包名称上传到不同的发行版。

CPAN客户端在包列表中查找包名称,以确定要安装哪个发行版,因此如果我的Whatever2包不在列表中,我就不能通过这种方式安装ACME::Foo::Bar

$ cpan Whatever2
CPAN: Storable loaded ok (v2.53)
Reading '/home/dfarrell/.local/share/.cpan/Metadata'
  Database was generated on Thu, 15 Dec 2016 13:53:43 GMT
Warning: Cannot install Whatever2, don't know what it is.
Try the command

    i /Whatever2/

to find objects with matching identifiers.

但是通过其发行版名称引用它则没有问题。

$ cpan DFARRELL/ACME-Foo-Bar-0.02.tar.gz
--> Working on DFARRELL/ACME-Foo-Bar-0.02.tar.gz
Fetching http://www.cpan.org/authors/id/D/DF/DFARRELL/ACME-Foo-Bar-0.02.tar.gz ... OK
Configuring ACME-Foo-Bar-0.02 ... OK
Building and testing ACME-Foo-Bar-0.02 ... OK
Successfully installed ACME-Foo-Bar-0.02
1 distribution installed

一个例外是cpanm,如果它没有在CPAN元数据库中找到包,它会回退到metacpan API的文件搜索。所以这没问题。

$ cpanm Whatever2

总结

Neil Bowers已经编写了一个关于CPAN术语的优秀词汇表。具有与其模块名称不同的命名空间的包被称为“杜鹃”包。

按照惯例,使用相同的包和模块名称很有用且推荐。特别是如果代码将通过CPAN或其他方式共享。但了解它们并不相同是很好的。


更新将示例中的“use”更改为“require”,因为“use”会在命名空间上调用“import()”。将包名称更改为“Whatever2”,以避免CPAN冲突。感谢Perlancar、Aristotle和Grinnz在/r/perl上的反馈。2016-12-15


这篇文章最初发表在PerlTricks.com上。

标签

David Farrell

David是一位专业程序员,他经常在Twitter博客上分享关于代码和编程艺术的见解。

浏览他们的文章

反馈

这篇文章有什么问题吗?请在GitHub上创建一个issue或pull request来帮助我们。