एक मॉड्यूल में परिभाषित फ़ंक्शन को ओवरराइट करना लेकिन इसके रनटाइम चरण में उपयोग करने से पहले?


20

चलो कुछ बहुत सरल है,

# Foo.pm
package Foo {
  my $baz = bar();
  sub bar { 42 };  ## Overwrite this
  print $baz;      ## Before this is executed
}

क्या ऐसा भी है कि मैं test.plरन कोड से बदल सकता हूं जो स्क्रीन पर कुछ और प्रिंट करने के $bazलिए सेट और बदल जाता है Foo.pm?

# maybe something here.
use Foo;
# maybe something here

क्या संकलक चरणों के साथ ऊपर मुद्रित करने के लिए मजबूर करना संभव है 7?


1
यह एक आंतरिक कार्य नहीं है - यह विश्व स्तर पर सुलभ है Foo::bar, लेकिन use Fooदोनों संकलन चरण (यदि कुछ पहले से परिभाषित किया गया था तो फिर से परिभाषित करना) और फू के रनटाइम चरण दोनों को चलाएंगे। केवल एक चीज जिसके बारे में मैं सोच सकता हूं @INCकि फू लोड होने के तरीके को संशोधित करने के लिए एक गहरी हैकिंग हुक होगी।
ग्रिंज

1
आप पूरी तरह से फ़ंक्शन को फिर से परिभाषित करना चाहते हैं, हाँ? (न केवल अपने ऑपरेशन के हिस्से को बदल दें, उस प्रिंट की तरह?) क्या रनटाइम से पहले रीडिफाइन करने के कुछ विशिष्ट कारण हैं? शीर्षक उस के लिए पूछता है, लेकिन सवाल शरीर कहता है / विस्तृत नहीं है। यकीन है कि आप ऐसा कर सकते हैं लेकिन मैं इस उद्देश्य के बारे में निश्चित नहीं हूं कि क्या यह फिट होगा।
zdim

1
@ ज़दिम हाँ कारण हैं। मैं उस मॉड्यूल के रनटाइम चरण से पहले किसी अन्य मॉड्यूल में उपयोग किए गए फ़ंक्शन को फिर से परिभाषित करने में सक्षम होना चाहता हूं। वास्तव में ग्रिंज ने क्या सुझाव दिया।
इवान कैरोल

@Grinnz क्या यह शीर्षक बेहतर है?
इवान कैरोल

1
एक हैक की आवश्यकता है। require(और इस प्रकार use) दोनों लौटने से पहले मॉड्यूल को संकलित और निष्पादित करते हैं। उसी के लिए जाता है evalevalइसे निष्पादित किए बिना भी कोड संकलित करने के लिए उपयोग नहीं किया जा सकता है।
इकेगामी

जवाबों:


8

एक हैक की आवश्यकता होती है क्योंकि require(और इस प्रकार use) दोनों लौटने से पहले मॉड्यूल को संकलित करते हैं और निष्पादित करते हैं।

उसी के लिए जाता है evalevalइसे निष्पादित किए बिना भी कोड संकलित करने के लिए उपयोग नहीं किया जा सकता है।

कम से कम घुसपैठ समाधान मैंने पाया है कि ओवरराइड होगा DB::postponed। यह एक संकलित आवश्यक फ़ाइल का मूल्यांकन करने से पहले कहा जाता है। दुर्भाग्य से, इसे केवल डिबगिंग ( perl -d) कहा जाता है ।

एक अन्य उपाय यह होगा कि फाइल को पढ़ें, इसे संशोधित करें और संशोधित फाइल का मूल्यांकन करें, निम्न की तरह थोड़े:

use File::Slurper qw( read_binary );

eval(read_binary("Foo.pm") . <<'__EOS__')  or die $@;
package Foo {
   no warnings qw( redefine );
   sub bar { 7 }
}
__EOS__

ऊपर ठीक से सेट नहीं %INCकिया गया है, यह चेतावनी द्वारा उपयोग किए गए फ़ाइल नाम को गड़बड़ कर देता है और इस तरह, यह कॉल नहीं करता है DB::postponed, आदि निम्नलिखित अधिक मजबूत विकल्प हैं:

use IO::Unread  qw( unread );
use Path::Class qw( dir );

BEGIN {     
   my $preamble = '
      UNITCHECK {
         no warnings qw( redefine );
         *Foo::bar = sub { 7 };
      }
   ';    

   my @libs = @INC;
   unshift @INC, sub {
      my (undef, $fn) = @_;
      return undef if $_[1] ne 'Foo.pm';

      for my $qfn (map dir($_)->file($fn), @libs) {
         open(my $fh, '<', $qfn)
            or do {
               next if $!{ENOENT};
               die $!;
            };

         unread $fh, "$preamble\n#line 1 $qfn\n";
         return $fh;
      }

      return undef;
   };
}

use Foo;

मैंने उपयोग किया UNITCHECK(जिसे संकलन के बाद लेकिन निष्पादन से पहले कहा जाता है) क्योंकि मैंने unreadपूरी फाइल में पढ़ने और नई परिभाषा को लागू करने के बजाय ओवरराइड (उपयोग ) को पूर्व निर्धारित किया था। यदि आप उस दृष्टिकोण का उपयोग करना चाहते हैं, तो आप का उपयोग करके वापस जाने के लिए एक फ़ाइल हैंडल प्राप्त कर सकते हैं

open(my $fh_for_perl, '<', \$modified_code);
return $fh_for_perl;

@INCहुक का उल्लेख करने के लिए कुडोस @Grinnz को ।


7

चूँकि यहाँ एकमात्र विकल्प गहराई से हैक होने वाले हैं, हम वास्तव में यहाँ चाहते हैं कि कोड को चलाने के बाद सबरटीन को %Foo::स्टैश में जोड़ा जाए :

use strict;
use warnings;

# bless a coderef and run it on destruction
package RunOnDestruct {
  sub new { my $class = shift; bless shift, $class }
  sub DESTROY { my $self = shift; $self->() }
}

use Variable::Magic 0.58 qw(wizard cast dispell);
use Scalar::Util 'weaken';
BEGIN {
  my $wiz;
  $wiz = wizard(store => sub {
    return undef unless $_[2] eq 'bar';
    dispell %Foo::, $wiz; # avoid infinite recursion
    # Variable::Magic will destroy returned object *after* the store
    return RunOnDestruct->new(sub { no warnings 'redefine'; *Foo::bar = sub { 7 } }); 
  });
  cast %Foo::, $wiz;
  weaken $wiz; # avoid memory leak from self-reference
}

use lib::relative '.';
use Foo;

6

यह कुछ चेतावनियों का उत्सर्जन करेगा, लेकिन प्रिंट 7:

sub Foo::bar {}
BEGIN {
    $SIG{__WARN__} = sub {
        *Foo::bar = sub { 7 };
    };
}

सबसे पहले, हम परिभाषित करते हैं Foo::bar। इसे Foo.pm में घोषणा द्वारा फिर से परिभाषित किया जाएगा, लेकिन "सबरूटीन फू :: बार पुनर्परिभाषित" चेतावनी को ट्रिगर किया जाएगा, जो सिग्नल हैंडलर को कॉल करेगा जो सबरूटीन को फिर से 7 पर लौटने के लिए फिर से परिभाषित करता है।


3
अगर मैंने कभी देखा है तो वेल एक हैक है।
इवान कैरोल

2
यह एक हैक के बिना संभव नहीं है। अगर सबरूटिन को दूसरे सबरूटीन में बुलाया जाता है, तो यह बहुत आसान होगा।
कोरोबा

यह तभी काम करेगा जब लोड किए जा रहे मॉड्यूल में चेतावनी सक्षम हो; Foo.pm चेतावनियों को सक्षम नहीं करता है और इस तरह यह कभी नहीं कहा जाएगा।
22

@ एसआरआरआर: तो इसे कॉल करें perl -w
चेरोबा

@ अचोरोबा: हाँ, यह काम करेगा, जैसे कि हर जगह चेतावनी को सक्षम करेगा, iirc। लेकिन मेरा कहना यह है कि आप सुनिश्चित नहीं हो सकते कि कोई उपयोगकर्ता कैसे चलेगा। उदाहरण के लिए, वन-लाइनर्स आमतौर पर सैंस सख्त या चेतावनी चलाते हैं।
szr

5

यहां एक समाधान है जो मॉड्यूल लोड करने की प्रक्रिया को हुक करने के साथ रीडोनली मॉड्यूल की रीडोनॉली-मेकिंग क्षमताओं को जोड़ती है:

$ cat Foo.pm 
package Foo {
  my $baz = bar();
  sub bar { 42 };  ## Overwrite this
  print $baz;      ## Before this is executed
}


$ cat test.pl 
#!/usr/bin/perl

use strict;
use warnings;

use lib qw(.);

use Path::Tiny;
use Readonly;

BEGIN {
    my @remap = (
        '$Foo::{bar} => \&mybar'
    );

    my $pre = join ' ', map "Readonly::Scalar $_;", @remap;

    my @inc = @INC;

    unshift @INC, sub {
        return undef if $_[1] ne 'Foo.pm';

        my ($pm) = grep { $_->is_file && -r } map { path $_, $_[1] } @inc
           or return undef;

        open my $fh, '<', \($pre. "#line 1 $pm\n". $pm->slurp_raw);
        return $fh;
    };
}


sub mybar { 5 }

use Foo;


$ ./test.pl   
5

1
@ हाइकमी धन्यवाद, मैंने आपके द्वारा सुझाए गए बदलाव किए हैं। अच्छी पकड़।
गॉर्डोनफिश

3

मैंने अपने समाधान को यहाँ संशोधित किया है, ताकि यह अब पर निर्भर न हो, यह Readonly.pmजानने के बाद कि मैं बहुत सरल विकल्प से चूक गया था, एम-कॉनराड के उत्तर के आधार पर , जिसे मैंने उस मॉड्यूलर दृष्टिकोण में शामिल किया है जो मैंने यहां शुरू किया था।

Foo.pm ( आरंभिक पोस्ट के समान )

package Foo {
  my $baz = bar();
  sub bar { 42 };  ## Overwrite this
  print $baz;      ## Before this is executed
}
# Note, even though print normally returns true, a final line of 1; is recommended.

OverrideSubs.pm अपडेट किया गया

package OverrideSubs;

use strict;
use warnings;

use Path::Tiny;
use List::Util qw(first);

sub import {
    my (undef, %overrides) = @_;
    my $default_pkg = caller; # Default namespace when unspecified.

    my %remap;

    for my $what (keys %overrides) {
        ( my $with = $overrides{$what} ) =~ s/^([^:]+)$/${default_pkg}::$1/;

        my $what_pkg  = $what =~ /^(.*)\:\:/ ? $1 : $default_pkg;
        my $what_file = ( join '/', split /\:\:/, $what_pkg ). '.pm';

        push @{ $remap{$what_file} }, "*$what = *$with";
    }

    my @inc = grep !ref, @INC; # Filter out any existing hooks; strings only.

    unshift @INC, sub {
        my $remap = $remap{ $_[1] } or return undef;
        my $pre = join ';', @$remap;

        my $pm = first { $_->is_file && -r } map { path $_, $_[1] } @inc
            or return undef;

        # Prepend code to override subroutine(s) and reset line numbering.
        open my $fh, '<', \( $pre. ";\n#line 1 $pm\n". $pm->slurp_raw );
        return $fh;
   };
}

1;

test-run.pl

#!/usr/bin/env perl

use strict;
use warnings;

use lib qw(.); # Needed for newer Perls that typically exclude . from @INC by default.

use OverrideSubs
    'Foo::bar' => 'mybar';

sub mybar { 5 } # This can appear before or after 'use OverrideSubs', 
                # but must appear before 'use Foo'.

use Foo;

रन और आउटपुट:

$ ./test-run.pl 
5

1

यदि किसी मौजूदा फ़ंक्शन की तुलना में sub barअंदर Foo.pmएक अलग प्रोटोटाइप है Foo::bar, तो पर्ल इसे अधिलेखित नहीं करेगा? यह मामला प्रतीत होता है, और समाधान को काफी सरल बनाता है:

# test.pl
BEGIN { *Foo::bar = sub () { 7 } }
use Foo;

या एक ही तरह की चीज

# test.pl
package Foo { use constant bar => 7 };
use Foo;

अद्यतन: नहीं, इसका कारण यह है कि पर्ल एक "निरंतर" सबरूटीन (प्रोटोटाइप के साथ ()) को फिर से परिभाषित नहीं करेगा , इसलिए यह केवल एक व्यवहार्य समाधान है यदि आपका नकली फ़ंक्शन स्थिर है।


BEGIN { *Foo::bar = sub () { 7 } }के रूप में बेहतर लिखा हैsub Foo::bar() { 7 }
ikegami

1
पुनः " पर्ल एक" निरंतर "सबरूटीन " को फिर से परिभाषित नहीं करेगा , यह सच भी नहीं है। यह सब उप स्थिर होने पर भी उप को 42 तक पुनर्परिभाषित करता है। यहां काम करने का कारण यह है क्योंकि पुनर्वित्त से पहले कॉल इनबिल्ड हो जाता है। यदि इवान ने sub bar { 42 } my $baz = bar();इसके बजाय अधिक सामान्य का उपयोग किया था my $baz = bar(); sub bar { 42 }, तो यह काम नहीं करेगा।
इकेगामी

यहां तक ​​कि बहुत संकीर्ण स्थिति में भी यह काम करता है, यह बहुत शोर है जब चेतावनी का उपयोग किया जाता है। ( Prototype mismatch: sub Foo::bar () vs none at Foo.pm line 5.और Constant subroutine bar redefined at Foo.pm line 5.)
ikegami

1

चलो एक गोल्फ प्रतियोगिता है!

sub _override { 7 }
BEGIN {
  my ($pm)= grep -f, map "$_/Foo.pm", @INC or die "Foo.pm not found";
  open my $fh, "<", $pm or die;
  local $/= undef;
  eval "*Foo::bar= *main::_override;\n#line 1 $pm\n".<$fh> or die $@;
  $INC{'Foo.pm'}= $pm;
}
use Foo;

यह सिर्फ विधि के प्रतिस्थापन के साथ मॉड्यूल के कोड को उपसर्ग करता है, जो कोड की पहली पंक्ति होगी जो संकलन चरण के बाद और निष्पादन चरण से पहले चलती है।

फिर, %INCप्रवेश भरें ताकि भविष्य का भार use Fooमूल में न खींचे।


बहुत अच्छा समाधान है। जब मैंने पहली बार शुरुआत की थी, तब मैंने कुछ इस तरह की कोशिश की थी, लेकिन इंजेक्शन का हिस्सा + BEGIN पहलू याद नहीं आ रहा था जिसे आपने अच्छी तरह से जोड़ा था। मैं इसे अच्छी तरह से अपने उत्तर के मॉड्यूलर संस्करण में शामिल करने में सक्षम था जो मैंने पहले पोस्ट किया था।
गोर्डोन्फ़िश 2

आपका मॉड्यूल डिजाइन के लिए स्पष्ट विजेता है, लेकिन मुझे यह पसंद है जब स्टैकओवरफ्लो भी एक न्यूनतम उत्तर प्रदान करता है।
dataless
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.