2010-07-26 10 views
8

BiorącWykrywanie zadeklarowane zmienne pakietu w Perl

# package main; 
our $f; 
sub f{} 
sub g {} 
1; 

Jak mogę określić, że $f, ale nie $g, został uznany? Poza mankietem pomyślałem, że *{main::g}{SCALAR} może być niezdefiniowany, ale jest to bona fide SCALAR ref.

Tło: Chciałbym zaimportować zmienną do main::, ale karp lub rechot, jeśli ta zmienna jest już zadeklarowana.

EDYCJA Dodano podprogram f w odpowiedzi na wstępną odpowiedź @ DVK.

ODPOWIEDŹ (2010-07-27)

To nie jest łatwe, ale jest możliwe.

An eval technique jest najbardziej przenośny, działa na perls starszych niż 5.10. W nowszych perłach introspekcyjne moduły, takie jak Devel::Peek i B, mogą rozróżniać.

Odpowiedz

1

Dałem z siebie wszystko, nawet starając się zapytać eval STRING czy $main::f zostało zadeklarowane przez our lub my. (Wymagało to oszukania, zamknięcia i późniejszego przywrócenia wartości STDERR w celu zmniejszenia chattiness.) Po zmianie pakietów deklaracje te nie są już widoczne podczas tymczasowego przełączania.

Poniższa technika wykryje czy $f została uznana przez

use vars qw/ $f /; 

poniższy kod:

package MyModule; 

use warnings; 
use strict; 

# using $f will confuse the compiler, generating 
# warnings of 'Variable "%f" is not available' 
# although we're going for $main::f 
my $__f = "from MyModule"; 

my %IMPORT_OK = (
    '$f' => [f => \$__f], 
); 

sub import { 
    my($pkg,@imports) = @_; 
    my $callpkg = caller; 

    die "I don't speak your dirty Pig-Latin" 
    if $callpkg !~ /\A\w+(::\w+)*\z/ || 
     grep !/\A[\[email protected]%]\w+\z/, @imports; 

    foreach my $name (@imports) { 
    my($sym,$ref) = @{ $IMPORT_OK{$name} || [] }; 
    die "unknown import: $name" unless $sym; 

    open my $saverr, ">&", \*STDERR or die "dup STDERR: $!"; 
    close STDERR; 

    my $declared = eval qq{ 
     package $callpkg; 
     my(undef)=$name; 
     1; 
    }; 

    open STDERR, ">&", $saverr or print "restore STDERR: $!"; 
    die "${callpkg}::$sym already exists" if $declared; 

    { 
     no strict 'refs'; 
     *{$callpkg . "::" . $sym} = $ref; 
    } 
    } 
} 

1; 
+0

+1, i zwykle nie pobudzam stringish eval() s. :) To jest mniej więcej moje obecne podejście. Co ważne, sprawdzanie eval tutaj * nie * wywołuje metody FETCH tie() d skalar - to byłoby No Good (tm). Zastanawiam się, czy lokalne() izing $ SIG {__ WARN__} może zająć się komunikatami o błędach? – pilcrow

+0

Tak, FWIW, w moich próbach jeśli zlokalizować _ \ _ WARN \ _ \ _ obsługi (i $ @ też z grzeczności) przed eval, ciszę błędy bez deskryptora pliku duppery. – pilcrow

4

STRESZCZENIE

W tym momencie, po dość intensywnych badań, jestem twardym stanowisku, że w sytuacji, gdy wpis w tablicy symboli z nazwą „X” został ogłoszony, ale nie przypisanego jest niemożliwe do ogólnego odróżnienia, który z typów odniesienia w glob został faktycznie zadeklarowany bez użycia głębokiego sondowania Devel :: stuff.

Innymi słowy, można powiedzieć tylko następujące 2 odrębne sytuacje:

  1. X nie został zadeklarowany w ogóle (wpis w tablicy symboli nie istnieje)

  2. X został ogłoszony, a niektóre typy globów zostały faktycznie przypisane.

    W tym drugim przypadku,

    • Ty może znaleźć jakie rodzaje glob zostały przydzielone, a które nie były

    • Ale, nie może dowiedzieć się, które z nieprzepisane do globu typy zostały zadeklarowane, a nieprzypisane, które nie zostały w ogóle zadeklarowane.

    Innymi słowy, dla our $f = 1; our @f;; możemy stwierdzić, że $main::f jest skalarem; , ale nie możemy stwierdzić, czy zostały zadeklarowane @f i %f - nie da się tego odróżnić od our $f = 1; our %f;.

    Należy pamiętać, że definicje podprogramów są również zgodne z tą drugą regułą, ale deklarowanie nazwy subdomena automatycznie przypisuje jej wartość (blok kodu), więc nigdy nie można mieć nazwy podrzędnej w "zadeklarowanej, ale nieprzypisanej" stan (uwaga: może nie być prawdą w przypadku prototypów - nie ma pojęcia).

ORIGINAL ODPOWIEDŹ

Cóż, bardzo ograniczona (i IMHO nieco kruchy) rozwiązanie do odróżnienia skalar z podprogramu może być użycie UNIVERSAL :: mogą:

use strict; 
our $f; 
sub g {}; 
foreach my $n ("f","g","h") { 
    # First off, check if we are in main:: namespace, 
    # and if we are, that we are a scalar 
    no strict "refs"; 
    next unless exists $main::{$n} && *{"main::$n"}; 
    use strict "refs"; 
    # Now, we are a declared scalr, unless we are a executable subroutine: 
    print "Declared: \$$n\n" unless UNIVERSAL::can("main",$n) 
} 

Rezultat :

Declared: $f 

Uwaga: {SCALAR} wydaje się nie działać, aby wyleczyć nie-skalary w moim testowaniu - szczęśliwie przeszedł przez @A i %H, gdy je zadeklarowałem i dodałem do pętli.

UPDATE

Próbowałem podejście Brian d Foy jest z rozdziałem 8 „Mastering Perl” i jakoś nie był w stanie zmusić go do pracy dla skalarów, skrótów lub tablice; ale jak wspomniano poniżej draegtun działa dla podprogramów lub zmiennych , które zostały przypisane do już:

> perl5.8 -we '{use strict; use Data::Dumper; 
    our $f; sub g {}; our @A=(); sub B{}; our $B; our %H=(); 
    foreach my $n ("f","g","h","STDOUT","A","H","B") { 
     no strict "refs"; 
     next unless exists $main::{$n}; 
     print "Exists: $n\n"; 
     if (defined ${$n}) { print "Defined scalar: $n\n"}; 
     if (defined @{$n}) { print "Defined ARRAY: $n\n"}; 
     if (defined %{$n}) { print "Defined HASH: $n\n"}; 
     if (defined &{$n}) { print "Defined SUB: $n\n"}; 
     use strict "refs";}}'  

Exists: f 
Exists: g 
Defined SUB: g   <===== No other defined prints worked 
Exists: STDOUT 
Exists: A 
Exists: H 
Exists: B 
Defined SUB: B   <===== No other defined prints worked 
+0

+1 Doskonały próbę. FWIW, nie jestem pewien, że \ * {name} {SCALAR} * jest zawsze fałszywe * - jest to test istniejący (który można wykonać 'strict' -yy), który pomija" h ". Sprawdzanie to jednak nie powiedzie się, jeśli zdefiniowano również podf {}. – pilcrow

+0

@pilcrow - tak, dokładnie to miałem na myśli w ostatnim akapicie. I masz rację co do '$ f' +' & f' ... ponieważ powiedziałem, że to było trochę kruche i ograniczone. – DVK

+1

@pilcrow @DVK Od perlref: "' * foo {THING} 'zwraca' undef', jeśli ta konkretna RZECZYWISTOŚĆ nie została jeszcze użyta, z wyjątkiem przypadku skalarów. '* Foo {SCALAR}' zwraca odniesienie do anonimowy skalar, jeśli '$ foo' nie został jeszcze wykorzystany. Może się to zmienić w przyszłej wersji." To sprawia, że ​​'& main :: f' versus' $ main :: f' jest kłopotliwym przypadkiem. –

0

można sprawdzić dla określonego podprogramu tak:

say 'g() defined in main' if defined &{'main::g'}; 

Niestety tylko ta sama metoda działa na zmiennej pakiet, jeśli wartość została przypisana:

our $f = 1; 
say '$f defined with value in main' if defined ${'main::f'}; 

/I3az/

1

Devel :: Peek wydaje się być w stanie odróżnić używanych i nieużywanych rzeczy w gnieździe SKALAR:

use strict; 
use warnings; 
use Devel::Peek; 

our $f; 
sub f { } 
sub g { } 

Dump(*f); 
Dump(*g); 

wyjście jest:

SV = PVGV(0x187360c) at 0x182c0f4 
    REFCNT = 3 
    FLAGS = (MULTI,IN_PAD) 
    NAME = "f" 
    NAMELEN = 1 
    GvSTASH = 0x24a084 "main" 
    GP = 0x1874bd4 
    SV = 0x182c0a4 
    REFCNT = 1 
    IO = 0x0 
    FORM = 0x0 
    AV = 0x0 
    HV = 0x0 
    CV = 0x24a234 
    CVGEN = 0x0 
    LINE = 6 
    FILE = "c:\temp\foo.pl" 
    FLAGS = 0xa 
    EGV = 0x182c0f4 "f" 
SV = PVGV(0x187362c) at 0x18514dc 
    REFCNT = 2 
    FLAGS = (MULTI,IN_PAD) 
    NAME = "g" 
    NAMELEN = 1 
    GvSTASH = 0x24a084 "main" 
    GP = 0x1874cbc 
    SV = 0x0 
    REFCNT = 1 
    IO = 0x0 
    FORM = 0x0 
    AV = 0x0 
    HV = 0x0 
    CV = 0x1865234 
    CVGEN = 0x0 
    LINE = 8 
    FILE = "c:\temp\foo.pl" 
    FLAGS = 0xa 
    EGV = 0x18514dc "g" 

Linie zainteresowania są w sekcji GP = , w szczególności SV, AV, HV i CV (odpowiednio skalar, tablica, hasz i kod). Zauważ, że zrzut *g pokazuje SV = 0x0. Niestety, nie wydaje się, aby był to programowy sposób uzyskiwania tych informacji. Metoda tępego podejścia polegałaby na przechwytywaniu wyjścia o wartości Dump() i analizowaniu go.

+1

i starszych Perls (przed 5.10) będzie zawsze mieć coś w skalarnego gniazda ... W nowszych perls, można przetestować za '$ {B :: svrev_2object (\\ * f) -> SV} == 0 ' – ysth

3

Starsze Perls (przed 5.10) zawsze będzie miał coś w skalarnego gniazda.

Na nowszych perłach wygląda na to, że stare zachowanie jest naśladowane, gdy próbuje się wykonać * FOO {SCALAR}.

można użyć modułu B introspekcję sprawdzić skalarnego gniazdo, choć:

# package main; 
our $f; 
sub f {} 
sub g {} 

use B; 
use 5.010; 
if (${ B::svref_2object(\*f)->SV }) { 
    say "f: Thar be a scalar tharrr!"; 
} 
if (${ B::svref_2object(\*g)->SV }) { 
    say "g: Thar be a scalar tharrr!"; 
} 

1; 
Powiązane problemy