在 Perl 严格模式下从变量访问完全限定的变量名
Access Fully Qualified Variable Name from Variable in Perl Strict Mode
我有一系列模块可以为我的脚本执行输出功能。有时直接调用该模块——称为 View
——有时使用扩展它的 child class (View::ChildName
)。视图在加载时声明 our $THEMENAME = 'default';
,但 child 在加载时声明其自己的特定 $THEMENAME
。
问题:当在child主题上调用new()
时,它会调用my $self = $class->next::method(%params);
(使用mro
)来获取[=72]设置的一些核心内容=] class 在扩展它之前。核心位之一是 parent class 设置 $self->{'themeName'}
。但是,如果它只是调用 $THEMENAME
,它会得到 parent 的设置:“default.”
我可靠且成功解决此问题的唯一方法是暂时关闭 strict
并执行此操作:
my $packName = ref $self;
{
no strict;
$self->{'themeName'} = ${${packName} . "::THEMENAME"};
}
这行得通,但是在分析代码时,如果经常创建 objects,这实际上会增加比我预期的更多的开销。我尝试了始终使用 parent 的包名称的替代方法,例如child 集 $View::THEMENAME
。此 有效 ,但前提是主题名称设置在 new
内,而不是在模块加载时;如果它正在加载,如果在脚本过程中创建了多个不同的 child objects(不同的 children),则会出现不稳定的行为。
这些选项似乎都不理想。有没有好的方法来做到这一点?我发现的唯一东西是 this old question,我认为合并 Moo
可能会增加更多的开销,而不是我试图通过摆脱当前的 no strict
块来避免的开销。是否已向更现代的 Perl 版本中添加了可以解决我的问题的任何内容?
另一种方法是一起回避这个问题,并在每个 child object 的 new
方法中简单地设置 $self->{'themeName'}
,尽管我试图避免这种变化是因为有相当数量的遗留 child classes 期望 $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;
}
一个可能的解决方案是将 THEMENAME 添加为可覆盖的方法。
View.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;
}
View/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
class 属性的概念是您应该忘记的(在 Perl 中)。模块具有常量和可能的变量是很好的,但它们不应被视为 class.
的一部分
我看到您可以采取四种方法:
- 更新子构造函数中的 属性。
- 提供值作为参数
- 覆盖访问器
- 提供默认方法
更新子构造函数中的属性
# 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' }
我的最终解决方案遵循@plentyofcoffee 和@ikegami 概述的内容,但我想看看我是否可以想出一种方法来自动设置它,而无需每个 child 模块实现它(记住遗留代码).假设 child 确实想要设置它,它将 $param{'themeName'} 传递给 parent 的构造函数,后者将其设置为 $self->{'themeName'}
。如果 themeName
未定义,我在 parent class 中提出了这个正则表达式,它提取 child 的名称作为后备 themeName
:
unless ($self->{'themeName'}) {
state $getThemeNameRegEx = qr#^SAFARI::(.*::)+(.*?)$#;
$class =~ /$getThemeNameRegEx/;
$self->{'themeName'} = // "default";
}
如果名称不包含至少低于 SAFARI
的两个级别,则设置为 default
,例如SAFARI::View
是 default
(parent 模块在没有 child 的情况下使用)并且 SAFARI::View::mysite
是 mysite
.
我有一系列模块可以为我的脚本执行输出功能。有时直接调用该模块——称为 View
——有时使用扩展它的 child class (View::ChildName
)。视图在加载时声明 our $THEMENAME = 'default';
,但 child 在加载时声明其自己的特定 $THEMENAME
。
问题:当在child主题上调用new()
时,它会调用my $self = $class->next::method(%params);
(使用mro
)来获取[=72]设置的一些核心内容=] class 在扩展它之前。核心位之一是 parent class 设置 $self->{'themeName'}
。但是,如果它只是调用 $THEMENAME
,它会得到 parent 的设置:“default.”
我可靠且成功解决此问题的唯一方法是暂时关闭 strict
并执行此操作:
my $packName = ref $self;
{
no strict;
$self->{'themeName'} = ${${packName} . "::THEMENAME"};
}
这行得通,但是在分析代码时,如果经常创建 objects,这实际上会增加比我预期的更多的开销。我尝试了始终使用 parent 的包名称的替代方法,例如child 集 $View::THEMENAME
。此 有效 ,但前提是主题名称设置在 new
内,而不是在模块加载时;如果它正在加载,如果在脚本过程中创建了多个不同的 child objects(不同的 children),则会出现不稳定的行为。
这些选项似乎都不理想。有没有好的方法来做到这一点?我发现的唯一东西是 this old question,我认为合并 Moo
可能会增加更多的开销,而不是我试图通过摆脱当前的 no strict
块来避免的开销。是否已向更现代的 Perl 版本中添加了可以解决我的问题的任何内容?
另一种方法是一起回避这个问题,并在每个 child object 的 new
方法中简单地设置 $self->{'themeName'}
,尽管我试图避免这种变化是因为有相当数量的遗留 child classes 期望 $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;
}
一个可能的解决方案是将 THEMENAME 添加为可覆盖的方法。
View.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;
}
View/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
class 属性的概念是您应该忘记的(在 Perl 中)。模块具有常量和可能的变量是很好的,但它们不应被视为 class.
的一部分我看到您可以采取四种方法:
- 更新子构造函数中的 属性。
- 提供值作为参数
- 覆盖访问器
- 提供默认方法
更新子构造函数中的属性
# 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' }
我的最终解决方案遵循@plentyofcoffee 和@ikegami 概述的内容,但我想看看我是否可以想出一种方法来自动设置它,而无需每个 child 模块实现它(记住遗留代码).假设 child 确实想要设置它,它将 $param{'themeName'} 传递给 parent 的构造函数,后者将其设置为 $self->{'themeName'}
。如果 themeName
未定义,我在 parent class 中提出了这个正则表达式,它提取 child 的名称作为后备 themeName
:
unless ($self->{'themeName'}) {
state $getThemeNameRegEx = qr#^SAFARI::(.*::)+(.*?)$#;
$class =~ /$getThemeNameRegEx/;
$self->{'themeName'} = // "default";
}
如果名称不包含至少低于 SAFARI
的两个级别,则设置为 default
,例如SAFARI::View
是 default
(parent 模块在没有 child 的情况下使用)并且 SAFARI::View::mysite
是 mysite
.