4
votes

Comment importer une constante avec "use strict", en évitant "Can't use bareword ... as an ARRAY ref"

J'ai un module dans un fichier qui exporte une constante qui est une référence de tableau. Je peux utiliser cette constante dans son module de définition, mais je ne peux pas l'utiliser après l'avoir importée. Le message d'erreur dit Impossible d'utiliser bareword ("AR") comme référence ARRAY alors que "strict refs" est utilisé à la ligne 28 du mod.pl. .

Considérez ce code de démonstration: p>

#!/usr/bin/perl
require 5.018_000;

use warnings;
use strict;

package Test;

use warnings;
use strict;

BEGIN {
    require Exporter;
    our $VERSION = 1.00;                # for version checking
    # Inherit from Exporter to export functions and variables
    our @ISA = qw(Exporter);
    our @EXPORT = qw();                 # exported by default
    our @EXPORT_OK = qw(AR);            # can be optionally exported
}

use constant AR => [1,2,3];

print AR->[1], "\n";
1;

package main;
Test->import(qw(AR));
print AR->[1], "\n";
#Can't use bareword ("AR") as an ARRAY ref while "strict refs" in use at mod.pl line 28.

Comment puis-je résoudre ce problème?


4 commentaires

@zdim: C'est exactement le code présenté dans man perlmod (1) .


ugh, vous avez raison ... quelqu'un devrait éditer ça, pour ne pas confondre les gens. C'est une discussion avec un point, pas de question, et qui avait été d'une pertinence pratique (il y a 20 ans?), Mais ce n'est pas ainsi que nous avons mis en place un module. Voir Exportateur pour une information beaucoup plus simple et plus claire (passe-partout) pour cela. Et de nombreux messages SO bien sûr, à commencer par la réponse d'ikegami ici


@zdim J'ai ouvert un perlbug avec un patch: rt.perl.org/ Ticket / Display.html? Id = 133909


@Grinnz Super, merci. J'ai revu cette partie, et elle est en effet proposée comme un aperçu simple et pratique d'un module (pour ainsi dire). Cela devrait vraiment être corrigé.


3 Réponses :


3
votes

L'instruction print AR -> [1] est analysée pendant la compilation mais la constante AR n'est pas importée dans l'espace de noms main jusqu'à l'exécution.

Le correctif est de s'assurer que AR est importé dans main au moment de la compilation

print &AR->[1], "\n";
print AR()->[1], "\n";

Il existe également des solutions de contournement d'exécution

BEGIN { Test->import( qw(AR) ) }


8 commentaires

Cela ne fonctionne pas en Perl 5.20 J'obtiens un message d'erreur Undefined subroutine & main :: AR lorsque vous utilisez la méthode AR () que vous suggérez


Bizarre, je suis sur Perl 5.20.2 et cela échoue, c'est pourquoi j'ai ajouté ma réponse. Je suppose que quelqu'un a corrigé un bug entre nos deux versions


Les trois solutions fonctionnent correctement dans la version 5.20. En fait, ils fonctionnent très bien dans toutes les versions de 5.6 à présent (5.28) si vous supprimez require 5.018_000; .


@ikegami: Vous souhaitez commenter nécessite 5.018_000 ? Je pensais que cela activait la fonctionnalité de langage jusqu'à Perl 5.18 (qui est la version que j'utilisais).


Cela garantit simplement que la version de l'interpréteur est 5.18+. Totalement inutile pour votre programme


Une autre «solution de contournement d'exécution» serait Test :: AR -> [1]


@Grinnz: Cela ne fonctionnerait-il pas même sans aucune importation?


@ U.Windl Oui, cela ne dépend donc pas de savoir si l'importation se produit au moment de la compilation



1
votes

J'ai changé

# Inherit from Exporter to export functions and variables
use parent 'Exporter';
our $VERSION = 1.00;                # for version checking
our @EXPORT = qw();                 # exported by default
our @EXPORT_OK = qw(AR);            # can be optionally exported

en

BEGIN {
    require Exporter;
    our $VERSION = 1.00;                # for version checking
    # Inherit from Exporter to export functions and variables
    our @ISA = qw(Exporter);
    our @EXPORT = qw();                 # exported by default
    our @EXPORT_OK = qw(AR);            # can be optionally exported
}

et votre code fonctionne maintenant

Je soupçonne qu'il y en a code dans l'Exporter qui doit être exécuté avant de configurer votre @EXPORT_OK variable


1 commentaires

Ce changement n'aide pas du tout.



5
votes

Vous devez exécuter l ' import avant qu'une référence à la constante ne soit compilée.

Vous pouvez utiliser un autre bloc BEGIN pour ce faire, mais cela signifie que nous avons maintenant deux hacks. Plutôt que d'affirmer à la fois l'utilisateur du module et le module lui-même, je suggère l'approche suivante. Cela permet au package intégré de ressembler autant que possible à un module réel.

L'approche consiste en ce qui suit:

  1. Placez le module entier tel quel dans un bloc BEGIN au début du script.
  2. Remplacez le 1; de fin par $ INC {"Foo / Bar.pm"} = 1; (pour Foo :: Bar ).

C'est tout. Cela vous permet d'utiliser le module normalement.

Donc, si votre module est le suivant:

#!/usr/bin/perl

BEGIN {
    package Test;

    use strict;
    use warnings;

    use Exporter qw( import );

    our $VERSION = 1.00;
    our @EXPORT_OK = qw(AR);

    use constant AR => [1,2,3];

    $INC{__PACKAGE__ .'.pm'} = 1;  # Tell Perl the module is already loaded.
}

use 5.018;
use warnings;

use Test qw( AR );

say AR->[1];

Et si votre script est le suivant:

#!/usr/bin/perl
use 5.018;
use warnings;

use Test qw( AR );

say AR->[1];

Vous pouvez utiliser ce qui suit:

package Test;

use strict;
use warnings;

use Exporter qw( import );

our $VERSION = 1.00;
our @EXPORT_OK = qw(AR);

use constant AR => [1,2,3];

1;

Comme vous pouvez le voir, je 'ai fait des nettoyages. Plus précisément,

  • Si vous avez besoin de la version 5.18, vous pouvez également activer les fonctionnalités linguistiques qu'elle fournit. Cela a été fait en remplaçant requis 5.018; par use 5.018;
    • Nous n'avons pas besoin d'utiliser use strict; explicitement car use 5.012; et les restrictions d'activation supérieures.
    • Nous pouvons utiliser say car use 5.010; l'active.
  • Un test n'est pas un exportateur, il ne doit donc pas hériter de l'exportateur. Depuis 15 à 20 ans, Exporter fournit une meilleure interface que celle que vous avez utilisée.
  • Pas besoin de créer ou d'initialiser @EXPORT si vous n'en avez pas besoin.
  • Pas besoin de bloc BEGIN autour de l'initialisation de @ISA et @EXPORT_OK .


9 commentaires

Donc la vraie astuce est $ INC {"Test.pm"} = 1 et en utilisant un test d'utilisation normal? Puis-je demander ce qui ne fonctionne pas avec mon code? En outre, est-il possible de rendre "test.pm" indépendant du nom de fichier utilisé?


Non, j'ai clairement énuméré deux étapes pour «l'astuce», et ce n'est qu'une seule d'entre elles. /// Le nom de la clé dans % INC en fonction du nom du package (c'est-à-dire basé sur le nom du monde du module), pas le nom du script.


@ U.Windl La raison pour laquelle votre code n'a pas fonctionné comme prévu est précisément parce que vous définissez le module dans le même fichier que vous l'utilisez, et au moment de la compilation (lorsque vous souhaitez importer les symboles à utiliser pour plus tard), le module n'a donc pas encore été exécuté, et votre appel import non plus. Si vous conservez les modules dans des fichiers séparés, tout fonctionnera normalement car le module sera compilé, exécuté et importé à partir de la phase de compilation de votre script par l'instruction use - votre code manuel import d'autre part se produit au moment de l'exécution.


@Grinnz: J'ai encore un problème de compréhension: je pensais que use constant ... est évalué au moment de la compilation. Je comprends que le contenu de l'exportateur et le $ INC {"Test.pm"} = 1 doivent être à l'intérieur d'un bloc BEGIN , mais pourquoi tout le reste?


C'est juste beaucoup plus court, plus simple et plus propre d'envelopper tout le module dans un bloc BEGIN. C'est également plus proche de la manière normale de charger un module. Oui, vous pouvez utiliser un bloc normal au lieu d'un bloc BEGIN et utiliser plusieurs blocs BEGIN à l'intérieur (comme indiqué ici ), mais c'est plus long et beaucoup plus compliqué, sujet aux erreurs et compliqué. Cela nécessite également plus d'indentation et plus de frappe. Il n'y a tout simplement aucune raison de le faire de cette façon.


J'ai juste apporté une petite amélioration à votre réponse, mais en fait, j'avais envie de réduire la réponse à son essence: "Fake module load using $ INC {__ MODULE__. '.Pm'} = 1 ; alors vous pouvez utilisez le module normalement. "


Je crois que vous voulez dire $ INC {__ PACKAGE__. '.pm'} = 1; . Mais cela ne fonctionne pas (en général). Vous avez réellement besoin de (my $ mod_path = __PACKAGE__) = ~ s {::} {/} g; $ mod_path. = ".pm"; $ INC {$ mod_path} = 1; . Compte tenu de la complexité, le petit peu de redondance est plutôt palettable.


Vous pouvez utiliser module_notional_filename (__ PACKAGE__) de Module :: Runtime pour le rendre un peu plus propre.


@ U.Windl Ce n'est pas seulement que certaines parties du module doivent être dans un bloc BEGIN; l ' import du module doit également avoir lieu au moment de la compilation. use accomplit cela, faisant ainsi croire à use que le module est déjà chargé est une solution propre; vous pouvez également appeler la méthode d'importation dans un bloc BEGIN.