Perl - data retrieval with hash of objects - perl

I have a class with getter and setter methods. I am creating a hash of objects of this class.
my %hash;
$hash{'foo'} = Myclass->new();
$hash{'bar'} = Myclass->new();
...
With a created object I am trying to set the data to a particular attribute of the package. It is successful and doesn't show any issues. But if I try to retrieve the data, the last value that is set is returned.
Code:
#!/usr/bin/perl
package Metadata;
my $myname = "";
sub new {
my $type = shift;
my $self = {};
bless $self, $type;
return $self;
}
sub setMyname {
my ($self, $tempName) = #_;
$myname = $tempName;
}
sub getMyname {
return $myname;
}
package main;
use YAML::XS 'LoadFile';
use Data::Dumper;
my %objHash = ();
my #list;
my $myname;
my $i = 0;
my #conf = LoadFile('input.yml');
my $config = \#conf;
foreach ( #conf ) {
$list[$i] = $config->[$i]->{mykey};
$objHash{$list[$i]} = Metadata->new();
$myname = $config->[$i]->{myname};
$objHash{$list[$i]}->setMyname($myname);
my $host = $objHash{$list[$i]}->getMyname();
$i++;
}
my $host = $objHash{$list[0]}->getMyname();
print $host;
print "\n";
my $host = $objHash{$list[1]}->getMyname();
print $host;
print "\n";
my $host = $objHash{$list[2]}->getMyname();
print $host;
print "\n";
YAML:
---
mykey: 1
myname: John
---
mykey: 2
myname: Doe
----
mykey: 3
myname: Chris
...
Expected output
John
Doe
Chris
Actual output
Chris
Chris
Chris
Am I missing anything?

You're storing the names of every object in $myname instead of in the object!
package Metadata;
use strict;
use warnings qw( all );
sub new {
my ($class) = #_;
my $self = bless({}, $class);
$self->{name} = undef;
return $self;
}
sub set_name {
my ($self, $name) = #_;
$self->{name} = $name;
}
sub get_name {
my ($self) = #_;
return $self->{name};
}
1;

Related

How can a perl constructor return a value not just a hashref

I want to create a Perl OO module to return a value like DateTime does, but don't know how to it right now. Anyone's help on this will be appreciated.
Below looks like what I wanted:
use DateTime;
use Data::Printer;
my $time = DateTime->now();
print $time . "\n";
print ref $time;
# p $time;
Output:
2022-11-23T13:22:39
DateTime
What I got:
package Com::Mfg::Address;
use strict;
use warnings;
#constructor
sub new {
my ($class) = #_;
my $self = {
_street => shift || "undefined",
_city => shift || "undefined",
_las_state => shift || "undefined",
_zip => shift || "undefined",
};
bless $self, $class;
return $self;
}
#accessor method for street
sub street {
my ( $self, $street ) = #_;
$self->{_street} = $street if defined($street);
return ( $self->{_street} );
}
#accessor method for city
sub city {
my ( $self, $city ) = #_;
$self->{_city} = $city if defined($city);
return ( $self->{_city} );
}
#accessor method for state
sub state {
my ( $self, $state ) = #_;
$self->{_state} = $state if defined($state);
return ( $self->{_state} );
}
#accessor method for zip
sub zip {
my ( $self, $zip ) = #_;
$self->{_zip} = $zip if defined($zip);
return ( $self->{_zip} );
}
sub print {
my ($self) = #_;
printf( "Address:%s\n%s, %s %s\n\n",
$self->street, $self->city, $self->state, $self->zip );
}
1;
# test.pl
#!/usr/bin/perl -w
use strict;
use Data::Printer;
BEGIN {
use FindBin qw($Bin);
use lib "$Bin/../lib";
}
use Com::Mfg::Address;
my $homeAddr = Com::Mfg::Address->new('#101 Road', 'LA', 'CA', '111111');
print $homeAddr;
# $homeAddr->print();
# p $homeAddr;
But this only gives me:
Com::Mfg::Address=HASH(0xb89ad0)
I am curious if print $homeAddr can give me:
something like #101Road-LA-CA-111111 and it really is object like above print $time . "\n";.
I tried to review DateTime source but still have no clue right now.
You're asking how to provide a custom stringification for the object. Use the following in your module:
use overload '""' => \&to_string;
sub to_string {
my $self = shift;
return
join ", ",
$self->street,
$self->city,
$self->state,
$self->zip;
}
This makes
print $homeAddr;
equivalent to
print $homeAddr->to_string();

Is it possible to register a function to preprocess log messages with Log::Log4perl?

In this example:
$logger->debug({
filter => \&Data::Dumper::Dumper,
value => $ref
});
I can pretty print my references instead of ARRAY(0xFFDFKDJ). But it's too boring to type that long code every time. I just want:
$logger->preprocessor({
filter => \&Data::Dumper::Dumper,
value => $ref
});
$logger->debug( $ref, $ref2 );
$logger->info( $array );
And $ref, $ref2, and $array will be dumped by Data::Dumper.
It there a way to do this?
UPD
With help of your answers I do the patch
Now you just:
log4perl.appender.A1.layout=FallbackLayout
log4perl.appender.A1.layout.chain=PatternLayout
log4perl.appender.A1.layout.chain.ConversionPattern=%m%n
log4perl.appender.A1.warp_message = sub { $#_ = 2 if #_ > 3; \
return #_; }
# OR
log4perl.appender.A1.warp_message = main::warp_my_message
sub warp_my_message {
my( #chunks ) = #_;
use Data::Dump qw/ pp /;
for my $msg ( #chunks ) {
$msg = pp $msg if ref $msg;
}
return #chunks;
}
UPD2
Or you can use this small module
log4perl.appender.SomeAPP.warp_message = Preprocess::Messages::msg_filter
log4perl.appender.SomeAPP.layout = Preprocess::Messages
package Preprocess::Messages;
sub msg_filter {
my #chunks = #_;
for my $msg ( #chunks ) {
$msg = pp $msg if ref $msg;
}
return #chunks;
};
sub render {
my $self = shift;
my $layout = Log::Log4perl::Layout::PatternLayout->new(
'%d %P %p> %c %F:%L %M%n %m{indent=2}%n%n'
);
$_[-1] += 1; # increase level of the caller
return $layout->render( join $Log::Log4perl::JOIN_MSG_ARRAY_CHAR, #{ shift() }, #_ );
}
sub new {
my $class = shift;
$class = ref ($class) || $class;
return bless {}, $class;
}
1;
Yes, of course you can set 'warp_message = 0' and combine msg_filter and render together.
log4perl.appender.SomeAPP.warp_message = 0
log4perl.appender.SomeAPP.layout = Preprocess::Messages
sub render {
my($self, $message, $category, $priority, $caller_level) = #_;
my $layout = Log::Log4perl::Layout::PatternLayout->new(
'%d %P %p> %c %F:%L %M%n %m{indent=2}%n%n'
);
for my $item ( #{ $message } ) {
$item = pp $item if ref $item;
}
$message = join $Log::Log4perl::JOIN_MSG_ARRAY_CHAR, #$message;
return $layout->render( $message, $category, $priority, $caller_level+1 );
}
The easy way: use warp_message
The easiest way to do this is to create a custom appender and set the warp_message parameter so you can get the original references that were passed to the logger:
package DumpAppender;
use strict;
use warnings;
use Data::Dumper;
$Data::Dumper::Indent = 0;
$Data::Dumper::Terse = 1;
sub new {
bless {}, $_[0];
}
sub log {
my($self, %params) = #_;
print ref($_) ? Dumper($_) : $_ for #{ $params{message} };
print "\n";
}
package main;
use strict;
use warnings;
use Log::Log4perl;
Log::Log4perl->init(\q{
log4perl.rootLogger=DEBUG,Dump
log4perl.appender.Dump=DumpAppender
log4perl.appender.Dump.layout=NoopLayout
log4perl.appender.Dump.warp_message=0
});
my $logger = Log::Log4perl->get_logger;
$logger->debug(
'This is a string, but this is a reference: ',
{ foo => 'bar' },
);
Output:
This is a string, but this is a reference: {'foo' => 'bar'}
Unfortunately, if you take this approach, you're stuck writing your own code to handle layouts, open files, etc. I wouldn't take this approach except for very simple projects that only need to print to screen.
A better way: composite appender
A better approach is to write your own composite appender. A composite appender forwards messages on to another appender after manipulating them somehow, e.g. filtering or caching them. With this approach, you can write only the code for dumping the references and let an existing appender do the heavy lifting.
The following shows how to write a composite appender. Some of this is explained in the docs for Log::Log4perl::Appender, but I copied much of it from Mike Schilli's Log::Log4perl::Appender::Limit:
package DumpAppender;
use strict;
use warnings;
our #ISA = qw(Log::Log4perl::Appender);
use Data::Dumper;
$Data::Dumper::Indent = 0;
$Data::Dumper::Terse = 1;
sub new {
my ($class, %options) = #_;
my $self = {
appender => undef,
%options
};
# Pass back the appender to be limited as a dependency to the configuration
# file parser.
push #{ $options{l4p_depends_on} }, $self->{appender};
# Run our post_init method in the configurator after all appenders have been
# defined to make sure the appenders we're connecting to really exist.
push #{ $options{l4p_post_config_subs} }, sub { $self->post_init() };
bless $self, $class;
}
sub log {
my ($self, %params) = #_;
# Adjust call stack so messages are reported with the correct caller and
# file
local $Log::Log4perl::caller_depth = $Log::Log4perl::caller_depth + 2;
# Dump all references with Data::Dumper
$_ = ref($_) ? Dumper($_) : $_ for #{ $params{message} };
$self->{app}->SUPER::log(
\%params,
$params{log4p_category},
$params{log4p_level}
);
}
sub post_init {
my ($self) = #_;
if(! exists $self->{appender}) {
die "No appender defined for " . __PACKAGE__;
}
my $appenders = Log::Log4perl->appenders();
my $appender = Log::Log4perl->appenders()->{$self->{appender}};
if(! defined $appender) {
die "Appender $self->{appender} not defined (yet) when " .
__PACKAGE__ . " needed it";
}
$self->{app} = $appender;
}
package main;
use strict;
use warnings;
use Log::Log4perl;
Log::Log4perl->init(\q{
log4perl.rootLogger=DEBUG, Dump
log4perl.appender.Dump=DumpAppender
log4perl.appender.Dump.appender=SCREEN
log4perl.appender.SCREEN=Log::Log4perl::Appender::Screen
log4perl.appender.SCREEN.layout=PatternLayout
log4perl.appender.SCREEN.layout.ConversionPattern=%d %p %m%n
});
my $logger = Log::Log4perl->get_logger;
$logger->debug(
'This is a string, but this is a reference: ',
{ foo => 'bar' },
);
Output:
2015/09/14 13:38:47 DEBUG This is a string, but this is a reference: {'foo' => 'bar'}
Note that you have to take some extra steps if you initialize Log::Log4perl via the API instead of via a file. This is documented in the composite appenders section of the Log::Log4perl::Appender documentation.

Perl: Subroutines first argument is not the class

I'm relatively new to Perl so bear with me.
My sub getGenes calls getFeaturesByGeneName in the class SequenceModule. The first loop runs fine however in the second loop it tries to invoke get_SeqFeatures (a BioPerl sub) on the string $name meaning that it skips my $self = shift.
What am I missing?
sub getGenes
{
my #names = shift;
my $genome = shift;
my #cds;
foreach my $name (#names)
{
my $feat = SequenceModule -> getFeatureByGeneName($genome, $name);
push (#cds, $feat);
}
return #cds;
}
...
sub getFeatureByGeneName
{
my $self = shift;
my $seq = shift;
my $name = shift;
my #cds = $seq -> get_SeqFeatures("CDS");
...
}
Speculation: you called getGenes with several names:
getGenes(('name1', 'name2'), $genome);
List don't nest in Perl, so the arguments are flattened:
getGenes('name1', 'name2', $genome);
shift can't return more than one element. Therefore,
my #names = shift;
is equivalent to
my #names;
$names[0] = shift;
The second name is still in #_, so it goes to $genome:
my $genome = shift;
If you need to pass a list to a sub, make it the last argument, or send a reference:
sub getGenes {
my $genome = shift;
my #names = #_;
}
getGenes($genome, 'name1', 'name2');
# OR
sub getGenes {
my $names = shift;
my $genome = shift;
for my $name (#$names) { # dereference
...
}
}
getGenes(['name1', 'name2'], $genome);

Why does perl object instance overwrite each other

I've written some Perl code which compose two classes inherent from a base one. I suppose it would print something like this
Mik: Meow! Meow!
Sat: Woof! Woof!
But it actually print this way:
Sat: Woof! Woof!
Sat: Woof! Woof!
,
package Animal;
sub new {
my $obj = shift;
my $name = shift;
our %pkg = ( 'name' => $name );
bless \%pkg, $obj;
return \%pkg;
}
package Cat;
#ISA = ("Animal");
sub new {
my $obj = shift;
my $name = shift;
my $self = $obj->SUPER::new($name);
return $self;
}
sub get_name {
my $obj = shift;
return $obj->{'name'};
}
sub talk {
my $obj = shift;
return "Meow! Meow!";
}
package Dog;
#ISA = ("Animal");
sub new {
my $obj = shift;
my $name = shift;
my $self = $obj->SUPER::new( $name );
return $self;
}
sub get_name {
my $obj = shift;
return $obj->{'name'};
}
sub talk {
my $obj = shift;
return "Woof! Woof!";
}
package Main;
my $cat = new Cat('Mike');
my $dog = new Dog('Sat');
print $cat->get_name() . ": " . $cat->talk() , "\n";
print $dog->get_name() . ": " . $dog->talk() , "\n";
But if I change the caller in this way, it prints what I suppose to be. So it is quite strange why the $cat object was overwritten after the $dog was instantiated?
package Main;
my $cat = new Cat('Mily');
print $cat->get_name() . ": " . $cat->talk() , "\n";
my $dog = new Dog('Sat');
print $dog->get_name() . ": " . $dog->talk() , "\n";
Why do you bless into a global variable? Change your constructor to:
sub new {
my $obj = shift;
my $name = shift;
my %pkg = ( 'name' => $name );
bless \%pkg, $obj;
return \%pkg;
}
Better yet, change it to something more idiomatic:
sub new {
my $class = shift;
my $name = shift;
my $self = { name => $name };
return bless $self, $class;
}
Moving on:
Why implement new and get_name in each kind of animal? Both methods can be inherited. While we're at it, we might as well get rid off the messing around with #ISA:
package Animal;
sub new {
my $class = shift;
my $name = shift;
my $self = { name => $name };
return bless $self, $class;
}
sub get_name {
my $self = shift;
return $self->{'name'};
}
package Cat;
use base qw/ Animal /;
sub talk {
my $self = shift;
return "Meow! Meow!";
}
package Dog;
use base qw/ Animal /;
sub talk {
my $self = shift;
return "Woof! Woof!";
}
package Main;
my $cat = Cat->new('Mike');
my $dog = Dog->new('Sat');
print $cat->get_name() . ": " . $cat->talk() , "\n";
print $dog->get_name() . ": " . $dog->talk() , "\n";
May I ask which tutorial or book you are following?
While the above is perfectly fine, you might as well do it the Modern Perl way:
package Animal;
use Moose;
has name => ( required => 1, is => 'rw', isa => 'Str' );
package Cat;
use Moose;
extends 'Animal';
has talk => ( default => "Meow! Meow!", is => 'ro' );
package Dog;
use Moose;
extends 'Animal';
has talk => ( default => "Woof! Woof!", is => 'ro' );
package Main;
my $cat = Cat->new( name => 'Mike');
my $dog = Dog->new( name => 'Sat');
print $cat->name . ": " . $cat->talk , "\n";
print $dog->name . ": " . $dog->talk , "\n";
You have declared the variable to store the instance data using
our %pkg
This is an alias for a single data structure (%Animal::pkg), so all your objects are using the same hash. Change our to my in order to create a new hash each time.
It might be worth noting that "inside-out" objects in Perl can and do use a shared data structure in the package to store instance data, but there is an additional level of abstraction required to make that work, and I wouldn't recommend starting OO Perl with them, they are an acquired taste.
In a nutshell: our declares package variables, so every time our %pkg = (...) is executed, you assign a new value to the same variable. As all \%pkg references point to the same var, all return values of new are the same object. A reference can only be blessed into one class, so the last one wins.
Just change the our to my, and it should work as expected.

Can't locate object method "add" via package "Heap"

I'm not sure why perl isn't recognizing the Heap's method add. Getting message given in question title. Here are the most relevant files.
#!/usr/bin/perl -w
use strict;
use Util;
use Heap;
use HuffTree;
my $heap = Heap->new;
my $test = 3;
$heap->add($test); # <--------ERROR HERE-----------
package Heap;
use strict;
use warnings;
use POSIX ();
sub new {
my $class = shift;
my $self = { "aref" => [""],
"next" => 1,
#_};
bless $self, $class;
}
sub print {
my $self = shift;
my $next = $self->{"next"};
my $aref = $self->{"aref"};
print "array => #$aref\n";
print "next => $next\n";
}
sub compare {
my ($self, $i, $j) = #_;
my $x = $self->{"aref"}[$i];
my $y = $self->{"aref"}[$j];
if (!defined $x) {
if (!defined $y) {
return 0;
} else {
return -1;
}
}
return 1 if !defined $y;
return $x->priority <=> $y->priority;
}
sub swap {
my ($self, $i, $j) = #_;
my $aref = $self->{"aref"};
($aref->[$i], $aref->[$j]) = ($aref->[$j], $aref->[$i]);
}
sub add {
my ($self, $value) = #_;
my $i = $self->{"next"};
$self->{"aref"}[$i] = $value;
while ($i > 1) {
my $parent = POSIX::floor($i/2);
last if $self->compare($i, $parent) <= 0;
$self->swap($i, $parent);
$i = $parent;
}
$self->{"next"}++;
}
sub reheapify {
my ($self, $i) = #_;
my $left = 2 * $i;
my $right = 2 * $i + 1;
my $winleft = $self->compare($i, $left) >= 0;
my $winright = $self->compare($i, $right) >= 0;
return if $winleft and $winright;
if ($self->compare ($left, $right) > 0) {
$self->swap($i, $left);
$self->reheapify($left);
} else {
$self->swap($i, $right);
$self->reheapify($right);
}
}
sub remove {
my $self = shift;
my $aref = $self->{"aref"};
my $result = $aref->[1];
$aref->[1] = pop #$aref;
$self->{"next"}--;
$self->reheapify(1);
return $result;
}
sub empty {
my $self = shift;
return $self->{"next"} == 1;
}
1;
package HuffTree;
use warnings;
use strict;
use Pair;
our #ISA = "Pair";
sub priority {
my $self = shift;
# lowest count highest priority
return -$self->{frequency};
}
sub left {
my $self = shift;
return $self->{left};
}
sub right {
my $self = shift;
return $self->{right};
}
1;
package Pair;
use warnings;
use strict;
sub new {
my $class = shift;
my $self = { #_ };
bless $self, $class;
}
sub letter {
my $self = shift;
return $self->{letter};
}
sub frequency {
my $self = shift;
return $self->{frequency};
}
sub priority {
my $self = shift;
return $self->{frequency};
}
1;
package Util;
use strict;
use warnings;
sub croak { die "$0: #_: $!\n"; }
sub load_arg_file {
my $path_name = shift #ARGV;
my $fh;
open($fh, $path_name) || croak "File not found.\n";
return $fh;
}
1;
You have a Heap.pm installed from CPAN. That's what gets loaded, not your own Heap.pm. The new sub in the Heap.pm from CPAN looks like this:
sub new {
use Heap::Fibonacci;
return &Heap::Fibonacci::new;
}
Which is actually a bug in said module, because Heap::Fibonacci uses the
standard bless \$h, $class; thing in its new sub,
so the reference is blessed into the Heap package, which
does indeed not have a sub called add (Heap::Fibonacci does).
To solve your immediate problem, you can:
make sure that your module is picked up before the "other" Heap (by modifying #INC with use lib, for example;
or not reinvent the wheel and actually use Heap::Fibonacci).
At any rate, it might be a good idea to report this problem
to the Heap module author - because even if you did not have
your own Heap.pm, your code would still fail with the same message.