Perl的"祝福"到底是做什么的?

use*_*145 138 perl bless

我理解在类的"新"方法中使用Perl中的"bless"关键字:

sub new {
    my $self = bless { };
    return $self;
}    
Run Code Online (Sandbox Code Playgroud)

但究竟什么是"祝福"对哈希引用呢?

Gor*_*son 138

通常,bless将对象与类关联.

package MyClass;
my $object = { };
bless $object, "MyClass";
Run Code Online (Sandbox Code Playgroud)

现在当你调用一个方法时$object,Perl知道要搜索该方法的包.

如果省略第二个参数,如示例中所示,则使用当前包/类.

为清楚起见,您的示例可能写成如下:

sub new { 
  my $class = shift; 
  my $self = { }; 
  bless $self, $class; 
} 
Run Code Online (Sandbox Code Playgroud)

编辑:请参阅kixx的更好细节的答案.


kix*_*ixx 77

bless 将引用与包关联.

引用的内容并不重要,它可以是散列(最常见的情况),数组(不常见),标量(通常这表示内向外对象),正则表达式,子程序或TYPEGLOB(参见" 面向对象的Perl:Damian Conway的概念和编程技术综合指南 "一书中的有用示例)或者甚至是对文件或目录句柄的引用(最不常见的情况).

效果bless是它允许您将特殊语法应用于祝福的引用.

例如,如果存储了一个有福的引用$obj(bless与包"Class" 关联),那么$obj->foo(@args)将调用一个子例程foo并作为第一个参数传递引用,$obj后跟其余的arguments(@args).子程序应在包"Class"中定义.如果foo包"Class"中没有子程序,@ISA则将搜索其他包的列表(从包"Class"中的数组中获取),并且将调用foo找到的第一个子例程.

  • @Blessed Geek,这不是理论上的细节.差异具有实际应用. (17认同)
  • 您的初始陈述不正确.是的,bless将引用作为其第一个参数,但它是受祝福的引用变量,而不是引用本身.$ perl -le'sub Somepackage :: foo {42}; %H =(); $ H = \%H; 祝福$ h,"Somepackage"; $ j = \%h; print $ j-> UNIVERSAL :: can("foo") - >()'42 (14认同)
  • Kixx 的解释很全面。我们不应该为转换器对理论细节的挑选而烦恼。 (2认同)
  • 现在,"per-out-object"的旧perlfoundation.org链接充其量只能在登录墙后面.[Archive.org链接原文在这里](https://web.archive.org/web/20071024192241/http://www.perlfoundation.org/perl5/index.cgi?inside_out_object). (2认同)
  • 也许这将代替@harmic 评论的断开链接:http://perldoc.perl.org/perlobj.html#Inside-Out-objects (2认同)

cha*_*aos 9

简短版本:它标记了附加到当前包命名空间的散列(以便该包提供其类实现).


小智 6

此函数告诉REF引用的实体它现在是CLASSNAME包中的对象,或者如果省略CLASSNAME则是当前包.建议使用两种形式的祝福.

示例:

bless REF, CLASSNAME
bless REF
Run Code Online (Sandbox Code Playgroud)

回报价值

此函数返回对包含在CLASSNAME中的对象的引用.

示例:

以下是显示其基本用法的示例代码,通过祝福对包的类的引用来创建对象引用 -

#!/usr/bin/perl

package Person;
sub new
{
    my $class = shift;
    my $self = {
        _firstName => shift,
        _lastName  => shift,
        _ssn       => shift,
    };
    # Print all the values just for clarification.
    print "First Name is $self->{_firstName}\n";
    print "Last Name is $self->{_lastName}\n";
    print "SSN is $self->{_ssn}\n";
    bless $self, $class;
    return $self;
}
Run Code Online (Sandbox Code Playgroud)


Dmi*_*try 5

我将尝试在这里提供一个答案,因为在我最初写这篇文章时,这里的答案并不太适合我(警告,这个答案的结构相当糟糕,请随意跳过对我来说不是特别有用的部分)你)。

Perl 的 bless 函数将指定的引用与一个包名字符串关联起来,并让被祝福的引用的箭头运算符在与该引用关联的包中查找方法,如果没有找到,则继续使用数组查找(如果有@ISA)是一个(这超出了本文的范围)。

为什么我们需要这个?

让我们首先用 JavaScript 表达一个示例:

(() => {
    //'use strict'; // uncomment to fix the bug mentioned below.

    class Animal {
        constructor(args) {
            console.log(this);
            this.name = args.name;
            this.sound = args.sound;
        }
    }

    /* This is left for historical reasons, 
     *    modern JavaScript engines no longer allow you to
     *    call class constructors without using new.
     * 
     * var animal = Animal({
     *     'name': 'Jeff',
     *     'sound': 'bark'
     * }); 
     * console.log(animal.name + ', ' + animal.sound); // seems good
     * console.log(window.name); // my window's name is Jeff?
     */

    // as of recently, Animal constructor cannot be called without using the new operator.
    var animal = new Animal({
        'name': 'Jeff',   
        'sound': 'bark'
    });

    console.log(animal.name + ', ' + animal.sound); // still fine.
    console.log("window's name: " + window.name); // undefined
})();
Run Code Online (Sandbox Code Playgroud)

现在让我们看看类构造的脱糖版本:

(() => {
    // 'use strict'; // uncomment to fix bug.

    var Animal = function(args) {
        this.name = args.name;
        this.sound = args.sound;
        return this; // implicit context hashmap
    };
    
    /** 
     *  The bug left for historical reasons,
     *      should still work now in modern web developer consoles.
     *      
     *  var animal = Animal({
     *      'name': 'Jeff',
     *      'sound': 'Bark'
     *  });
     *  console.log(animal.name + ', ' + animal.sound); // seems good
     *  console.log("The window's name is: " + window.name); // my window's name is Jeff?
     */
  
    // the new operator causes the "this" inside methods to refer to the animal
    // rather than the global scope, so the bug mentioned above does not occur.
    var animal = new Animal({
        'name': 'Jeff',
        'sound': 'bark'
    });
    console.log(animal.sound);    
    console.log(window.name); // the name has not been changed by the constructor.
})();
Run Code Online (Sandbox Code Playgroud)

Animal 的构造函数接受一个属性对象并返回一个具有这些属性的 Animal,或者如果您忘记添加 new 关键字,它将返回整个全局上下文(位于window浏览器开发人员控制台内)。

Perl 没有“this”、“new”或“class”,但它仍然可以有一个行为类似的函数。我们不会有构造函数或原型,但我们将能够创建新的动物并修改它们的个体属性。

# immediatly invoked subroutine execution(IIFE).
(sub {
    my $Animal = (sub {
        return {
            'name' => $_[0]{'name'},
            'sound' => $_[0]{'sound'}
        };
    });

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound' => 'bark'
    });

    print $animal->{sound};
})->();
Run Code Online (Sandbox Code Playgroud)

现在,我们有一个问题:如果我们希望动物自己发出声音,而不是直接打印它们的声音,该怎么办?也就是说,我们需要一个函数performSound 来打印动物自己的声音。

实现此目的的一种方法是为 Animal 的每个实例提供其自己的 PerformSound 子例程引用。

# self contained scope 
(sub {
    my $Animal = (sub {
        $name = $_[0]{'name'};
        $sound = $_[0]{'sound'};
    
        return {
            'name' => $name,
            'sound' => $sound,
            'performSound' => sub {
                print $sound . "\n";
            }
        };
    });

    my $animal = $Animal->({
        'name' => 'Jeff',
        'sound' => 'bark'
    });

    $animal->{'performSound'}();
})->();
Run Code Online (Sandbox Code Playgroud)

这通常不是我们想要的,因为 PerformSound 被作为构建的每个动物的全新子例程引用。构建 10000 只动物可能会分配 10000 个 PerformSound 子例程。我们希望有一个子例程performSound,供所有查找自己的声音并打印它的Animal 实例使用。

(() => {
    'use strict';

    /* a function that creates an Animal constructor which can be used to create animals */
    var Animal = (() => {
        /* function is important, as fat arrow does not have "this" and will not be bound to Animal. */
        var InnerAnimal = function(args) {
            this.name = args.name;
            this.sound = args.sound;
        };
        /* defined once and all animals use the same single function call */
        InnerAnimal.prototype.performSound = function() {
            console.log(this.name);
        };
        
        return InnerAnimal;
    })();
 
    var animal = new Animal({
        'sound': 'bark',
        'name': 'Jeff'
    });
    animal.performSound(); // Jeff
})();
Run Code Online (Sandbox Code Playgroud)

与 Perl 的相似之处就到此为止了。

JavaScript 的 new 运算符不是可选的,没有它,对象方法内的“this”会污染全局范围:

(() => {
    // uncommenting this prevents unintentional
    //     contamination of the global scope, and throws a TypeError instead.
    // 'use strict'; 

    var Person = function() {
        this.name = "Sam";
    };
//    var wrong = Person(); // oops! we have overwritten window.name or global.main.
//    console.log(window.name); // my window's name is Sam?
    var correct = new Person; // person's name is actually stored in the person now.    
})();
Run Code Online (Sandbox Code Playgroud)

我们希望为每个动物提供一个函数来查找该动物自己的声音,而不是在构造时对其进行硬编码。

Blessing 让我们可以使用包的子例程,而不必将子例程引用附加到每个对象,它还可以引用ref更有意义的包名称(例如Animal)作为对象的名称,而不是无聊的HASH或任何其他引用对象你选择祝福:

package Animal;
sub new {
    my $packageRef = $_[0];
    my $name = $_[1]->{'name'};
    my $sound = $_[1]->{'sound'};

    my $this = {
        'name' => $name,
        'sound' => $sound
    };   
    
    bless($this, $packageRef);
    return $this;
}

# all animals use the same performSound to look up their sound.
sub performSound {
    my $this = shift;
    my $sound = $this->{'sound'};
    print $sound . "\n";
}

package main;
my $animal = Animal->new({
    'name' => 'Cat',
    'sound' => 'meow'
});

print("The animal's ref is: " . ref($animal) . "\n");
$animal->performSound();
Run Code Online (Sandbox Code Playgroud)

摘要/TL;DR

  1. Perl 没有“this”,“class”,也没有“new”。

  2. 将对象祝福到包中会为该对象提供对该包的引用。

  3. 使用箭头运算符来调用受祝福的referent( $blessedObject->method(...arguments))的方法通常与调用相同Package::method($blessedObject, ...arguments),但如果未找到方法,它将继续使用@ISA包的方法进行查找,这超出了本文的范围。

  4. 事实上,您可以在运行时创建新类,只要您违反严格的“refs”或使用 eval,这里演示了如何做到这一点:

#!/usr/bin/perl

use warnings;
use strict;

print('Enter the name for the class(eg Greeter): $ ');
my $class_name = <>;
chomp $class_name;

print('Enter the name of the method(eg greet): $ ');
my $method_name = <>;
chomp $method_name;

no strict 'refs';
# The line below violates strict 'refs'.
*{$class_name . '::new'} = sub {
    my $self = bless {}, $_[0];
    return $self;
}; 
use strict 'refs';

no strict 'refs';
# The line below violates strict 'refs'
*{$class_name . '::' . $method_name} = sub {
    print("Hello, World!\n");
};
use strict 'refs';

my $instance = ($class_name)->new();
$instance->$method_name();
Run Code Online (Sandbox Code Playgroud)

为什么会出现混乱?

bless 令人困惑的原因之一是,实际上存在三种调用包的方法

  1. 通过A::a(), 作为封装子程序。
  2. 通过A->a(), 作为包子例程,但它__PACKAGE__作为第一个参数隐式地在其他参数之前传递。
  3. 透着$a->a()有福$aA

下面的代码说明了这一点:

# | Illustrates catching 3 distinct ways of calling a package's member.
package Test;

sub new {        
    return bless {}, __PACKAGE__;
}

sub runTest {
    if (ref($_[0]) eq __PACKAGE__) {
        # | $_[0] is the blessed reference.
        # | Despite being called with "->", $_[1] is NOT "Test"(__PACKAGE__).
        print("Test::runTest was called through a blessed reference call(\$instance->runTest().\n");
    } elsif ($_[0] eq __PACKAGE__) {
        # | $_[0] is "Test"(__PACKAGE__), but we can't determine for sure whether it was -> or ::.
        print("Test::runTest was called through Test->runTest() or through Test::runTest() with 'Test' as the first argument.\n");
    } else {
        # | $_[0] is neither a blessed reference nor "Test"(__PACKAGE__), so it can't be an arrow call.
        print "Test::runTest was called through Test::runTest()\n";
    }
}

package main;

my $test = Test->new();
$test->runTest();
Test->runTest();
Test::runTest();
Test::runTest('Test'); # <- Same as "Test->runTest();"
Test::runTest($test); # <- Same as "$test->runTest();"
Run Code Online (Sandbox Code Playgroud)

另一个原因是,与 JavaScript、Python 不同,它们可以有多个具有不同名称但不同定义/方法/属性的类,Perl 类具有 unique( ref $obj),因为在任何给定时间只能有一个具有特定名称的包,并且 @ ISA 需要一点时间来适应。

我的最后一个原因是,包比其他脚本语言中的类更不具体,在其他脚本语言中,您可以通过赋值运算符将对类本身的引用存储在变量中,而在 Perl 中,您不仅不能存储对类的引用(您只能通过名称字符串引用包),但是如果不违反严格的“refs”或使用 eval,试图通过存储在变量中的名称(例如 String[$method])引用包似乎是不可能的。

无论如何,希望有人会觉得这篇文章有用。


注意:这是一个相当古老的答案尝试,我试图清理大量天真和令人尴尬/毫无意义/分散注意力的陈述,并添加更多有用的示例来帮助理解这个概念,但它仍然远离我想要的喜欢它(重读仍然相当令人沮丧)。我留下它,因为我仍然相信它可能对某人有用。

请谨慎对待,对于因答案结构不佳而引起的任何头痛,我深表歉意。

  • 在运行时创建新类并非不可能。`my $o = bless {}, $anything;` 将祝福一个对象进入 `$anything` 类。类似地,`{没有严格的'refs'; *{$任何东西。'::somesub'} = sub {my $self = shift; return $self-&gt;{count}++};` 将在 `$anything` 中命名的类中创建一个名为 'somesub' 的方法。这在运行时都是可能的。然而,“可能”并不意味着它成为日常代码中的一个很好的实践。但它在构建 Moose 或 Moo 等对象覆盖系统时很有用。 (3认同)

zdi*_*dim 5

-ed 引用的具体区别bless在于它获得额外的内部内容:SV 引用(存储在标量中)获取附加值FLAGS( OBJECT),并且有一个STASH携带包名称(还有一些其他差异)

perl -MDevel::Peek -wE'
    package Pack  { sub func { return { a=>1 } } }; 
    package Class { sub new  { return bless { A=>10 } } }; 
    $vp  = Pack::func(); print Dump $vp;   say"---"; 
    $obj = Class->new;   print Dump $obj'
Run Code Online (Sandbox Code Playgroud)

打印,相同(且与此无关)部分被抑制

SV = IV(0x12d5530) at 0x12d5540
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x12a5a68
  SV = PVHV(0x12ab980) at 0x12a5a68
    REFCNT = 1
    FLAGS = (SHAREKEYS)
    ...
      SV = IV(0x12a5ce0) at 0x12a5cf0
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 1
---
SV = IV(0x12cb8b8) at 0x12cb8c8
  REFCNT = 1
  FLAGS = (PADMY,ROK)
  RV = 0x12c26b0
  SV = PVHV(0x12aba00) at 0x12c26b0
    REFCNT = 1
    FLAGS = (OBJECT,SHAREKEYS)             <--
    STASH = 0x12d5300   "Class"            <--
    ...
      SV = IV(0x12c26b8) at 0x12c26c8
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 10
Run Code Online (Sandbox Code Playgroud)

据此,口译员知道

  • 这是一个对象

  • 它属于什么包

这告诉了它的用途。

例如,当取消引用该变量 ( $obj->name) 时,在包(或层次结构)中查找具有该名称的子项,变量(“对象”)将作为第一个参数传递,等等。