【问题标题】:Access Fully Qualified Variable Name from Variable in Perl Strict Mode在 Perl 严格模式下从变量访问完全限定的变量名
【发布时间】:2021-10-04 12:51:56
【问题描述】:

我有一系列为我的脚本执行输出功能的模块。有时会直接调用模块——它被称为View——有时会使用扩展它的子类(View::ChildName)。 View 在加载时声明our $THEMENAME = 'default';,而子视图在加载时声明自己特定的$THEMENAME

问题:在子主题上调用new() 时,它会调用my $self = $class->next::method(%params);(使用mro)来获取父类设置的一些核心内容,然后再对其进行扩展。核心位之一是父类设置$self->{'themeName'}。但是,如果它只是调用$THEMENAME,它会获取父级的设置:“默认”。

我可靠且成功地解决此问题的唯一方法是暂时关闭 strict 并执行以下操作:

my $packName = ref $self;

{ 
    no strict;
    $self->{'themeName'} = ${${packName} . "::THEMENAME"};
}   

这可行,但在分析代码时,如果频繁创建对象,这实际上会增加比我预期的更多的开销。我尝试了始终使用父包名称的替代方法,例如孩子设置$View::THEMENAME。这有效,但前提是主题名称设置在new 内,而不是在模块加载时;如果它正在加载,如果在脚本过程中创建了几个不同的子对象(不同的子对象),则会出现不稳定的行为。

这些选项似乎都不太理想。有没有好的方法来做到这一点?我发现的唯一一件事是this old question,我认为合并Moo 可能会增加比我通过摆脱当前no strict 块来避免的更多开销。是否在更现代的 Perl 版本中添加了任何可以解决我的问题的内容?

另一种方法是一起避开这个问题,只需在每个子对象的 new 方法中设置 $self->{'themeName'},尽管我试图避免这种变化,因为有相当多的遗留子类期望 $THEMENAME存在。


View.pm 的最小可重现示例:

use strict;
package View;

our $THEMENAME = 'default';

sub new {
    my $class = shift;
    my $params = shift;
    my $self = { 'setting' => $params{'setting'} };

    bless $self, $class;

    $self->{'themeName'} = $THEMENAME;
    return $self;  
}

View/Child.pm:

use strict;
use mro;
package View::Child;

use parent 'View';

our $THEMENAME = 'child';

sub new {
    my $class = shift;
    my $params = shift;
    my $self = $class->next::method($params);

    bless $self, $class;

    say STDOUT $self->{'themeName'};
    # Prints 'default' not 'child'.

    return $self;  
}

现在是一个脚本来调用它:

use View::Child;
my $object = View::Child->new();

如果您将第一个代码块添加到 View.pm,它会给出预期的结果,但似乎每次调用 new 会增加大约 9 毫秒 - 超过它处理其他所有事情所需的时间我有更长的全长new 方法——如果程序运行多次迭代,它会加起来:

use strict;
package View;

our $THEMENAME = 'default';

sub new {
    my $class = shift;
    my $params = shift;
    my $self = { };

    bless $self, $class;

    my $packName = ref $self;

    { 
         no strict;
         $self->{'themeName'} = ${${packName} . "::THEMENAME"};
    }  
    return $self;  
}

【问题讨论】:

  • 你能添加一个最小的可运行示例吗?这将有助于澄清你的问题。见minimal reproducible example
  • @HåkonHægland 我添加了一个。谢谢!
  • 此行在您当前的示例中返回 undefmy $self = $class->next::method($params);
  • 哦!我在描述文件时混淆了文件名的符号,我认为:应该是View/Child.pm而不是View::Child.pm。也许这就是问题? (对于演示代码,不是我的问题。)
  • 为什么不能在调用了父构造函数后,在子构造函数中设置主题名称?

标签: perl oop strict


【解决方案1】:

类属性的概念是你应该忘记的(在 Perl 中)。模块可以有常量和可能的变量,但它们不应该被视为类的一部分。

我看到您可以采取四种方法:

  • 更新子构造函数中的属性。
  • 将值作为参数提供
  • 覆盖访问器
  • 提供默认的方法

更新子构造函数中的属性

# View.pm
sub new {
    my ($class, $params) = @_;
    my $self = bless({}, $class);

    $self->{ setting   } = $params->{ setting };
    $self->{ themeName } = 'default';

    return $self;  
}

sub themeName { $_[0]->{themeName} }  # Optional
# Child/View.pm
sub new {
    my ($class, $params) = @_;
    my $self = $class->next::method($params);

    $self->{ themeName } = 'child';

    return $self;
}

提供值作为参数

# View.pm
sub new {
    my ($class, $params) = @_;
    my $self = bless({}, $class);

    $self->{ setting   } = $params->{ setting   };
    $self->{ themeName } = $params->{ themeName } // 'default';

    return $self;  
}

sub themeName { $_[0]->{themeName} }  # Optional
# Child/View.pm
sub new {
    my ($class, $params) = @_;
    my $self = $class->next::method({ themeName => 'child', %$params });
    return $self;
}

覆盖访问器

# View.pm
sub new {
    my ($class, $params) = @_;
    my $self = bless({}, $class);

    $self->{ setting } = $params->{ setting };

    return $self;  
}

sub themeName { 'default' }
# Child/View.pm

# No need to override `new`.

sub themeName { 'child' }

提供默认的方法

# View.pm
sub new {
    my ($class, $params) = @_;
    my $self = bless({}, $class);

    $self->{ setting   } = $params->{ setting };
    $self->{ themeName } = $class->defaultThemeName;

    return $self;  
}

sub defaultThemeName { 'default' }

sub themeName { $_[0]->{themeName} }  # Optional
# Child/View.pm

# No need to override `new`.

sub defaultThemeName { 'child' }

【讨论】:

    【解决方案2】:

    一种可能的解决方案是将 THEMENAME 添加为可覆盖的方法。

    查看.pm:

    use strict;
    package View;
    
    our $THEMENAME = 'default';
    
    sub THEMENAME {return 'default'}
    
    sub new {
        my $class = shift;
        my $params = shift;
        my $self = { 'setting' => $params->{'setting'} };
    
        bless $self, $class;
    
        $self->{'themeName'} = $self->THEMENAME;
        return $self;  
    }
    

    查看/Child.pm:

    use strict;
    use mro;
    package View::Child;
    
    use parent 'View';
    
    our $THEMENAME = 'child';
    
    sub THEMENAME {return 'child'}
    
    sub new {
        my $class = shift;
        my $params = shift;
        my $self = $class->next::method($params);
    
        bless $self, $class;
    
        say STDOUT $self->{'themeName'};
        # Prints 'default' not 'child'.
        #
        return $self;  
    }
    
    # perl -Mlib=. -MView::Child -e 'View::Child->new()'
    child
    

    【讨论】:

      【解决方案3】:

      我的最终解决方案遵循@plentyofcoffee 和@ikegami 概述的内容,但我想看看是否可以想出一种方法来自动设置它,而无需每个子模块都实现它(记住遗留代码)。假设孩子确实想要设置它,它会将 $param{'themeName'} 传递给父母的构造函数,将其设置为$self->{'themeName'}。如果themeName 未定义,我在父类中提出了这个正则表达式,它提取孩子的名字作为后备themeName

      unless ($self->{'themeName'}) {
          state $getThemeNameRegEx = qr#^SAFARI::(.*::)+(.*?)$#; 
          $class =~ /$getThemeNameRegEx/;
          $self->{'themeName'} = $2 // "default"; 
      }
      

      如果名称不包含低于SAFARI 的至少两个级别,则设置为default,例如SAFARI::Viewdefault(父模块正在使用,没有子模块),SAFARI::View::mysitemysite

      【讨论】:

        猜你喜欢
        • 2013-07-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-02
        • 2013-06-30
        • 2015-10-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多