Perl编程技巧:提升你的代码能力
1. 生成序列
在Perl中,我们通常可以使用简单的语法
@count_up = 0..100;
来生成递增的整数序列。但如果我们想要更复杂的序列,比如以2为步长计数或者倒序计数,就需要自己创造语法了。
Perl中的尖括号有两种用途:作为
readline
的简写和
glob
的简写。我们可以利用这一点,将非
readline
的尖括号用于列表推导式。
以下是具体操作步骤:
1. 安装新的
glob
函数:
package Glob::Lists;
use Carp;
# Regexes to parse the extended list specifications...
my $NUM = qr{\s* [+-]? \d+ (?:\.\d*)? \s* }xms;
my $TO = qr{\s* \.\. \s*}xms;
my $FILTER = qr{ (?: : (.*) )? }xms;
my $ABtoZ = qr{\A ($NUM) (,) ($NUM) ,? $TO ($NUM) $FILTER \Z}xms;
my $AZxN = qr{\A ($NUM) $TO ($NUM) (?:x ($NUM))? $FILTER \Z}xms;
# Install a new glob( ) function...
no warnings 'redefine';
*CORE::GLOBAL::glob = sub
{
my ($listspec) = @_;
# Does the spec match any of the acceptable forms?
croak "Bad list specification: <$listspec>"
if $listspec !~ $ABtoZ && $listspec !~ $AZxN;
# Extract the range of values and any filter...
my ($from, $to, $incr, $filter) = $2 eq ',' ? ($1, $4, $3-$1, $5)
: ($1, $2, $3, $4);
# Work out the implicit increment, if no explicit one...
$incr = $from > $to ? -1 : 1 unless defined $incr;
# Check for nonsensical increments (zero or the wrong sign)...
my $delta = $to - $from;
croak sprintf "Sequence <%s, %s, %s...> will never reach %s",
$from, $from+$incr, $from+2*$incr, $to
if $incr == 0 || $delta * $incr < 0;
# Generate list of values (and return it, if not filter)...
my @vals = map { $from + $incr * $_ } 0..($delta/$incr);
return @vals unless defined $filter;
# Apply the filter before returning the values...
$filter =~ s/\b[A-Z]\b/\$_/g;
return eval "grep {package ".caller."; $filter } \@vals";
};
- 使用新的语法生成序列:
use Glob::Lists;
for ( <1..100 x 7> ) {...} # 1, 8, 15, 22,...85, 92, 99
my @even_countdown = <10,8..0>; # 10, 8, 6, 4, 2, 0
my @fract_countdown = <10,9.5,..0>; # 10, 9.5, 9,...1, 0.5, 0
my @some_primes = <1..100 x 3 : /7/ && is_prime(N)>;
# 7, 37, 67, 73, 79, 97
2. 减少错误检查代码
在与外部世界交互时,程序可能会遇到各种错误,如磁盘空间不足、网络连接丢失等。为了使程序更健壮,我们通常需要检查这些错误,但这会导致大量重复的代码。
Perl的
Fatal
核心模块可以帮助我们在不显式检查错误的情况下处理错误。以下是具体操作步骤:
1. 引入
Fatal
模块并指定要覆盖的函数:
use Fatal qw( open close );
open( my $fh, '>', '/invalid_directory/invalid_file' );
print {$fh} "Hello\n";
close $fh;
-
如果需要更优雅地处理错误,可以使用
eval块:
use Fatal qw( open close );
eval {
open( my $fh, '>', '/invalid_directory/invalid_file' );
print {$fh} "Hello\n";
close $fh;
};
die "File error: $!" if $@;
-
在自己的模块中使用
Fatal模块:
package MyCode;
sub succeed { 1 }
sub fail { 0 }
use Fatal qw( :void succeed fail );
succeed( );
fail( );
1;
3. 返回更智能的值
在Perl中,返回值的上下文有列表上下文、标量上下文和空上下文。在标量上下文中,我们可以返回多种类型的值,如字符串、计数、布尔值等,但Perl没有提供足够的上下文来帮助我们决定返回什么。
Contextual::Return
模块可以帮助我们解决这个问题。以下是一个示例:
use Time::HiRes 'time';
use Contextual::Return;
my $elapsed = 0;
my $started_at = 0;
my $is_running = 0;
# Convert elapsed seconds to HH::MM::SS string...
sub _HMS
{
my ($elapsed) = @_;
my $hours = int($elapsed / 3600);
my $mins = int($elapsed / 60 % 60);
my $secs = int($elapsed) % 60;
return sprintf "%02d:%02d:%02d", $hours, $mins, $secs;
}
sub stopwatch
{
my ($run) = @_;
# Update elapsed time...
my $now = time( );
$elapsed += $now - $started_at if $is_running;
$started_at = $now;
# Defined arg turns stopwatch on/off, undef arg resets it...
$is_running = $run if @_;
$elapsed = 0 if @_ && !defined $run;
# Handle different scalar contexts...
return
NUM { $elapsed }
STR { _HMS( $elapsed ) }
BOOL { $is_running }
}
4. 返回动态值
Contextual::Return
模块还有一个强大的功能,即返回的标量值可以是动态的。动态值每次被计算时都会自适应。
以下是一个示例:
use Contextual::Return;
use Time::HiRes qw( sleep time ); # Allow subsecond timing
# Subroutine returns an active timer value...
sub timer
{
my $start = time; # Set initial start time
return VALUE # Return an active value that...
{
my $elapsed = time - $start; # 1. computes elapsed time
$start = time; # 2. resets start time
return $elapsed; # 3. returns elapsed time
}
}
# Create an active value...
my $process_timer = timer( );
# Use active value...
while (1)
{
do_some_long_process( );
print "Process took $process_timer seconds\n";
}
5. 添加自定义Perl语法
有时候,Perl的语法可能无法满足我们的需求,我们可以尝试添加自定义语法。
例如,在定义子例程时,我们希望可以直接指定参数列表,但Perl没有提供这样的语法。我们只能自己处理参数:
sub convert_to_base
{
my ($base, $number) = @_; # <-- DIY parameter list
my $converted = ''
while ($number > 0)
{
$converted = $NUMERAL_FOR[$number % $base] . $converted;
$number = int( $number / $base);
}
return $converted;
}
以下是一个流程图,展示了生成序列的大致流程:
graph TD;
A[输入序列规范] --> B{规范是否匹配};
B -- 是 --> C[提取范围和过滤器];
B -- 否 --> D[抛出错误];
C --> E{是否有显式增量};
E -- 是 --> F[使用显式增量];
E -- 否 --> G[计算隐式增量];
F --> H{增量是否合理};
G --> H;
H -- 是 --> I[生成值列表];
H -- 否 --> D;
I --> J{是否有过滤器};
J -- 是 --> K[应用过滤器];
J -- 否 --> L[返回值列表];
K --> L;
通过以上这些技巧,我们可以提升Perl代码的效率和可维护性,让我们的编程工作更加轻松。
Perl编程技巧:提升你的代码能力
6. 各技巧总结与对比
为了更清晰地了解上述几种Perl编程技巧,下面通过表格进行总结对比:
| 技巧名称 | 作用 | 示例代码 | 优点 |
| — | — | — | — |
| 生成序列 | 自定义生成复杂整数序列 |
perl use Glob::Lists; for ( <1..100 x 7> ) {...}
| 可灵活生成不同步长、顺序的序列,支持过滤 |
| 减少错误检查代码 | 减少错误检查的重复代码 |
perl use Fatal qw( open close ); open( my $fh, '>', '/invalid_directory/invalid_file' );
| 简化错误处理,使代码更简洁 |
| 返回更智能的值 | 根据不同上下文返回合适的值 |
perl sub stopwatch {... return NUM { $elapsed } STR { _HMS( $elapsed ) } BOOL { $is_running } }
| 提高返回值的灵活性和适用性 |
| 返回动态值 | 返回值可自动更新 |
perl sub timer {... return VALUE { my $elapsed = time - $start; $start = time; return $elapsed; } }
| 方便跟踪动态信息,如时间间隔 |
| 添加自定义Perl语法 | 扩展Perl语法以满足特定需求 |
perl sub convert_to_base { my ($base, $number) = @_; ... }
| 使代码更符合特定业务逻辑,提高可读性 |
7. 技巧的综合应用
在实际编程中,我们可以综合运用这些技巧来解决复杂的问题。例如,我们要编写一个程序来监控一系列任务的执行时间,并将结果记录到文件中。
以下是具体的实现步骤:
1. 使用
timer
函数创建动态计时器:
use Contextual::Return;
use Time::HiRes qw( sleep time );
sub timer
{
my $start = time;
return VALUE
{
my $elapsed = time - $start;
$start = time;
return $elapsed;
}
}
-
使用
Fatal模块处理文件操作的错误:
use Fatal qw( open close );
- 定义任务列表并执行任务:
my $task_timer = timer();
my @tasks = ('task1', 'task2', 'task3');
open(my $log_fh, '>', 'task_log.txt');
for my $task (@tasks)
{
print "Performing $task...\n";
print {$log_fh} "Performing $task...\n";
do_task($task);
print "Finished $task in $task_timer seconds\n";
print {$log_fh} "Finished $task in $task_timer seconds\n";
}
close $log_fh;
sub do_task
{
my ($task) = @_;
# 模拟任务执行
sleep(rand(2));
}
8. 注意事项
在使用这些技巧时,也有一些注意事项需要我们关注:
-
生成序列
:在使用自定义序列语法时,要确保序列规范的正确性,否则会抛出错误。例如,序列的增量不能为零或方向错误。
-
减少错误检查代码
:在使用
Fatal
模块时,要注意函数的声明顺序。如果在函数声明之前使用
Fatal
模块,可能会导致模块无法找到函数而抛出错误。
-
返回更智能的值
:在使用
Contextual::Return
模块时,要确保不同上下文的返回值逻辑正确,避免出现意外的结果。
-
返回动态值
:动态值的计算逻辑要清晰,避免出现逻辑错误导致值计算不准确。
-
添加自定义Perl语法
:自定义语法要符合整体代码的风格和逻辑,避免使代码变得过于复杂难以维护。
以下是一个流程图,展示了综合应用技巧的大致流程:
graph TD;
A[开始] --> B[创建动态计时器];
B --> C[引入Fatal模块];
C --> D[定义任务列表];
D --> E[打开日志文件];
E --> F{是否有任务};
F -- 是 --> G[执行任务];
G --> H[记录任务执行时间];
H --> F;
F -- 否 --> I[关闭日志文件];
I --> J[结束];
通过掌握这些Perl编程技巧,并注意相关的注意事项,我们可以编写出更高效、更健壮、更易维护的代码,提升我们的编程能力。
超级会员免费看
1092

被折叠的 条评论
为什么被折叠?



