使用 __new__ 根据输入从不同的类返回对象

问题描述 投票:0回答:2

我想重写

__new__
函数,对于一个特定的类,根据传递的输入,它从一个类或另一个类返回一个对象。

我写了下面的代码——这似乎有效——但感觉就像在作弊,我想知道这是“最好的”还是“最像 python 的”方法:

class A:
    def __new__(cls, x):
        if x == 1:
            a = 0
            self = B.__new__(A, a)
            self.cls = B
        else:
            a = 0
            b = 0
            self = C.__new__(A, a, b)
            self.cls = C
        return self

    def __init__(self, x):
        self.__class__ = self.cls

class B(A):
    def __new__(cls, a):
        self = object.__new__(cls)
        self.a = a
        return self

    def print_this(self):
        print("self.a is: ", self.a)
        print("class is B")

class C(A):
    def __new__(cls, a, b):
        self = object.__new__(cls)
        self.a = a
        self.b = b
        return self

    def print_this(self):
        print("self.a is: ", self.a)
        print("self.b is: ", self.b)
        print("class is C")
        

xxx = A(1)
print("xxx.__class__: ", xxx.__class__)
xxx.print_this()

yyy = A(2)
print("yyy.__class__: ", yyy.__class__)
yyy.print_this()

它返回以下内容,这就是我想要的

xxx.__class__:  <class '__main__.B'>
self.a is:  0
class is B
yyy.__class__:  <class '__main__.C'>
self.a is:  0
self.b is:  0
class is C
python new-operator return-type
2个回答
1
投票

是的,它很脏 - 对于读者来说,

A()
实际上创建了另一个类的实例并不明显,如果有人在
B.__new__
中更改某些内容,他可能会破坏他永远不会想到的部分,并且对于没有经验的 python 来说可能很难理解程序员这是怎么回事。

你搜索的是工厂设计模式。

from enum import Enum

class ClsEnum(Enum):
    B = "B"
    C = "C"

class A:
    pass

def cls_factory(e: ClsEnum) -> A:
    if e == ClsEnum.B:
        return B(0)
    elif e == ClsEnum.C:
        return C(0, 0)
    raise TypeError

class B(A):
    def __init__(self, a):
        self.a = a

    def print_this(self):
        print("self.a is: ", self.a)
        print("class is B")


class C(A):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def print_this(self):
        print("self.a is: ", self.a)
        print("self.b is: ", self.b)
        print("class is C")
        

xxx = cls_factory(ClsEnum.B)
print("xxx.__class__: ", xxx.__class__)
xxx.print_this()

yyy = cls_factory(ClsEnum.C)
print("yyy.__class__: ", yyy.__class__)
yyy.print_this()

此代码提供与您的版本相同的输出。


0
投票

我在处理类似情况时遇到了这个问题,并认为我会添加我的方法,因为它运作良好。

IMO 这种方法有一个有效的用例,在我的例子中是在使用身份映射时,其中应该只有一个具有给定 ID 的对象实例。由于只能有一个实例,它应该是最具体的子类,因为用户以后将无法创建它的另一个实例。

这是一个工作的、符合 mypy 的(Python 3.11)示例,它说明了我的方法。

from __future__ import annotations
from typing import Type, Optional
from abc import ABC

class A(ABC):

  # return new instance of either A or appropriate subclass of A
  def __new__(cls: Type[A], arg: Optional[str] = None):

    print(f'A.__new__({cls}, arg={arg})')

    # map arg to subclass (can be based on heuristic inspection of args)
    cls_map = {
      'B': B,
      'C': C
    }

    if arg in cls_map:
      # use more specific class
      cls_new = cls_map[arg]
    else:
      # no subclass required
      cls_new = cls

    if cls is cls_new:
      # already have correct class, create new object
      return object.__new__(cls_new)
    else:
      # invoke correct __new__
      return cls_new.__new__(cls_new, arg)

  def __init__(self, arg: Optional[str] = None):
    print(f'A.__init__({arg})')

# subclasses which can be returned by A()
class B(A): pass
class C(A): pass

巧妙的是 B/C 根本不需要实现 __new__ 或意识到 A.__new__ 中发生了任何异常情况。然而,他们可以实现 __new__ 并且它仍然可以正常工作,如果需要一些常见的处理(比如我的身份映射场景),他们甚至可以调用 super().__new__ 。

测试代码:

a = A()
assert type(a) is A

b = A('B')
assert type(b) is B

c = A('C')
assert type(c) is C

输出:

A.__new__(<class '__main__.A'>, arg=None)
A.__init__(None)
A.__new__(<class '__main__.A'>, arg=B)
A.__new__(<class '__main__.B'>, arg=B)
A.__init__(B)
A.__new__(<class '__main__.A'>, arg=C)
A.__new__(<class '__main__.C'>, arg=C)
A.__init__(C)

或者你可以使用工厂函数,但是你必须在任何地方调用工厂函数而不是仅仅实例化 IMO 更直观的类。基本上,工厂函数由构造函数本身包装。你返回一个比你要求的更具体的类并没有违反任何规则——它仍然会像鸭子一样嘎嘎叫,只是可能是一个更具体的嘎嘎。

就是说,一个类很少知道哪些类是它的子类,而且这种方法很少有必要。

© www.soinside.com 2019 - 2024. All rights reserved.