我如何知道 Perl 方法属于哪个包?
我正在处理一些遗留代码,其中使用了
多个(!)XML 解析模块。
这意味着具有相同名称的方法正在互相践踏。
有没有一种简单的方法来判断特定方法属于哪个包?
Possible Duplicate:
In Perl, how can I check from which module a given function was imported?
I'm working with some legacy code where more than one (!) XML-parsing module has been use
d.
This means that methods with the same name are trampling on one another.
Is there an easy way to tell which package a particular method belongs to?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
简单且最可能正确的答案是,最后加载的导出给定函数的模块将是其函数被使用的模块。
当导出函数(或其他符号)时,接收包的符号表会被修改。因此,如果两个导入函数对表进行更改,则最后的更改将被保留。
回想一下,
use Foo;
或多或少相当于BEGIN { require Foo; Foo->import 如果 Foo->can(import); }
。由于
import
只是一个具有特殊名称的子例程,唯一的限制是 J. Random Hacker 扭曲的想象力。import
可以是任何代码。它可以做任何事,也可以什么都不做。例如,它可以包含确保没有已定义的函数被覆盖的逻辑。但是,除非有任何奇怪的情况,最后加载的将是正在使用的。
为了在技术上 100% 正确,
use Foo;
相当于BEGIN { require Foo; Foo->导入; }
。只是这段代码并不像您期望的那样工作。
具体代码与我的示例有所不同。
上面的代码的作用与方法解析在 Perl 中的工作方式有关。
通常,调用
Foo->some_sub
,其中some_sub
不存在,并且AUTOLOAD
函数在Foo
,some_sub
将由AUTOLOAD
函数处理。import
有一种特殊情况,可以免除AUTOLOAD
检查。请参阅perlsub 关于自动加载。在检查(或实际上未检查)
AUTOLOAD
之后,我们检查继承。对于原始包的@ISA
中的每个项目,都会重复对匹配函数或AUTOLOAD
函数进行相同的检查。这是一个递归过程,包括父级的父级等等,直到检查整个继承树 -@ISA
按从左到右、深度优先的顺序检查。在此期间,AUTOLOAD
异常一直存在,导入
时将跳过该异常。最终,如果没有找到匹配的方法,我们就会求助于通用基类 UNIVERSAL。如果该函数存在于
UNIVERSAL
中,则会调用该函数,否则我们会抛出一个异常,表示无法找到该函数。因此,在我们的
Foo->import;
调用中,将调用UNIVERSAL::import
来处理这项工作。那么,这个函数有什么作用呢?在 Perl 5.12.2 中,代码如下所示:
请注意,如果未在
UNIVERSAL
上调用该函数,该函数所做的第一件事就是退出。现在我所说的一切都是正确的,只要您不做以下几件事:
覆盖方法解析顺序。如果你这样做,那么方法解析将会发生,无论你如何定义它。无论我们是否到达 UNIVERSAL,或者何时到达,都完全悬而未决,取决于您的想法。
覆盖
UNIVERSAL::import
。你可以猴子补丁 UNIVERSAL::import 来做任何你想做的事。同样,它的行为方式完全取决于您的想法。因此,我上面给出的半等效代码只是所发生情况的简写。我认为这会更容易理解,因为它不需要了解 Perl 如何做事的那么多细节,但它并不 100% 等同于实际发生的情况。做意想不到的事情打破了等价性。
我的代码与完全相同的代码不同
此外,我的代码调用
Foo->can
,它通常会回退到UNIVERSAL::can
。在正常的事件链中,没有任何地方可以
被调用。当人们考虑 Perl 中的can
问题时,这一点变得尤其棘手。can
可以被继承图中的任何类覆盖或重新实现。哪个可以
被调用取决于方法解析顺序。多重继承的所有问题都适用于此。can
看不到自动加载的函数。由于自动加载不适用于导入,这看起来似乎没什么大不了的。问题是,如果您使用自动加载,则最好重载can
来考虑这一点。因此,这使上述问题变得更加复杂。最好的办法是使用非核心模块NEXT
来启用方法重新调度,以便可以由链中的每个模块处理。不幸的是,很少这样做。结论
所有这些都值得深思熟虑。
您可以接受我的速记,但知道在某些情况下它并不完全正确。
或者您可以接受实际的代码,它有自己的一组异常。
无论哪种方式,如果您突破了任一示例的表面,都需要解决一些微妙的问题。
The simple, and most likely correct answer is that the last module loaded that exports a given function will be the one whose function is used.
When a function (or other symbol) is exported, the symbol table for the receiving package is modified. So if two import functions make changes to the table, the last change is what is preserved.
Recall that
use Foo;
is more or less equivalent toBEGIN { require Foo; Foo->import if Foo->can(import); }
.Since
import
is just a subroutine with a special name, the only limitation is the twisted imagination of J. Random Hacker.import
can be any code. It can do anything or nothing. For example, it could contain logic to make sure no already defined functions are over-written.But, barring any weirdness, the last loaded will be the one in use.
To be 100% technically correct,
use Foo;
is equivalent toBEGIN { require Foo; Foo->import; }
.Except that this code doesn't work like you might expect.
Where the exact code varies from my example.
What the above code does is tied up in how method resolution works in Perl.
Ordinarily, a call to
Foo->some_sub
wheresome_sub
does not exist and anAUTOLOAD
function is defined inFoo
,some_sub
would be handled by theAUTOLOAD
function. There is a special case forimport
that exempts it fromAUTOLOAD
checks. See perlsub on Autoloading.After
AUTOLOAD
has (or really has not) been checked, we check inheritance. The same check for matching functions or anAUTOLOAD
function is repeated for each item in the original package's@ISA
. This is a recursive process that includes the parent's parents and so forth until the entire inheritance tree is checked--@ISA
is checked in left to right, depth first order. All this time theAUTOLOAD
exception is in place and it will be skipped forimport
.Eventually, if no matching method is found, we fall back on
UNIVERSAL
the universal base class. If the function exists inUNIVERSAL
, it is called, otherwise we throw an exception that says the function couldn't be found.So, in the case of our
Foo->import;
call,UNIVERSAL::import
is called to handle the job. So, what does this function do?In Perl 5.12.2 the code looks like this:
Note that the first thing the function does is bail out if it wasn't called on
UNIVERSAL
.Now everything I've said is true, as long as you don't do several things:
override the method resolution order. If you do this then method resolution will happen however you have defined it to. Whether we get to UNIVERSAL or not or when is totally up in the air and subject to your whims.
override
UNIVERSAL::import
. You could monkey patch UNIVERSAL::import to do whatever you want. Again how this will behave is completely subject to your whims.So, the semi-equivalent code I gave above is a just shorthand for what happens. I thought it would be easier to understand, since it does not require knowing as many details of how Perl does things, but it isn't 100% equivalent to what really happens. Doing unexpected things breaks the equivalence.
Where my code varies from exact equivalent code
Further, my code calls
Foo->can
which generally falls back toUNIVERSAL::can
. In no place iscan
called in the normal chain of events. This gets especially hairy when one considers the issues withcan
in Perl.can
may be overridden or reimplemented by any class in the inheritance graph. Whichcan
gets called is subject to method resolution order. All the problems with multiple inheritance apply here.can
does not see autoloaded functions. Since autoloading doesn't apply to import this may not seem like a big deal. The problem is that it is considered good practice to overloadcan
to take this into account if you use autoloading. So, this compounds the issues above. The best thing to do is to use the non-core moduleNEXT
to enable method redispatch, so that can can be handled by each module in the chain. Unfortunately, this is rarely done.Conclusion
All this is one hell of a lot to chew on.
You can accept my shorthand, knowing that in some cases, it is not exactly correct.
Or you can accept the actual code, that has its own set of exceptions.
Either way, if you break through the surface of either example there are some subtle issues to cope with.