Perl 高级技巧:从调度表到代码学习
1. 网络代理设置与使用
在网络编程中,使用
Net::Proxy
模块可以方便地进行代理设置。以下是一个示例代码,展示了如何设置代理并运行:
# show some information on STDERR
Net::Proxy->set_verbosity(1);
# run this on your workstation
my $proxy = Net::Proxy->new(
{ in =>
{
# local port for local SSH client
port => 2222,
type => 'tcp',
},
out =>
{
host => 'home.example.com',
port => 443,
proxy_host => 'proxy.company.com',
proxy_port => 8080,
proxy_user => 'id23494',
proxy_pass => 's3kr3t',
proxy_agent => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows XP)',
},
}
);
$proxy->register( );
Net::Proxy->mainloop( );
使用浏览器访问 HTTPS 服务器时,只需像往常一样操作,因为浏览器已经配置为使用公司代理。
Net::Proxy
发行版中包含的两个脚本
sslh
和
connect - tunnel
分别支持服务器端单端口运行两个服务和客户端通过公司代理。
2. 改进调度表
2.1 基本调度表
调度表以哈希的形式存在,是一种将代码与键关联的有用技术。例如:
my %dispatch =
(
red => sub { return qq{<font color="#ff0000">$_[0]</font>} },
green => sub { return qq{<font color="#00ff00">$_[0]</font>} },
blue => sub { return qq{<font color="#0000ff">$_[0]</font>} },
black => sub { return qq{<font color="#000000">$_[0]</font>} },
white => sub { return qq{<font color="#ffffff">$_[0]</font>} },
);
print $dispatch{black}->('knight');
但这种方法仅适用于键为固定字符串的情况,因为哈希查找依赖于字符串相等性。
2.2 正则表达式键的问题
当使用包含元字符的正则表达式作为键时,简单的哈希查找将失效。例如:
my %dispatch =
(
'\\d' => sub { return "saw a digit" },
'[a-z]' => sub { return "saw a lowercase letter" },
);
查找
$dispatch{5}
不会找到任何匹配项。
2.3 使用
Regexp::Assemble
解决问题
可以使用
Regexp::Assemble
将调度表的所有键组合成一个单一的正则表达式。示例代码如下:
my %dispatch =
(
'\\d' => sub { return "saw a digit" },
'[a-z]' => sub { return "saw a lowercase letter" },
);
my $re = Regexp::Assemble->new->track->add(keys %dispatch);
while (<>)
{
$re->match($_) and print $dispatch{$re->matched}->( );
}
2.4 IRC 机器人示例
以下是一个简单的 IRC 机器人示例,它可以跟踪 karma 和 factoids:
use DispatchBot;
my $bot = DispatchBot->new(
server => "irc.perl.org",
port => "6667",
channels => ["#bottest"],
nick => 'rebot',
);
$bot->run( );
package DispatchBot;
use strict;
use Regexp::Assemble;
use Bot::BasicBot;
use YAML qw(LoadFile DumpFile);
use vars qw( $VERSION @ISA );
$VERSION = '0.03';
@ISA = 'Bot::BasicBot';
my $factoid = _load( 'factoid.dat' ); # "foo" is "bar" factoids
my $karma = _load( 'karma.dat' ); # keep track of foo++ and foo--
sub _load
{
my $file = shift;
return -e $file ? LoadFile($file) : { };
}
sub _save
{
my ($dictionary, $file) = @_;
DumpFile( $file, $dictionary );
}
sub _flush
{
_save( $factoid, 'factoid.dat' );
_save( $karma, 'karma.dat' );
}
END { _flush }
my %dispatch =
(
# define a factoid
'(\\S+) is (.*)$' => sub { $factoid->{$_[0]} = $_[1]; _flush; return },
# query a factoid
'(\\S+)\s*\\?$' => sub
{
exists $factoid->{$_[0]}
and return "I believe that $_[0] is $factoid->{$_[0]}"
},
# drop a factoid
'forget (\\S+)$'=> sub
{
if (exists $factoid->{$_[0]})
{
my $message = "I forgot $_[0]";
delete $factoid->{$_[0]};
_flush;
return $message;
}
},
# karma shifts
'(\\S+)\\+\\+' => sub { $karma->{$_[0]}++; _flush; return },
'(\\S+)--' => sub { $karma->{$_[0]}--; _flush; return },
# karma query
'^karma (\\S+)$' => sub
{
return exists $karma->{$_[0]}
? "$_[0] has karma of $karma->{$_[0]}"
: "$_[0] has neutral karma"
},
# time... to die
'^!quit$' => sub { exit },
);
my $re = Regexp::Assemble->new->track->add(keys %dispatch);
sub said
{
my ($self, $arg) = @_;
$re->match($arg->{body})
and return $dispatch{$re->matched}->($re->capture);
return;
}
3. 跟踪近似值
3.1 浮点数的近似性
浮点数本质上是近似的,Perl 在硬件上以最准确的方式表示数字,但通常最多只能达到约 16 位有效数字。例如:
my $dx = 21123000000000000000000000000000000000000000000000000;
my $rate = 1.23e12;
my $end = ( 23 * $dx - $rate * 230 - 2.34562516 ** 2 - 0.5 ) ** 0.33;
在 32 位机器上,这个计算可能不准确。
3.2 区间算术
区间算术是一种跟踪数值计算准确性的简单技术,它将每个值表示为一个范围(最小值到最大值)。例如:
sqrt( [1.2, 1.3] ) -> [1.095445, 1.140175]
[1.2, 1.3] * [-1, 0.9] -> [-1.3, 1.17]
区间算术的优点是,只要注意舍入误差,精确结果总是保证在生成的区间内,并且区间越小,计算结果越精确。
3.3 让 Perl 使用区间表示
可以通过
Number::Intervals
模块让 Perl 将每个浮点数表示为一个区间:
package Number::Intervals;
# Compute maximal error in the representation of a given number...
sub _eps_for
{
my ($num, $epsilon) = (shift) x 2; # copy arg to both vars
$epsilon /= 2 while $num + $epsilon/2 != $num; # whittle epsilon down
return $epsilon;
}
# Create an interval object, allowing for representation errors...
sub _interval
{
use List::Util qw( min max );
my ($min, $max) = ( min(@_), max(@_) );
return bless [$min - _eps_for($min), $max + _eps_for($max)], __PACKAGE__;
}
# Convert all floating-point constants to interval objects...
sub import
{
use overload;
overload::constant(
float => sub
{
my ($raw, $cooked) = @_;
return _interval($cooked);
},
);
}
4. 重载运算符
4.1 问题引入
Number::Intervals
模块虽然有用,但在使用时可能会出现问题。例如:
use Number::Intervals;
my $avogadro = 6.02214199e23; # standard physical constant
my $atomic_mass = 55.847; # atomic mass of iron
my $mass = 100; # mass in grams
my $count = int( $mass * $avogadro/$atomic_mass );
print "Number of atoms in $mass grams of iron = $count\n";
结果不准确,原因是 Perl 将区间对象的数组引用转换为整数地址进行计算。
4.2 重载算术运算符
可以使用
overload
模块重载
Number::Intervals
对象的算术运算符:
use overload
(
# Add two intervals by independently adding minima and maxima...
q{+} => sub
{
my ($x, $y) = _check_args(@_);
return _interval($x->[0] + $y->[0], $x->[1] + $y->[1]);
},
# Subtract intervals by subtracting maxima from minima and vice versa...
q{-} => sub
{
my ($x, $y) = _check_args(@_);
return _interval($x->[0] - $y->[1], $x->[1] - $y->[0]);
},
# Multiply intervals by taking least and greatest products...
q{*} => sub
{
my ($x, $y) = _check_args(@_);
return _interval($x->[0] * $y->[0], $x->[1] * $y->[0],
$x->[1] * $y->[1], $x->[0] * $y->[1],
);
},
# Divide intervals by taking least and greatest quotients...
q{/} => sub
{
my ($x, $y) = _check_args(@_);
return _interval($x->[0] / $y->[0], $x->[1] / $y->[0],
$x->[1] / $y->[1], $x->[0] / $y->[1],
);
},
# Exponentiate intervals by taking least and greatest powers...
q{**} => sub
{
my ($x, $y) = _check_args(@_);
return _interval($x->[0] ** $y->[0], $x->[1] ** $y->[0],
$x->[1] ** $y->[1], $x->[0] ** $y->[1],
);
},
# Integer value of an interval is integer value of bounds...
q{int} => sub
{
my ($x) = @_;
return _interval(int $x->[0], int $x->[1]);
},
# Square root of interval is square roots of bounds...
q{sqrt} => sub
{
my ($x) = @_;
return _interval(sqrt $x->[0], sqrt $x->[1]);
},
# Unary minus: negate bounds and swap upper/lower:
q{neg} => sub
{
my ($x) = @_;
return _interval(-$x->[1], -$x->[0]);
},
# etc. etc. for the other arithmetic operators...
);
sub _check_args
{
my ($x, $y, $reversed) = @_;
return $reversed ? ( _interval($y), $x )
: ref $y ne __PACKAGE__ ? ( $x, _interval($y) )
: ( $x, $y );
}
4.3 字符串化和数值化处理
为了让 Perl 正确处理区间对象的字符串化和数值化,还需要添加额外的处理:
use overload
(
# Stringify intervals as: VALUE (±UNCERTAINTY)...
q{""} => sub
{
my ($self) = @_;
my $uncert = ($self->[1] - $self->[0]) / 2;
use charnames qw( :full );
return $self->[0]+$uncert . " (\N{PLUS-MINUS SIGN}$uncert)";
},
# Numerify intervals by averaging their bounds (with warning)...
q{0+} => sub
{
my ($self) = @_;
carp "Approximating interval by a single (averaged) number";
return ($self->[0] + $self->[1]) /2;
},
);
经过这些处理后,浮点数计算可以正确进行,并且自动跟踪和报告准确性。
5. 从混淆代码中学习
5.1 Perl 的创意玩法
Perl 以其严肃的娱乐性而闻名,如 Perl 高尔夫(用最少的字符解决问题)、JAPHS(以创造性的方式打印简单消息)和代码混淆(编写奇怪但能实现惊人功能的代码)。虽然这些技巧不会用于生产代码,但编写这些创意程序需要仔细研究和探索。
5.2 混淆代码示例
以下是一个在 Perl Monks 上发布的混淆代码示例:
#!/usr/bin/perl # how to (ab)use substr
use warnings;
use strict;
my $pi='3.14159210535152623346475240375062163750446240333543375062';
substr ($^X,0)=
substr ($pi,-6);map{
substr ($^X,$.++,1)=chr(
substr ($pi,21,2)+
substr ($pi,$_,2))}(12,28,-18,-6,-10,14);map{$^O=$"x(
substr ($pi,-5,2));
substr ($^O,sin(++$a/8)*32+
substr ($pi,-2)/2+1,1)=$_;
substr ($^O,sin($a/4)*(
substr ($pi,2,2))+
substr ($pi,-7,-5)-1,1)=$_;print"$^O$/";eval($^X.('$b,'x3).
substr ($pi,-3,1).'.'.
substr ($pi,9,2));}(map{chr($_+
substr ($pi,21,2))}(
substr ($pi,8)x6)=~/../g);
这个代码虽然看起来奇怪,但可以引发不同水平程序员的思考。例如:
- 初学者可能会问:这是什么语言?这真的是一个能运行的计算机程序吗?
- 有一定 Perl 经验的人可能会问:Perl 允许这样奇怪的格式而不报错吗?能创建这么多位数精度的数字吗?
substr
函数是做什么的,和 C 语言中的函数类似吗?
- 更有经验的程序员可能会问:只有一个
print
语句且没有
for
或
while
循环,动画是如何实现的?可以把
substr
放在赋值语句左边吗?
substr
有两个和三个参数的形式吗?程序里的
select
调用是做什么的?为什么
use strict;
和
use warnings;
没有报错?为什么
$a
和
$b
不需要用
my
声明?
当遇到 Perl 的创意代码时,可以使用
B::Deparse
工具拆解代码,并仔细研究
perldoc perlfunc
和
perldoc perlvar
来了解代码的工作原理。然后思考如何利用 Perl 的丰富功能来创建自己的代码。
通过以上这些 Perl 技巧,我们可以在网络编程、数值计算、运算符重载等方面提升编程能力,同时从创意代码中学习到更多的 Perl 知识。
6. 技巧总结与应用场景
6.1 技巧总结
| 技巧名称 | 主要功能 | 关键模块或方法 |
|---|---|---|
| 网络代理设置 | 方便进行网络代理配置,支持通过公司代理访问服务器 |
Net::Proxy
模块,
new
、
register
、
mainloop
方法
|
| 改进调度表 | 解决正则表达式作为键时哈希查找失效的问题 |
Regexp::Assemble
模块,
new
、
track
、
add
、
match
方法
|
| 跟踪近似值 | 避免浮点数计算中的舍入误差,保证计算结果的准确性 |
Number::Intervals
模块,
_eps_for
、
_interval
方法
|
| 重载运算符 | 使对象在不同运算中表现符合预期,正确处理区间对象的运算、字符串化和数值化 |
overload
模块,
constant
方法,重载各种运算符的子例程
|
| 从混淆代码学习 | 通过分析混淆代码,深入理解 Perl 语言特性和编程技巧 |
无特定模块,使用
B::Deparse
工具和
perldoc perlfunc
、
perldoc perlvar
文档
|
6.2 应用场景
-
网络编程
:在需要通过代理服务器访问网络资源的场景中,使用
Net::Proxy模块可以轻松实现代理设置,确保网络连接的正常进行。 - 文本处理与消息响应 :在开发 IRC 机器人等需要根据不同消息模式做出响应的程序中,改进调度表的技巧可以高效地处理各种消息匹配和响应逻辑。
- 科学计算 :在涉及浮点数计算的科学研究、工程计算等领域,跟踪近似值和重载运算符的技巧可以避免因浮点数近似性导致的计算误差,保证计算结果的准确性。
- 代码学习与探索 :分析混淆代码可以帮助程序员深入了解 Perl 语言的各种特性和编程技巧,提升编程能力和思维方式。
7. 操作流程梳理
7.1 网络代理设置操作流程
graph LR
A[引入 Net::Proxy 模块] --> B[设置代理信息]
B --> C[创建代理对象]
C --> D[注册代理]
D --> E[进入主循环运行代理]
7.2 改进调度表操作流程
graph LR
A[定义调度表哈希] --> B[使用 Regexp::Assemble 组合键为正则表达式]
B --> C[读取输入数据]
C --> D[进行正则匹配]
D --> E{是否匹配成功}
E -- 是 --> F[调用对应代码块]
E -- 否 --> C
7.3 跟踪近似值与重载运算符操作流程
graph LR
A[引入 Number::Intervals 模块] --> B[将浮点数常量转换为区间对象]
B --> C[进行数值计算]
C --> D[重载运算符处理区间对象运算]
D --> E[处理区间对象的字符串化和数值化]
E --> F[输出准确计算结果]
8. 学习建议与拓展
8.1 学习建议
- 实践操作 :对于每个技巧,都要亲自编写代码进行实践,通过调试和运行代码来加深对技巧的理解。
-
阅读文档
:深入阅读相关模块的文档,了解模块的详细功能和使用方法,如
Net::Proxy、Regexp::Assemble、Number::Intervals等模块的文档。 - 分析优秀代码 :除了混淆代码,还可以分析一些开源的优秀 Perl 项目代码,学习他人的编程思路和技巧。
8.2 拓展方向
- 结合其他模块 :可以将这些技巧与其他 Perl 模块结合使用,如数据库操作模块、网络爬虫模块等,实现更复杂的功能。
- 开发实用工具 :利用所学技巧开发一些实用的工具,如自动化测试工具、数据处理工具等,提升工作效率。
- 参与开源项目 :参与 Perl 开源项目的开发,与其他开发者交流合作,不断提升自己的编程水平。
通过对这些 Perl 高级技巧的学习和应用,我们可以在不同的编程场景中更加得心应手,充分发挥 Perl 语言的优势,实现各种复杂的功能。同时,不断探索和学习新的技巧和方法,将有助于我们成为更优秀的 Perl 程序员。
超级会员免费看
15

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



