使用classmethods实现可选构造函数,如何向这些可选构造函数添加函数?

2024-09-30 01:24:55 发布

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

我有一个类,可以通过使用类方法的替代构造函数来构造。你知道吗

class A:

    def __init__(self, a, b):
            self.a = a
            self.b = b

    @classmethod
    def empty(cls, b):
        return cls( 0 , b)

所以我们不必像A()那样构建A,我现在也可以做A.empty()。你知道吗

为了方便用户,我想进一步扩展这个empty方法,这样我就可以通过A.empty()以及更专业但密切相关的A.empty.typeI()A.empty.typeII()初始化A。你知道吗

我天真的做法并没有达到我的目的:

class A:

    def __init__(self, a, b):
            self.a = a
            self.b = b

    @classmethod
    def empty(cls, b):

        def TypeI(b):
            return cls( 0 , b-1)

        def TypeII(b):
            return cls( 0 , b-2)

        return cls( 0 , b)

有人能告诉我怎么做吗(或者至少能说服我为什么那会是个糟糕的主意)。我想强调的是,对于用法,我认为这样的方法对于用户来说非常方便和清晰,因为函数是直观地分组的。你知道吗


Tags: 方法用户self目的return专业initdef
3条回答

您可以通过使Empty成为A的嵌套类而不是类方法来实现所需的功能。最重要的是,它提供了一个方便的名称空间—从不创建它的实例—在其中放置各种可选构造函数,并且可以轻松地进行扩展。你知道吗

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

    def __repr__(self):
        return 'A({}, {})'.format(self.a, self.b)

    class Empty(object):  # nested class
        def __new__(cls, b):
            return A(0, b)  # ignore cls & return instance of enclosing class

        @staticmethod
        def TypeI(b):
            return A(0, b-1)

        @staticmethod
        def TypeII(b):
            return A(0, b-2)

a = A(1, 1)
print('a: {}'.format(a))      # --> a: A(1, 1)
b = A.Empty(2)
print('b: {}'.format(b))      # --> b: A(0, 2)
bi = A.Empty.TypeI(4)
print('bi: {}'.format(bi))    # --> bi: A(0, 3)
bii = A.Empty.TypeII(6)
print('bii: {}'.format(bii))  # --> bii: A(0, 4)

通常最好有统一的类接口,这意味着不同的用法应该相互一致。我认为A.empty()A.empty.type1()是不一致的,因为前缀A.empty在它们各自的意思是不同的。你知道吗

更好的接口是:

class A:
    @classmethod
    def empty_default(cls, ...): ...
    @classmethod
    def empty_type1(cls, ...): ...
    @classmethod
    def empty_type2(cls, ...): ...

或:

class A:
    @classmethod
    def empty(cls, empty_type, ...): ...

您不能这样做,因为A.empty.something需要将底层方法对象绑定到该类型,这样您就可以实际调用它。Python不会这么做,因为类型的成员是empty,而不是TypeI。你知道吗

所以您需要做的是在您的类型中有一些返回绑定类方法的对象empty(例如SimpleNamespace)。问题是,当我们用class结构定义类型时,我们还不能访问它。所以我们不能访问它的成员来设置这样的对象。相反,我们必须事后再做:

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

    @classmethod
    def _empty_a (cls, b):
        return cls(1, b)

    @classmethod
    def _empty_b (cls, b):
        return cls(2, b)

A.empty = SimpleNamespace(a = A._empty_a, b = A._empty_b)

现在,您可以访问该成员的项并获取绑定方法:

>>> A.empty.a
<bound method type._empty_a of <class '__main__.A'>>
>>> A.empty.a('foo').a
1

当然,那可不是那么漂亮。理想情况下,我们希望在定义类型时设置它。我们可以使用元类来解决这个问题,但实际上我们可以使用类装饰器轻松地解决这个问题。例如这个:

def delegateMember (name, members):
    def classDecorator (cls):
        mapping = { m: getattr(cls, '_' + m) for m in members }
        setattr(cls, name, SimpleNamespace(**mapping))
        return cls
    return classDecorator

@delegateMember('empty', ['empty_a', 'empty_b'])
class A:
    def __init__ (self, a, b):
        self.a = a
        self.b = b

    @classmethod
    def _empty_a (cls, b):
        return cls(1, b)

    @classmethod
    def _empty_b (cls, b):
        return cls(2, b)

神奇的是,它起作用了:

>>> A.empty.empty_a
<bound method type._empty_a of <class '__main__.A'>>

既然我们已经成功了,当然我们应该讨论一下这是否真的是你想做的事情。我的意见是你不应该这样做。你已经从所做的努力中看到了这不是Python通常做的事情。这已经是一个好迹象表明你不应该这么做。Explicit is better than implicit,所以最好只要求用户键入类方法的全名。我上面的例子当然是以一种A.empty.empty_aA.empty_a更长的方式构建的。但即使是你的名字,也没有理由让它不能只是一个下划线而不是一个点。你知道吗

而且,您可以简单地在一个方法中添加多个默认路径。提供默认参数值,或者使用合理的回退,您可能不需要很多类方法来创建类型的替代版本。你知道吗

相关问题 更多 >

    热门问题