重写_unew_uu()以用默认参数替换类方法

2024-05-23 14:46:34 发布

您现在位置:Python中文网/ 问答频道 /正文

下面是一个有两种不同实现的类ProgressPrinter的略长示例,具体取决于它所使用的文件描述符是连接到控制台还是其他什么。用户可以通过调用ProgressPrinter.create()创建实例,该函数有一个方便的默认参数:

import sys

class ProgressPrinter:
    def __init__(self, file):
        self._file = file

    def print_progress(self, message):
        raise NotImplementedError

    @classmethod
    def create(cls, file=sys.stderr):
        if file.isatty():
            return TTYProgressPrinter(file)
        else:
            return NoTTYProgressPrinter(file)

class TTYProgressPrinter(ProgressPrinter):
    def print_progress(self, message):
        # Image some fancy stuff with ANSI escape codes.
        pass

class NoTTYProgressPrinter(ProgressPrinter):
    def print_progress(self, message):
        print(message, file=self._file)

重写它的最佳方式是什么,这样用户就可以直接调用ProgressPrinter([file]),而不是调用ProgressPrinter.create([file]),从而获得相同的结果

我尝试过重写__new__(),甚至写了一个元类,但都没有成功(很多TypeError: object() takes no parametersTypeError: __init__() missing 1 required positional argument: 'file',甚至还有一个RecursionError: maximum recursion depth exceeded while calling a Python object

更新:删除示例中的冗余代码。
更新:使实现实际上成为基类的子类


Tags: 用户self示例messagereturninitdefcreate
3条回答

What is the best way to rewrite this so that, instead of calling ProgressPrinter.create([file]), users could directly call ProgressPrinter([file]) with same result?

只是克制一下。您真正想要的是用户对您的代码感到满意。当我看到ProgressPrinter.create([file])时,我可以假设一个工厂将创建与ProgressPrinter相关但可能属于不同类的对象。您希望它们是子类,这很好

但是,当我看到ProgressPrinter([file])时,我希望对象正好来自类,而不是子类的成员。这意味着这段代码将更难阅读和理解,这是您肯定不想要的。我知道Python允许它,我甚至知道如何做到这一点。但这是不自然的,因为程序员不需要这样做

规则是遵循既定的模式。您可以按照@Barmar的建议使用委托给子对象(子类),也可以坚持工厂模式。但请不要使用反模式


如果您真的想这样做,诀窍是在new中构建并配置一个适当子类的对象。但是必须使用Object.__new__来构建子类的对象。如果只使用标准创建,则会递归到ProgressPrinter.__new__,导致堆栈溢出

最小的改变是用这个__new__方法替换__init__方法:

def __new__(cls, file):
    if (cls == ProgressPrinter):                   # delegate sub object creation
        return ProgressPrinter.create(file)
    pp = super(ProgressPrinter, cls).__new__(cls)  # trick to avoid recursion
    pp.file = file
    return pp

您还可以删除create方法并将其代码直接复制到__new__

但这只是向您展示Python语言的可能性,我仍然强烈建议您不要在生产代码中使用它,并坚持使用更好的工厂模式。未来的维护人员肯定会责怪您使用这种反模式

通过委托而不是子类化,在__init__方法中选择实现类来完成

import sys

class ProgressPrinter:
    def __init__(self, file=sys.stderr):
        self._file = file
        self.printer = ProgressPrinter.create(file)

    def print_progress(self, message):
        self.printer.print_progress(message)

    @classmethod
    def create(cls, file=sys.stderr):
        if file.isatty():
            return TTYProgressPrinter(file)
        else:
            return NoTTYProgressPrinter(file)

class TTYProgressPrinter:
    def __init__(self, file):
        self._file = file

    def print_progress(self, message):
        # Image some fancy stuff with ANSI escape codes.
        pass

class NoTTYProgressPrinter:
    def __init__(self, file):
        self._file = file

    def print_progress(self, message):
        print(message, file=self._file)

下面是一个让您起步的工作示例

class IceCream:
    def __new__(ignored_cls, flavor):
        # Here we actually return a new instance,
        # not update one created for us as in __init__.
        # We ignore the class because we're not returning
        # instances of it directly; we could merge this class
        # and IceCreamBase, but then we'd have to override __new__
        # in both Vanilla and Chocolate back to the default impl.
        if flavor == 'vanilla':
            return Vanilla()
        else:
            return Chocolate()

class IceCreamBase:
    def eat(self):
        return "Yum! It was %s" % self.flavor

class Vanilla(IceCreamBase):
    @property
    def flavor(self):
        return "vanilla"

class Chocolate(IceCreamBase):
    @property
    def flavor(self):
        return "chocolate"

现在我们可以这样做:

>>> IceCream('vanilla').eat()
'Yum! It was vanilla'
>>> IceCream('other').eat()
'Yum! It was chocolate'
>>> 

此时,您一定已经注意到,IceCream只是一个伪装的函数,以复杂的方式调用

如果需要为参数返回正确的值(而不是其他内部状态),我建议您在本例中使用工厂函数(而不是方法)

相关问题 更多 >