Perl : Two packages in same file cannot import sam

2020-06-28 02:38发布

问题:

This is an interesting Perl behaviour. (atleast to me :) )

I have two packages PACKAGE1 and PACKAGE2 which exports function with same name Method1().

As there will be so many packages which will export this same function, use-ing everything in a Perl file will be tedious. So, I have created a general include file INCLUDES.pm which will have these uses.

INCLUDES.pm:

use PACKAGE1;
use PACKAGE2;

1;

PACKAGE1.pm:

package PACKAGE1;

use Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw (
          Method1
);

sub Method1{
print "PACKAGE1_Method1 \n";
}

1;

PACKAGE2.pm:

package PACKAGE2;

use Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw (
    Method1
);

sub Method1{
    print "PACKAGE2_Method1 \n";
}

1;

Tests.pl:

##################first package################
package Test1;
use INCLUDES;
my @array = values(%INC);
print  "@array  \n";

Method1();

##################second package################
package Test2;
use INCLUDES;  #do "INCLUDES.pm";
my @array = values(%INC);
print  "@array  \n";

Method1();

The motive is, only the latest package's Method1() should be used in any Perl file.

The output surprises me. I would expect that both Method1() calls in Tests.pl should be success. But only the first Method1() executes, the second Method1() call says "undefined".

OUTPUT:

C:/Perl/site/lib/sitecustomize.pl PACKAGE1.pm C:/Perl/lib/Exporter.pm PACKAGE2
.pmINCLUDES.pm

PACKAGE2_Method1

C:/Perl/site/lib/sitecustomize.pl PACKAGE1.pm C:/Perl/lib/Exporter.pm PACKAGE2
.pm INCLUDES.pm

Undefined subroutine &Test2::Method1 called at C:\Temp\PackageSample\Tests.pl line 15.

Do somebody have any answers/views on this?

The actual scenario:

the methods in multiple Perl modules will be having same name. But the methods from the High preference perl module should only be used.

For example, if PACKAGE1 contains Method1(), Method2() & PACKAGE2 contains only Method1(), then Method1() should be used from PACKAGE2 & Method2() should be used from PACKAGE1

Basically I want to achieve a Hierarchy among modules based on Preference. Is there any way for this?

回答1:

In Perl, use Module is equivalent to

BEGIN { require Module; Module->import; }

But require caches the list of modules that have been required. It only loads the module once per Perl process. So only the first use IMPORTS does anything. Since your IMPORTS module doesn't have an import method, nothing happens when you use it again.

I'm not quite sure what you're attempting to accomplish. Perhaps your IMPORTS module should be an actual package, with an import method that exports whatever functions you want. That way, each use IMPORTS would export functions into the package that called it.



回答2:

use MyPackage is equivalent to BEGIN{ require MyPackage; MyPackage->import }. Inheriting from Exporter sets up an import class method which does the function "aliasing".

The problem is that you INCLUDES modules does not re-export the modules correctly. This is important because this is the process that imports the functions into the caller namespaces. While this isn't hard to craft on your own, there is a handy module for this purpose Import::Into.

Here is an example contained within a single file, it should be easy enough to reinflate into multiple, the only important difference is in the Includes module. I have made some other superficial changes but those are more for my taste.

#!/usr/bin/env perl

use strict;
use warnings;

package PACKAGE1;

use parent 'Exporter';
our @EXPORT = qw(Method1);

sub Method1 {
  print "PACKAGE1_Method1 \n";
}

package PACKAGE2;

use parent 'Exporter';
our @EXPORT = qw(Method1);

sub Method1 {
  print "PACKAGE2_Method1 \n";
}

package Includes;

use Import::Into;

# uncomment in mulitple files
#use PACKAGE1 ();  # prevent import
#use PACKAGE2 ();  # ditto

sub import {
  my $class = shift;
  my $caller = caller;

  PACKAGE1->import::into( $caller );
  PACKAGE2->import::into( $caller );
}

package Test1;
Includes->import; # in seperate files replace with `use Includes;`

Method1();

package Test2;
Includes->import; # ditto

Method1();

A real world example is the module utf8::all which makes extensive use of this mechanism to load lots of unicode stuff into the caller package.

Edit

To allow importing specific things from the Includes module, you could have it inherit from Exporter as well and craft its @EXPORT and @EXPORT_OK to do what you mean. Otherwise, you could continue with Import::Into and make something like bundles.

sub import {
  my $class  = shift;
  my $bundle = shift;

  my $caller = caller;

  if ($bundle eq 'Bundle1') {
    PACKAGE1->import::into( $caller );
    ... # other things in Bundle1
  } elsif ($bundle eq 'Bundle2') {
    PACKAGE2->import::into( $caller );
    ... # other things in Bundle2
  }
}

Then in your test modules

use Includes 'Bundle1';

In short crafting your own import method is not that hard, and every little is magical about Exporter. Once you learn about symbol table manipulation your don't need it or Import::Into, though it is a slightly more advanced topic. Here is a question I asked about it much earlier in my Perl days: Demystifying the Perl glob (*)

All that said, if object-oriented concepts of inheritance and polymorphism will do the job, you might want to investigate that route too. Here is an example of that:

#!/usr/bin/env perl

use strict;
use warnings;

package PACKAGE1;

sub Method1 {
  my $class = shift;
  print "PACKAGE1_Method1 \n";
}

sub Method2 {
  my $class = shift;
  print "PACKAGE1_Method2 \n";
}

package PACKAGE2;

# if multiple files use this
#use parent 'PACKAGE1';
# rather than
our @ISA = 'PACKAGE1';

# any methods in PACKAGE2 will override those declared in PACKAGE1 

sub Method1 {
  my $class = shift;
  print "PACKAGE2_Method1 \n";
}

package Test1;

# in seperate files need to use
#use PACKAGE2;

PACKAGE2->Method1();
PACKAGE2->Method2();

package Test2;

# ditto
#use PACKAGE1
#use PACKAGE2

PACKAGE2->Method1();
PACKAGE2->Method2();

# you can still use PACKAGE1 and get the originals
PACKAGE1->Method1();
PACKAGE1->Method2();

See now there is no Includes package and no symbols are imported into the Test* namespaces. PACKAGE2 provides Method2 because it inherits from PACKAGE1 and it does not override the method declaration with one of its own.