Variable / Constant in UNIVERSAL?
I'm sure my problem is related to Namespaces and scoping, but I need some help!
I'm writing an OO Perl script with a fairly large number of classes and a requirement to minimise external module use (don't ask... I know, I know...)
So, I want to use UNIVERSAL to offer a logging method that every object can use.
Here's a very simple example that I've just whipped up.
use strict;
use warnings;
package House;
sub new {
my ( $class, %args ) = @_;
my $self = {
colour => $args{colour},
size => $args{size},
};
bless $self, $class;
return $self;
}
package Boat;
sub new {
my ( $class, %args ) = @_;
my $self = {
doors => $args{doors},
roof => $args{roof},
};
bless $self, $class;
return $self;
}
package main;
my $obj = Boat->new( colour => "red", size => "big" );
$obj->_logger("created a big red boat");
my $obj2 = House->new( doors => 1, roof => "yes" );
$obj2->_logger("created a house with a door and roof");
package UNIVERSAL;
use POSIX qw( strftime );
use Sys::Hostname;
my $error_log
= hostname() . "-" . strftime( "%Y-%m-%d_%H.%M", localtime ) . ".log";
sub _dump {
my ( $self, $data, $filepath ) = @_;
open my $fh, ">", $filepath or die "Cannot write to $filepath: $!";
print $fh $data;
}
sub _logger {
my ( $self, $data ) = @_;
my $timestamp = strftime( "%Y-%m-%d %H:%M:%S", localtime );
$self->_dump( $timestamp . " " . $data, $error_log );
}
__END__
The problem is the $error_log
variable in the UNIVERSAL namespace doesn't seem to be accessible by the objects in other classes in the same way that the U开发者_开发问答NIVERSAL methods are.
Errors with my $error_log
:
Use of uninitialized value $filepath in open at ./test_uni.pl line 47.
Use of uninitialized value $filepath in concatenation (.) or string at ./test_uni.pl line 47.
Cannot write to : No such file or directory at ./test_uni.pl line 47.
Actually, now I type this I wonder if a closure with a class method in UNIVERSAL would work.
While I go and try that, does anyone have any suggestions for me please?
Thanks!
==================== UPDATE ======================
A closure with a class method in UNIVERSAL seemed to work:
package UNIVERSAL;
use POSIX qw( strftime );
use Sys::Hostname;
{
sub ERROR_LOG {
return hostname() . "-" . strftime( "%Y-%m-%d_%H.%M", localtime ) . ".log";
}
}
And then I call it in UNIVERSAL::_logger
with UNIVERSAL->ERROR_LOG
.
BUT! I only want the ERROR_LOG filepath to be created once at runtime. With this it will evaluate it every time...
Is this the only way? How can I access variables in the UNIVERSAL package from elsewhere?
Thanks!
The problem in your case is just that you run the code before the stuff in UNIVERSAL is set up.
Move the main package all the way down, or wrap UNIVERSAL in a BEGIN block, and it works.
Nothing weird about UNIVERSAL or my going on here.
Update: Okay, it is a bit weird that you can call _logger (so that part is loaded already), but the $error_log is not there yet. Here is a minimal example that demonstrates this behaviour (remove the BEGIN block to see the problem):
use strict;
use warnings;
ABC->hey();
package ABC;
BEGIN{
my $x = 1;
sub hey(){
print "x = $x";
}
}
Maybe this explains it:
A my has both a compile-time and a run-time effect. At compile time, the compiler takes notice of it. The principal usefulness of this is to quiet use strict 'vars' , but it is also essential for generation of closures as detailed in perlref. Actual initialization is delayed until run time, though, so it gets executed at the appropriate time, such as each time through a loop, for example.
My reading would be that the variable declaration and the subroutines are compiled before the code is executed, but that the assignment of the value does not take place until the line which does it is reached again (which in your case is after you call the subroutines which are closures around the still uninitialized value).
精彩评论