3

__new__Python 类的方法是否可以引用 的子类A(常量)实例?A

为了说明(激励?)这个问题,下面是 Perl 中类似 Lisp 的列表的玩具实现。Lisp 列表通常被递归定义为一对(又名cons),其第一个元素是某个任意 Lisp 对象,其第二个元素是一个列表。为了避免无限递归,Lisp 有一个称为哨兵常量nil,它被定义为一个列表。因此,nilis a list、(cons 3 nil)is a list、(cons 3 (cons 1 nil))is a list 等(尽管最后两个示例更常见地分别写为(3)(3 1))。

我在下面给出的这个定义的 Perl 实现解释nillist. 然而,list类的定义引用了这个nil常量。

# list.pm
use strict;
BEGIN {
  sub list::nil ();
}

package _nil;
{
  my $nil = bless \ do { my $x }, '_nil';
  sub nil () { $nil; }
}
*car = *cdr = sub { shift };
our @ISA = ( 'list' );

package list;
*nil = *_nil::nil;
sub new {
  my $cls = shift;
  @_ ? bless [ shift, $cls->new( @_ ) ], $cls : nil;
}
sub car () { shift->[ 0 ] }
sub cdr () { shift->[ 1 ] }
use Exporter 'import';
our @EXPORT = qw( list nil );
1;

 

% perl -Mlist -e 'printf "nil->isa(\047list\047) = %s\n", \
                  nil->isa(q(list)) ? q(t) : q(f)'
nil->isa('list') = t

car并且cdr对于对象的第一个和第二个元素是 Lisp-ish cons。)

这个例子的重点是表明,在 Perl 中(即使是我过时的 Perl,在专家水平上也很差),实现一个引用子类实例的类是微不足道的。这是因为,在 Perl 中,一个类甚至可以在定义之前B声明为子类。(这是行AA

our @ISA = ( 'list' );

做。)

我正在寻找某种方法来在 Python 中近似这种效果。

编辑/澄清:

在我尝试过的许多事情中,有一个接近目标。

from collections import namedtuple
from itertools import chain

class List(namedtuple('pair', 'car cdr')):
    def __new__(cls, *args):
        if len(args) > 0:
            return super(List, cls).__new__(cls, args[0], cls(*args[1:]))
        else:
            return nil
    def __str__(self):
        return '(%s)' % ' '.join(map(str, self))
    def __iter__(self):
        return chain((self.car,), iter(self.cdr))

class Nil(object):
    car = cdr = property(lambda s: s)
    __str__ = lambda s: 'nil'
    def __iter__(self):
        if False: yield

nil = Nil()

print List(3, 4, 5)
print nil, nil.car, nil.cdr
print 'isinstance(nil, List) -> %s' % isinstance(nil, List)

输出是:

(3 4 5)
nil nil nil
isinstance(nil, List) -> False

...这很好,除了最后一行。

如果改为更改Nilto start with的定义class Nil(List),则会出现错误:

Traceback (most recent call last):
  File "list.py", line 38, in <module>
    nil = Nil()
  File "list.py", line 25, in __new__
    return nil
NameError: global name 'nil' is not defined
4

5 回答 5

1

It's been a long time since I've been exposed to Perl, but is it what you're doing in your code? car and cdr of nil are supposed to return itself, right?

class List:

    @classmethod
    def new(cls, *args):
        if len(args) > 0:
            return cls(args[0], cls.new(*args[1:]))
        else:
            return nil

    def __init__(self, head, tail):
        self.head = head
        self.tail = tail

    def car(self):
        return self.head

    def cdr(self):
        return self.tail


class Nil(List):

    def __init__(self):
        List.__init__(self, None, None)

    def car(self):
        return self

    def cdr(self):
        return self


nil = Nil()


print(isinstance(nil, List))
于 2013-08-10T15:51:47.090 回答
1

似乎实现您想要的最简单的方法是:

from itertools import chain

class List(object):

    def __init__(self, car=None, *args):
        self.car = car
        if args:
            self.cdr = List(*args)
        else:
            self.cdr = ()

    def __str__(self):
        return '(%s)' % ' '.join(map(str, iter(self)))

    def __iter__(self):
        if not self.car:
            return iter(())
        return chain((self.car,), iter(self.cdr))


nil = List()

print List(3, 4, 5)
print nil, nil.car, nil.cdr
print 'isinstance(nil, List) -> %s' % isinstance(nil, List)

结果:

(3 4 5)
() None ()
isinstance(nil, List) -> True

它与您的代码非常不同,但据我所知,它的作用几乎相同(除了它不会打印出文本字符串 'nil' 而不是空值,而是在这种情况下使用典型的 Python 空值. 这很容易改变)。

于 2013-08-10T17:03:48.723 回答
1

好的,我找到了解决方案。在我在问题末尾提供的代码中,都Nil继承了Listand tuple,并给它一个__new__方法,如下所示:

class Nil(List, tuple):
    def __new__(cls):
        return globals().setdefault('nil', tuple.__new__(cls))

    # rest of definition unchanged

现在输出是

(3 4 5)
nil nil nil
True
于 2013-08-10T17:05:50.530 回答
1

听起来您真正要问的是:如果声明子类需要基类存在,但基类的实现需要子类,如何解决?

答案在于以下几个因素:

  • Python 类是值,存储在变量中。
  • 您可以更改变量在运行时引用的值。
  • __new__在您调用它之前,不会评估块的主体。

因此,您可以首先声明您的基类,其构造函数引用子类,然后在任何人有机会运行构造函数之前将子类放置到位。例如:

Sub = None
sub_instance = None

class Base(object):
    def __new__(cls):
        # you can do something with Sub or sub_instance in here,
        # because this won't be executed until the following
        # class decl and instantiation have been evaluated.
        pass

class Sub(Base):
    def __new__(cls):
       # Override __new__ in the subclass so that the base class
       # constructor won't run for this subclass.
       # By calling the object constructor directly we can build a type
       # "manually" without running Base.__new__.
       return object.__new__(Sub)
    def __init__(self):
       pass

sub_instance = Sub()

# You can safely do the following at any point after you've
# defined Sub and sub_instance. Before that, it'll fail
# because Sub and sub_instance are still None.
base_instance = Base()

Python 只是将全局范围视为字典,因此只要在您访问它时其中包含正确的值,就会发生预期的行为。由于类只是 type 的值type,因此您可以在程序期间随时更改分配给Sub变量的内容,并且由于 Python 是动态类型的,您甚至可以根据需要将其从一种类型更改为另一种类型。

这里的主要挑战是在不评估基类构造函数的情况下实例化子类。一种方法是使用子类中的特殊实现来屏蔽基类构造函数,如我在上面的示例中所示。在这个重写的实现中,您需要保留基类期望的任何不变量,而不调用基类构造函数本身。另一种解决方案是仅将基类构造函数设计为能够容忍 sub_instanceNone在初始设置期间出现。

于 2013-08-10T15:55:18.483 回答
1

__new__因为任何其他函数都可以通过此名称引用在其范围内绑定到某个名称的任何对象。

示例实现:

class Cons:
    def __new__(cls, car, cdr):
        if cdr is None and car is None:
            return cls.NIL  # __init__ is called unnecessarily 
        else:
            return super().__new__(cls)

    def __init__(self, car, cdr):
        self.car = car
        self.cdr = cdr

    @classmethod
    def from_iterable(cls, iterable):
        it = iter(iterable)
        try:
            car = next(it)
        except StopIteration:
            return cls.NIL
        else:
            return cls(car, cls.from_iterable(it))

    def __iter__(self):
        if self is not self.NIL:
            yield self.car
            yield from self.cdr

    def _repr(self):
        if self is self.NIL:
            return "NIL"
        else:
            return "({} . {})".format(self.car, self.cdr._repr())

    def __repr__(self):
        return "<{} {}>".format(self.__class__.__name__, self._repr()) 

Cons.NIL = NIL = super(Cons, Cons).__new__(Cons)
NIL.car = NIL
NIL.cdr = NIL
于 2013-08-10T16:12:37.577 回答