Python类如何引用其子类之一或其实例?

时间:2013-08-10 15:00:17

标签: python

Python类__new__的{​​{1}}方法是否可以引用A子类的(常量)实例?

为了说明(激励?)这个问题,下面是Perl中类似Lisp的列表的玩具实现。 Lisp列表通常被递归地定义为一对(aka A),其第一个元素是一些任意的Lisp对象,其第二个元素是一个列表。为了避免无限递归,Lisp有一个名为cons的标记常量定义作为列表。因此,nil是列表,nil是列表,(cons 3 nil)是列表等(尽管最后两个示例更常见的是(cons 3 (cons 1 nil))和{{ 1}},分别)。

我在下面给出的这个定义的Perl实现将(3)构造为(3 1)的子类的实例。然而,nil类的定义是指list常量。

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对象的第一个和第二个元素的Lisp-ish。)

示例的要点是,在Perl中(即使是我过时的Perl,在一个低于专家级别),实现一个引用子类实例的类是微不足道的。这是因为,在Perl中,即使在cdr被定义之前,类cons也可以被声明为B的子类。 (这就是行

A

一样。)

我正在寻找一些方法来在Python中估算这种效果。

EDIT /澄清:

在我尝试过的很多事情中,这是一个接近标记的事情。

A

输出结果为:

our @ISA = ( 'list' );

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

如果将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 开头,则会收到错误:

Nil

5 个答案:

答案 0 :(得分:1)

自从我接触到Perl已经有很长一段时间了,但是你在代码中做了什么? car的{​​{1}}和cdr应该归还自己,对吧?

nil

答案 1 :(得分: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的值,因此您可以在程序中随时更改分配给Sub变量的值,并且由于Python是动态类型的,因此您甚至可以将其从一种类型更改为另外,如果你想。

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

答案 2 :(得分: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

答案 3 :(得分: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空值在这种情况下。这很容易改变。)

答案 4 :(得分:1)

好的,我找到了解决方案。在我在问题末尾提供的代码中,Nil继承Listtuple,并为其提供__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