Perl Moose: атрибут устанавливается только когда упоминается в подпрограмме BUILD

У меня есть сценарий, который рекурсивно создает имена подкаталогов / файлов каталога и имена файлов в этих подкаталогах в виде объектов:

package Dir;
use Moose;
use Modern::Perl;
use File;
use strict;
use warnings;

has 'path' => (is => 'ro', isa => 'Str', required => 1); 
has 'name' => (is => 'ro', isa => 'Str', lazy => 1, default => sub { my $self = shift; my ($name) = $self->path =~ /\/([^\/]*)$/; return $name; } );
has 'subdirs' => (is => 'rw', isa => 'ArrayRef[Dir]' );  
has 'files' => (is => 'rw', isa => 'ArrayRef[File]' );  
has 'num_dirs' => (is => 'ro', isa => 'Int', lazy => 1, default => sub { my $self = shift; scalar @{$self->subdirs}; } );


sub BUILD {
  my $self = shift;
  my $path = $self->path;

  # run some tests
  logf('Path to the directory does not exist.')             if (!-e $path);
  logf('The path should point to a directory, not a file.') if (!-d $path);

  # populate subdirs attribute with Dir objects
  opendir my $dh, $path or die "Can't opendir '$path': $!";

  # Get files and dirs and separate them out into categories
  my @dirs_and_files = grep { ! m{^\.$|^\.\.$} } readdir $dh;
  closedir $dh or die "Can't closedir '$path': $!";
  my @subdir_names        = grep { -d "$path/$_" } grep { !m{^\.}  } @dirs_and_files;
  my @file_names          = grep { -f "$path/$_" } grep { !m{^\.}  } @dirs_and_files;

  # Create objects
  my @dir_objects =          map { Dir->new  ( path => $path . '/' . $_ ) } @subdir_names;
  my @file_objects =         map { File->new ( path => $path . '/' . $_ ) } @file_names;

  # Populate this with file and directory objects
  $self->subdirs             ( \@dir_objects );
  $self->files               ( \@file_objects );
}

1;

Обратите внимание, что код имеет files атрибут, который содержит массив File объекты. File имеет следующие атрибуты:

has 'path' => (is => 'ro', isa => 'Str', required => 1); 
has 'name' => (is => 'ro', isa => 'Str', lazy => 1, default => sub { my $self = shift; my ($name) = $self->path =~ /\/([^\/]*)$/; return $name; } );

Проблема в том, что name атрибут никогда не устанавливается, когда File объект создан. Я не уверен почему.

РЕДАКТИРОВАТЬ 1: Решение (вроде) Итак, я ударил это в File объект, чтобы увидеть, вызвало ли это создание атрибута:

sub BUILD {
  my $self = shift;
}

Это не решило проблему. Тем не менее, это сделал:

sub BUILD {
  my $self = shift;
  $self->name;
}

У меня вопрос, однако, почему я должен был это сделать?

3 ответа

Атрибуты с lazy => 1 создаются только когда вызывается их метод доступа, а не после создания.

Просто примечание:

Вы неправильно утверждаете, что путь не существует, если у вас нет разрешения на родительский каталог. Кроме того, вы неправильно утверждаете, что путь к каталогу не тот, если у вас нет разрешения на его родительский каталог.

Вы также без необходимости stat файл дважды. На самом деле, вам не нужно stat файл вообще с тех пор opendir уже делает проверки, которые вы делаете.

Просто замени

logf('Path to the directory does not exist.')             if (!-e $path);
logf('The path should point to a directory, not a file.') if (!-d $path);

opendir my $dh, $path or die "Can't opendir '$path': $!";

с

opendir(my $dh, $path)
   or do {
      logf("Can't open directory \"$path\": $!");
      die("Can't open directory \"$path\": $!");
   };

Это также позволяет избежать состояния гонки в вашем коде, вероятность того, что положение вещей может измениться между проверками и opendir,

Проблема в том, что ваш паттерн не работает, если есть косая черта.

my ($name) = $self->path =~ /\/([^\/]*)$/;

Если $self->path является /some/thing оно работает. Если это /some/thing/ это "работает", но [^\/]* счастливо совпадает с пустой строкой Так что вы не получите предупреждение.

Вы можете добавить дополнительную косую черту и изменить ее так, чтобы она соответствовала ОДНОЙ или нескольким не косым чертам. Также, используя альтернативные разделители, мы можем очистить все эти зубочистки.

my ($name) = $self->path =~ m{/ ([^/]+) /? $}x;

Но на самом деле не следует разбирать пути с помощью регулярных выражений. Используйте один из множества встроенных модулей, таких как File:: Basename или File:: Spec.

return basename($self->path);

Некоторые примечания стороны.

Moose очень медленно запускается и лучше всего подходит для длительных процессов, таких как веб-серверы. Для чего-то общего, такого как класс File и Dir, рассмотрите возможность использования Moo. Он в основном совместим с Moose, гораздо быстрее, и при использовании вместе с Types:: Standard делает типы лучше. Например, было бы хорошо создать тип StrNotEmpty, чтобы избежать подобных проблем.

Если это не упражнение, в Perl уже есть отличный модуль для подобных вещей. Посмотрите в Path:: Tiny.

Другие вопросы по тегам