Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@ISA vs use base vs use parent

Tags:

perl

Ran across this little nugget... I think I have it figured out, but see if anyone can shed more light.

I have a module in my PERL5LIB environment variable that I use for logging. It is in e:/Scripts/Log/Logger.pm. Following is an excerpt:

package Log::Logger;
...

package Log::NullLogger;
use base 'Log::Logger';
...

package Log::FileLogger;
use base 'Log::Logger';
...

package Log::DateFileLogger;
use base 'Log::FileLogger';
...

package LogTester;
use Data::Dump 'dump';
test() unless caller();
sub test {
    warn dump \%INC;
    ...
}

I wanted to make some changes to this module so I copied it to e:/Test/Log/Log/Logger.pm and started making changes and testing... I added a new class and altered an inherited routine in the Log::Logger package...

package BasicFormatter;
...

But my tests were very odd. Some of the new code was being used, some was not. The dump of %INC had:

"Log/Logger.pm" => "e:/Scripts/Log/Logger.pm"

... the old module! From what I can tell, use base 'Log::Logger' went out and re-loaded Log/Logger.pm from PERL5LIB, replacing only those packages in it (i.e. package BasicFormatter was coming from the new module, but all other packages were from the old module).

I read about use parent and when I replaced my use base with use parent I got compile errors on use parent 'Log::FileLogger' ostensibly because there was no Log/FileLogger.pm module. use base never complained about this -- a weakness in use parent in my opinion.

I added

use lib '..';

to the top of the module and everything worked with use base but that was not a fix I like, because it would not be in the final module when I "publish" it back to e:/Scripts/Log/Logger.pm. With the use lib '..', %INC now showed

"Log/Logger.pm" => "../Log/Logger.pm"

which makes sense.

My next step was to remove the use lib '..' and replace all use base... with our @ISA=(...). When I did this, the code started working and %INC didn't show an entry for Log/Logger.pm at all.

Note, when I set up a test script in e:/Test/Log/TestLogger.pl with

use lib '.';

in it, it picks up e:/Test/Log/Log/Logger.pm fine and the use base calls don't re-require the package.

So, my conclusion is this... when running the module as a modulino, the module isn't "loaded" as far as Perl is concerned. So the first attempt to use base on that module does a require <module> which then loads it from the @INC path, which could replace packages already in the modulino being executed. Using @ISA instead bypasses the calls to require and so the module isn't loaded from @INC.

Anyone else got more information on this... is testing a module as a modulino just a bad idea?

like image 780
mswanberg Avatar asked Sep 06 '25 03:09

mswanberg


2 Answers

I think it would be useful to clarify a few things:

@ISA

is for defining a package's parent classes. It does not affect how packages are loaded. You can manipulate it directly, or you can use the parent or the slightly older base pragmas. The difference between manipulating @ISA directly and these two pragmas is that the pragmas do a little more: use base qw/Foo Bar/; and use parent qw/Foo Bar/; are both roughly similar in effect to

BEGIN {
    require Foo;
    require Bar;
    push @ISA, qw(Foo Bar);
}

meaning that not only do they manipulate @ISA, they also load the package with require. There is one exception, which is use parent -norequire, qw/Foo Bar/; - this may be useful if, as you seem to be showing, all of your packages reside in the same file. However, both pragmas do not affect how require operates, including where it looks, for that you need to manipulate @INC.

@INC

is the place that Perl's use, require, and do look for files (the latter with a few exceptions). There are several ways to manipulate @INC:

  • From the command line: perl -I/path/to/modules script.pl (see perlrun)
  • From within each script individually: use lib '/path/to/modules';, or by direct manipulation of @INC
  • Using the PERL5LIB environment variable
  • Globally for the Perl installation: sitecustomize.pl (only when configured)
  • When building a new Perl, there are various options (search for "@INC")

Perl looks through @INC in order. So in your case, if Perl is loading the wrong version of Log::Logger, that means that Perl finds it earlier in @INC than the version you actually wanted to load. I would recommend inspecting @INC and making sure that you only include those paths from which you actually want to load modules, or placing the path for the "development" version of Log::Logger earlier in @INC. For example, when I develop modules, I usually specify the directory for the development version on the command line with e.g. perl -Ilib ....

Note that there was a change as of Perl 5.26: the current directory . was removed as a default entry in @INC. (Perl 5.24.1 also introduced some related changes.)

%INC

basically just keeps track of which modules have already been loaded, so that require and use don't load the same module twice. While it is possible to trick require into not looking for a file by adding its entry to %INC manually, there are usually better solutions than this.

like image 53
haukex Avatar answered Sep 08 '25 15:09

haukex


parent requires you to explicitly say whether you want it to actually load the parent module or just set up inheritance. This is a strength, not a weakness; base had the weakness of always trying to load the module, leading to the problem you had.

Do:

use parent -norequire => 'Log::Logger';
...
use parent -norequire => 'Log::FileLogger';

etc.

That said, the problem base had wouldn't have happened if you consistently used use or require to load your module; if you just run a module (e.g. perl Foo/Bar.pm) or otherwise load it (use Test::Log::Logger instead of use lib 'Test'; use Log::Logger??) and your code also tries to use it, you always risk multiple versions being found.

like image 20
ysth Avatar answered Sep 08 '25 17:09

ysth