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

时间:2015-03-25 15:56:28

标签: 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初始化A.empty()以及更专业但密切相关的A.empty.typeI() A.empty.typeII() 1}}和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)

我天真的做法并没有完全符合我的要求:

{{1}}

任何人都可以告诉我如何做到这一点(或者至少说服我为什么会这样做很糟糕)。我想强调的是,对于用法而言,我认为这种方法对用户来说非常方便和清晰,因为功能是直观分组的。

4 个答案:

答案 0 :(得分:2)

你不能真的这样做,因为A.empty.something会要求底层方法对象绑定到该类型,所以你可以实际调用它。 Python根本不会这样做,因为类型的成员是empty,而不是TypeI

所以你需要做的是在你的类型中有一些对象empty(例如SimpleNamespace),它返回 bound classmethods。问题是我们无法访问类型,因为我们使用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更长的方式构建的。但即使你的名字,也没有理由说它不能只是一个下划线而不是一个点。

此外,您只需在单个方法中添加多个默认路径即可。提供默认参数值,或使用合理的回退,您可能不需要很多类方法来创建您的类型的替代版本。

答案 1 :(得分:2)

您可以通过使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)

答案 2 :(得分:1)

通常更好的是具有统一的类接口,这意味着不同的用法应该彼此一致。我认为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, ...): ...

答案 3 :(得分:1)

这是我的other answer的增强实现,正如一位评论者所说的那样 - “与继承相得益彰”。你可能不需要这个,但其他人可能会做类似的事情。

它通过使用metaclass动态创建和添加类似于其他答案中所示的嵌套Empty类来实现此目的。主要区别在于派生类中的默认Empty类现在将返回Derived个实例,而不是基类类A的实例。

派生类可以通过定义自己的嵌套Empty类来覆盖此默认行为(它甚至可以从基类中的类派生)。另请注意,对于Python 3,使用不同的语法指定元类:

class A(object, metaclass=MyMetaClass):

以下是使用Python 2元类语法的修订实现:

class MyMetaClass(type):
    def __new__(metaclass, name, bases, classdict):
        # create the class normally
        MyClass = super(MyMetaClass, metaclass).__new__(metaclass, name, bases,
                                                        classdict)
        # add a default nested Empty class if one wasn't defined
        if 'Empty' not in classdict:
            class Empty(object):
                def __new__(cls, b):
                    return MyClass(0, b)

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

                @staticmethod
                def TypeII(b):
                    return MyClass(0, b-2)
            setattr(MyClass, 'Empty', Empty)
        return MyClass

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

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

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)

通过上述内容,您现在可以执行以下操作:

class Derived(A):
    pass  # inherits everything, except it will get a custom Empty

d = Derived(1, 2)
print('d: {}'.format(d))      # --> d: Derived(1, 2)
e = Derived.Empty(3)
print('e: {}'.format(e))      # --> e: Derived(0, 3)
ei = Derived.Empty.TypeI(5)
print('ei: {}'.format(ei))    # --> ei: Derived(0, 4)
eii = Derived.Empty.TypeII(7)
print('eii: {}'.format(eii))  # --> eii: Derived(0, 5)