bennymack has asked for the wisdom of the Perl Monks concerning the following question:

Dear Perl Monks,

I have what appears to be a fairly difficult to solve problem with Attribute::Handlers. When a module that has some attributes defined is required at runtime, the attribute handling code does not run. I've looked through the Attribute::Handlers source and it seems like it only does its thing in a couple of specific compiler phases, namely CHECK and INIT (and END too.) So after those are complete it simply does not do anymore attribute handling.

I put together an example of what I am trying to get to work and posed the question via paste bin on the irc.freenode.net #perl channel. Here is the code for your viewing convenience:

############### SuperClass.pm package SuperClass; use strict; use warnings; use Attribute::Handlers(); # Keeping this simple for now. sub WrapSub : ATTR(CODE) { my( $class, $typeglob, $referent, ) = @_; my( $package_sub ) = sprintf '%s::%s', $class, *{ $typeglob }{NAME +}; my $wrap_sub = sub { warn 'before'; $referent->( @_ ); warn 'after'; }; warn 'WrapSub wrapping: ', $package_sub; no warnings 'redefine'; *{ $typeglob } = $wrap_sub; return; } 1; #################### SubClass.pm package SubClass; use strict; use warnings; use parent 'SuperClass'; sub foo : WrapSub { warn 'fooooo'; } 1; #!/usr/bin/env perl #################### test_attribute_handlers_compiletime.pl use strict; use warnings; use SubClass(); SubClass->foo; __END__ $ ./test_attribute_handlers_compiletime.pl WrapSub wrapping: SubClass::foo at SuperClass.pm line 17. before at SuperClass.pm line 12. fooooo at SubClass.pm line 7. after at SuperClass.pm line 14. #!/usr/bin/env perl #################### test_attribute_handlers_runtime.pl use strict; use warnings; use UNIVERSAL::require(); SubClass->require; SubClass->foo; __END__ $ ./test_attribute_handlers_runtime.pl fooooo at SubClass.pm line 7.

Is there a simple or not-so-simple solution to this? Such as modifying A::H, using a different handler module, or writing my own MODIFY_CODE_ATTRIBUTES method? Thanks in advance!

Replies are listed 'Best First'.
Re: Attribute Handlers don't run when called at run time.
by tilly (Archbishop) on Sep 04, 2008 at 15:44 UTC
    This is exactly why I personally avoid attributes. That's a kind of code fragility that I don't like. Particularly since I like using Apache::Reload. If there is an alternate solution, I'm interested to hear it. But I just avoid using attributes.

    In your case I might do something like this in the parent class (untested):

    sub wrap_sub { my ($class, $function) = @_; no strict 'refs'; no warnings 'redefine'; my $referent = \&{"$class\::$function"}; my $wrap_sub = sub { warn 'before'; $referent->( @_ ); warn 'after'; }; warn "wrap_sub wrapping: $class\::$function"; no warnings 'redefine'; *{"$class\::$function"} = $wrap_sub; return; }
    And then in your child class you write:
    sub foo { warn 'fooooo'; } __PACKAGE_->wrap_sub("foo");

      Tilly, that's actually very similar to what I've been doing in the meantime to work around this. I just wanted to look for a solution that would let me use the cleaner looking attributes

      Here is an actual example of my work around:

      sub _is_inactive { my( $self ) = @ARG; my( $realword ) = $self->realword; return $realword =~ $self->CHEVRON_QR; } install_sub( { code => __PACKAGE__->hash_instance_cache( 'sub' => '_is_inactive' +), as => 'is_inactive' } );

      As you may have guessed, I'm using all this to automatically cache the return value of the sub in the hash based object instance. A different issue altogether I'm sure ;) I just found myself working with references to hash values which can be repetitive and error prone so I abstracted it into an attribute but the that is also abstracted into something that can be used separately from attribute handlers.

        Three responses.

        My first one is that this smells like micro-optimization. In general micro-optimization is a bad idea. Keep your code clean and wait until you have a documented issue before you try to optimize it.

        The second is that if you're only doing one thing with the sub, you can massively simplify your API to the following:

        install_sub("is_inactive", sub { my( $self ) = @ARG; my( $realword ) = $self->realword; return $realword =~ $self->CHEVRON_QR; });
        The third piece of advice is that when I have to work with hashes I sometimes find it very useful to use Hash::Util's lock_keys method to avoid trying to access a key that isn't there. Think of it as strict.pm for hashes. Sure, it is slower. But it is easy to make turning it on conditional. (Though unless I really need the performance, I usually just leave it on.)