Python有类似Java匿名内部类的东西吗?

49

在Java中,您可以使用匿名内部类来定义一个新的类。当你只需要重写类的一个方法时,这非常有用。

假设您想创建OptionParser的子类,仅覆盖单个方法(例如exit())。在Java中,您可以编写以下代码:

new OptionParser () {

    public void exit() {
        // body of the method
    }
};

这段代码创建了一个匿名类,继承了OptionParser类,并仅覆盖了其中的exit()方法。

Python中有类似的用法吗?在这些情况下使用哪种惯用语?

12个回答

57

您可以使用type(name, bases, dict)内置函数来动态创建类。例如:

op = type("MyOptionParser", (OptionParser,object), {"foo": lambda self: "foo" })
op().foo()

由于OptionParser不是新式类,您必须在基类列表中明确包含object


2
这是内联类定义/创建匿名类的正确方式(引用自上述手册页面:“这本质上是类语句的动态形式”);“被接受的答案”应该指向它。 - Vlad
我不理解这个 {"foo": lambda self: "foo" },它应该返回自身吗? - zakdances
点击链接查看文档。"dict"字典是命名空间,包含类体的定义并成为 "dict" 属性。因此 "foo" 是一个仅带有 "self" 参数的方法名称。该方法恰好返回字符串 "foo"。 - Joe Hildebrand
2
这是一个动态类的示例,而不是匿名类。 - GuSuku

16

Java通常使用匿名类来模拟闭包或代码块。由于在Python中可以轻松地传递方法,因此不需要像匿名内部类那样笨重的构造:

def printStuff():
   print "hello"

def doit(what):
   what()

doit(printStuff) 

编辑:我知道这不是在这种特殊情况下需要的内容。我只是描述了最常见的Python解决方案,用于Java中最常见的匿名内部类问题。


7
我希望你能为我翻译成中文。以下是需要翻译的内容:-1 我想要继承 optparse.OptionParser() 而不是传递方法引用。 - Andrea Francia
那么,编写一个真正的子类有什么问题呢?我只是在说明匿名内部类的最常见用法。 - Joachim Sauer
4
Java的匿名内部类是为了减少复杂语法而存在的一种方式。Python已经拥有简单的语法,不需要匿名类来减轻负担。 - S.Lott
3
我知道Python更好,但答案是关于传递方法引用的,而问题是关于子类化和覆盖单个方法的。 - Andrea Francia
3
我不会说Python更好。每种语言都有其优点。在Python中传递可调用代码比Java更容易。我知道你正在寻找其他的东西,但我会保留我的答案,以防其他人发现你的问题并正在寻找我提供的内容。 - Joachim Sauer
@saua: 没错。我不能再点赞了,stackoverflow说“这个投票太旧了,无法撤销或更改”。 - Andrea Francia

16

有三种方法可以实现这个:

  1. 正确的子类(当然)
  2. 使用对象作为参数调用自定义方法
  3. (你可能想要的)-- 向对象添加新方法(或替换现有方法)。

选项3的示例(编辑以删除对“new”模块的使用 -- 它已被弃用,我不知道):

import types
class someclass(object):
    val = "Value"
    def some_method(self):
        print self.val

def some_method_upper(self):
    print self.val.upper()

obj = someclass()
obj.some_method()

obj.some_method = types.MethodType(some_method_upper, obj)
obj.some_method()

除了创建一个命名的内部类并实例化它(这可能更易于阅读),选项3似乎是实现这一目标的好方法。 - Greg Case
是的,这就是我为什么写下“你可能想要的”。我只是把它放在最后,因为帖子的文本流程更好 - 我想列举选项。 - gnud
为什么你在使用new?它已经被废弃了很长时间。 - rob
既然我们正在覆盖先前定义的方法,并且新的临时命名方法(some_method_upper)从未被使用,为什么不使用lambda并将其内联,因为这将更符合匿名内联类的一致性。 - fuentesjr
当然,在这个简单的例子中,这是可以做到的。该示例仅演示如何用另一种方法替换一个方法--在实际例子中,lambda可能不是最优选择。 - gnud
显示剩余2条评论

13

好的,类是一等对象,所以如果需要,您可以在方法中创建它们。例如:

from optparse import OptionParser
def make_custom_op(i):
  class MyOP(OptionParser):
    def exit(self):
      print 'custom exit called', i
  return MyOP

custom_op_class = make_custom_op(3)
custom_op = custom_op_class()

custom_op.exit()          # prints 'custom exit called 3'
dir(custom_op)            # shows all the regular attributes of an OptionParser

但是,为什么不只是在正常级别上定义类呢?如果需要自定义它,请将自定义内容作为__init__的参数。

(编辑:修复了代码中的打字错误)


谢谢,但我不理解这行代码:custom_op = custom_op_class().它应该被删除吗? - Andrea Francia
1
make_custom_op() 返回一个类。你必须调用这个类来创建一个实例,这就是那行代码所做的事情。然而,在我的代码的最后两行中有一些错误,我已经修复了它们,以防它们让你感到困惑。 - John Fouhy
这是一种简单但强大的技术,如果正确使用(不仅仅是作为一个hack),可以使代码非常优雅。 - Daniel Naab
所以答案是肯定的。对我来说,这看起来就像Java一样。我错了吗? - frhack

6

Python不直接支持这个(匿名类),但由于其简洁的语法,它并不是真正必要的:

class MyOptionParser(OptionParser):
    def exit(self, status=0, msg=None):
        # body of method

p = MyOptionParser()

唯一的缺点是你需要将MyOptionParser添加到你的命名空间中,但正如John Fouhy指出的那样,如果你要多次这样做,你可以将其隐藏在一个函数内部。

5
Python可能有更好的方法来解决您的问题。如果您可以提供更具体的细节,那将会更有帮助。
例如,如果您需要在代码的特定点更改调用的方法,则可以通过将函数作为参数传递来实现此目的(在Python中,函数是一等对象,您可以将它们传递给函数等)。您还可以创建匿名lambda函数(但它们仅限于单个表达式)。
此外,由于Python非常动态,您可以在创建对象后更改对象的方法object.method1 = alternative_impl1,虽然实际上这有点复杂,请参见gnud的答案

你不能用这种方式来替换方法——请看我的答案了解正确的方法。 - gnud

2
在Python中,您可以使用lambda语句声明匿名函数。我不太喜欢它们 - 它们不太易读,并且功能有限。
但是,您所谈论的内容可以使用完全不同的方法在Python中实现:
class a(object):
  def meth_a(self):
    print "a"

def meth_b(obj):
  print "b"

b = a()
b.__class__.meth_a = meth_b

方向正确,但是这并不起作用。文件“t”,第12行,在<module>中: b.meth_a() TypeError: meth_b()需要1个参数(0个给定) - gnud
正确的做法是要更改对象的方法,你必须将其更改为元类。我已经更新了代码。 - rob
b.class.meth_a = meth_b这段代码会影响所有基于类"a"的对象还是只有"b"对象? - Andrea Francia
最好提出这个问题作为单独的问题,这样答案就可以得到投票了。 - James Hopkin
我认为通过在交互式Shell中简单尝试可以更好地回答这个问题。 这是Python的主要优势之一:对于语言特性是否可行有疑问或问题?在shell中尝试一下,看看是否有效。 - rob

0
您可以通过变量来隐藏类:
 class var(...):
     pass
 var = var()

替代

 var = new ...() {};

0

如果你想要别出心裁,可以使用一次性的名称_作为派生类的名称:

class _(OptionParser):

    def exit(self):
        pass  # your override impl

0

我通常用Python3内部类来完成这个操作

class SomeSerializer():
    class __Paginator(Paginator):
        page_size = 10

    # defining it for e.g. Rest:
    pagination_class = __Paginator

    # you could also be accessing it to e.g. create an instance via method:
    def get_paginator(self):
        return self.__Paginator()

由于我使用了双下划线,这混淆了“名称修饰”和内部类的概念。从外部,你仍然可以通过SomeSerializer._SomeSerializer__Paginator访问内部类和子类,但是SomeSerializer.__Paginator将不起作用,这可能是你想要的,也可能不是,如果你希望它更加“匿名”。

然而,如果你不需要名称修饰,我建议使用“私有”符号,即单个下划线。

在我的情况下,我只需要一个快速的子类来设置一些类属性,然后将其分配给我的RestSerializer类的类属性,所以双下划线表示“不再进一步使用它”,如果我开始在其他地方重用它,可能会改为没有下划线。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接