我有一个可以使用类方法通过替代构造函数构造的类。
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}}
任何人都可以告诉我如何做到这一点(或者至少说服我为什么会这样做很糟糕)。我想强调的是,对于用法而言,我认为这种方法对用户来说非常方便和清晰,因为功能是直观分组的。
答案 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_a
比A.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)