我该如何在Perl模块中创建私有函数?

38

我正在开发一个 Perl 模块,但出于某种原因,我的测试驱动程序调用了其中一个我认为应该是私有的函数,并且执行成功。我很惊讶,于是开始在 Google 上搜索,但实际上找不到有关如何在 Perl 模块中创建私有函数的文档。

我看到有人建议在“私有”函数的闭括号后面加上分号,像这样:

sub my_private_function {
...
}; 

我尝试过这样做,但是我的驱动脚本仍然可以访问我想要私有化的函数。

我会编写一个更简短的例子,但是这就是我要达到的效果:

模块TestPrivate.pm:

package TestPrivate;

require 5.004;

use strict;
use warnings;
use Carp;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);

require Exporter;

@ISA = qw(Exporter AutoLoader);

our @EXPORT_OK = qw( public_function );
our @EXPORT    = qw( );

$VERSION = '0.01';

sub new {
    my ( $class, %args ) = @_;
    my $self = {};
    bless( $self, $class );
    $self->private_function("THIS SHOULD BE PRIVATE");
    $self->{public_variable} = "This is public";
    return $self;
}

sub public_function {
    my $self     = shift;
    my $new_text = shift;
    $self->{public_variable} = $new_text;
    print "Public Variable: $self->{public_variable}\n";
    print "Internal Variable: $self->{internal_variable}\n";
}

sub private_function {
    my $self     = shift;
    my $new_text = shift;
    $self->{internal_variable} = $new_text;
}

驱动程序:TestPrivateDriver.pl

#!/usr/bin/perl
use strict;
use TestPrivate 'public_function';
my $foo = new TestPrivate();
$foo->public_function("Changed public variable");
$foo->private_function("I changed your private variable");
$foo->public_function("Changed public variable again");
$foo->{internal_variable} = "Yep, I changed your private variable again!";
$foo->public_function("Changed public variable the last time");

驱动器输出:

Public Variable: Changed public variable
Internal Variable: THIS SHOULD BE PRIVATE
Public Variable: Changed public variable again
Internal Variable: I changed your private variable
Public Variable: Changed public variable the last time
Internal Variable: Yep, I changed your private variable again!

因此,在模块的最后一个闭合大括号之后添加了一个分号,但输出结果仍然相同。我唯一真正发现的是在私有函数的第一行添加这一行:

caller eq __PACKAGE__ or die;

但这似乎相当hacky。我没有很多编写Perl模块的经验,所以也许我正在错误地设置我的模块?在Perl模块中是否可能有私有函数和变量?

谢谢帮助我学习!


如果你正在编写一个面向对象的类,你可以去掉EXPORT部分。这与方法或可见性无关。 - brian d foy
我之前尝试导出函数,因为我以为这样会使得其他函数不能导出或变成私有的。现在我知道那样做并没有帮助。 - BrianH
我认为你应该重新考虑你的“正确”答案。你可以实现你想要的,但你必须使用像Brian Phillips或Brian D Foy建议的内部类结构。有一本书叫做“Perl最佳实践”,其中有一两章专门讲述这个概念。 - Joe Casadonte
9个回答

39

来自perldoc perltoot(文档约四分之一处):

Perl不会对谁可以使用哪些方法施加限制。公共与私有的区别是惯例,而非语法。(除非您在“数据成员作为变量”中描述的Alias模块中使用了它)。偶尔你会看到方法名以一个或两个下划线开头或结尾。这种标记是一种惯例,表示这些方法仅为该类及其最亲密的附属类独有,但这种区别并不被 Perl 自身强制执行。这取决于程序员的行为。

因此,我建议您在“私有”方法的开头加上一个或两个下划线,以帮助阻止其使用。


是的 - 我也注意到了下划线前缀 - 我尝试过这样做,以为这是一种神奇的 Perl 方法来使函数私有化。我想这只是一个“警告”,但并不可强制执行。 - BrianH
8
“Underscore”?美妙的形象。 - AmbroseChapel
3
从Perl 5.18开始,你可以使用'my'或'state'来声明私有子例程。 https://perldoc.perl.org/perlsub.html#Lexical-Subroutines - Беров

24

只有将代码引用存储在一个词法变量中的“拙劣解决方案”,而这个变量只能在该作用域内被访问到:

my $priv_func1 = sub { my $self = shift; say 'func1'; };

sub public_sub { 
    my $self = shift;

    $priv_func1->( $self );
}

我想不出一种严格“保护”的字段的方法。

就我所知,除了源代码过滤器之外(嘘,我没有提到它们...)。


编辑:实际上,事实证明我可以想出一种非常混乱的保护方法。但这可能涉及通过AUTOLOAD子程序传递所有调用。 (!!)


1
“Inside-Out objects”可以用于创建C++(ish)术语中的“private”字段。也就是说,数据实际上存储在单独的哈希表中。 - Arne Vogel

15

这个有效:

my $priv_func1 = sub {
    my $self = shift; say 'func1';
};

sub public_sub { 
    my $self = shift;

    $self->$priv_func1(@_);
}

虽然这样做不太高效,因为在运行时调用$priv_func需要进行变量解引用。 - Ether
15
以太坊:你完全错了,实际上它更加高效,因为它不需要进行方法查找。 - Leon Timmermans
这个问题的唯一要点是(并且这个问题困扰了我)- 在尝试使用它之前,您必须确保已声明私有方法。 您不必提供完整的定义,只需声明变量即可。 如果您想的话稍后可以定义它(有点像C语言)。my $priv_func1; sub public_sub { my $self = shift; $self->$priv_func1(@_); } $priv_func1 = sub { my $self = shift; say 'func1'; } - livefree75

9

仅检查调用者:

package My;

sub new {
  return bless { }, shift;
}

sub private_func {
  my ($s, %args) = @_;
  die "Error: Private method called"
    unless (caller)[0]->isa( ref($s) );

  warn "OK: Private method called by " . (caller)[0];
}

sub public_func {
  my ($s, %args) = @_;

  $s->private_func();
}

package main;

my $obj = My->new();

# This will succeed:
$obj->public_func( );

# This will fail:
$obj->private_func( );

1
我认为可以使用方法属性以更模块化的方式实现这个。 - Ether

6

你想做什么?也许有更好的Perl方法来完成你想要的任务。

例如,如果您不希望人们操纵您的对象,因为您想强制执行封装,您可以使用类似于Class::InsideOut的东西。该模块具有Class::InsideOut::About文档模块,解释了该概念。还有Object::InsideOut,Brian Phillips已经提到过了。


你最好的选择确实是使用Class::InsideOut或Moose http://www.catalyzed.org/2009/06/a-gentle-introduction-to-moose.html - Drew Stephens
1
幸运的是我不赌博。更好的选择是分析情况并根据你所发现的选项进行评估。 :) - brian d foy

3

在使用这种面向对象的编程风格一段时间后,你会发现无法直接使用Data::Dumper来转储对象或查看其数据,这让它有些“不像Perl”的感觉。但是,如果你想尝试一下,我建议使用Object::InsideOut。它支持为对象提供私有数据和方法,还有许多其他方便的功能(访问器生成、默认构造函数等)。


如果你处理的是小项目,它还可以节省内存。 - Brad Gilbert

3
我们可以在Perl私有函数下面编写一些内容,以检查调用是否来自与caller[0]相同的对象包。
sub foo {
  my ($s, %args) = @_;
  die "Error: Private method called"
      unless (caller)[0]->isa( ref($s) );
}

2
如果您使用像Moose这样的系统,您可以获得公共/私有区分,如此处所示。请注意保留HTML标签。

那是一个相当糟糕的配方 - 试试这个:http://search.cpan.org/~franckc/MooseX-MethodPrivate-0.1.2/lib/MooseX/MethodPrivate.pm - Ether
@ether 这个链接已经过时了 @chris 你也是,但我为你的提交了一个编辑。你们应该把版本去掉,使用 dist/ 而不是 ~author/ - xenoterracide
修正后的链接:http://search.cpan.org/perldoc?MooseX::MethodPrivate (@xenoterracide:你意识到这条评论已经超过一年了吗?;) - Ether
1
@ether 没有考虑过这个...管理员仍然可以修改,我不知道是否有10k人。我只是想成为一个好公民,保持SE的准确性。 - xenoterracide

0
在您的包文件中:将私有方法定义为CODE-Ref,例如:
my $private_methode = sub{};

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