应该有一个 - 最好只有一个 - 显而易见的方法。
我开始使用这样的代码:
class Base(tuple):
def __new__(cls, *args, **kw):
return super().__new__(cls, args, **kw)
class Usual(Base):
pass
class Optimized(Base): # optimized to case with 2 arguments
def __init__(self, *_, **kw):
super().__init__(**kw)
if len(self) != 2:
self.__class__ = Usual
我决定将from_iterable
c-tor添加到Base
,就像itertools.chain
一样。很高兴。
然而,我无法在PyPy的源代码中找到用Python实现的chain
,所以我决定找出自己的解决方案。
@classmethod
def from_iterable(cls, iterable):
return cls(*iterable)
我想到的第一个。简单,可读,pythonic。但是,我 belive 主演的表达式很昂贵,所以如果它不能使代码更灵活,我就避免使用不必要的表达式。
super().__new__
@classmethod
def from_iterable(cls, iterable):
obj = super().__new__(cls, iterable)
obj.__init__()
return obj
使用Base.__new__
而不是使用修改后的tuple.__new__
,它基于iterable构造实例。工作很好,但让我担心__init__
- 它接收传递给__new__
的位置参数,如果对象是通过调用类来创建的,但是当对象是对象时没有接收到位置参数(self
除外)由from_iterable
创建。这种不一致性不是问题,因为__init__
无论如何都会忽略它们,但我担心在子类中覆盖__init__
会导致细微的错误,很难追踪。
我可以写obj.__init__(*iterable)
,但之后这个解决方案比之前更糟糕 - 更复杂,更不易阅读,而且我仍然希望避免使用星号表达。
class WithFromIterableCtor(type):
def __call__(cls, *args):
return super().__call__(args)
def from_iterable(self, iterable):
return super().__call__(iterable)
class Base(tuple, metaclass=WithFromIterableCtor):
pass
class Optimized(Base):
def __init__(self, iterable):
if len(self) != 2:
self.__class__ = Usual
# self.__init__() # Usual has no __init__
else:
super().__init__()
这是我决定使用的解决方案。我编写了简单的元类来改变实例的创建。它以封装的方式处理问题。它也避免了星号表达。
然而,它使类混淆:__init__
定义表明它需要迭代,但是要创建一个实例,你传递可变数量的参数...另外,是不是在这里使用元类过度?
我错过了更好的方法吗?如果不是,哪个灵魂更好 - 第一或第三?
答案 0 :(得分:1)
这种(metaclasse方式)显然是而不是这种“明显的”方式。 一般来说,在“Python talk”中我们谈论“一种明显的”方式,这是最简单的方式(即使它可能不是最高效的方式)。在这些例子中,你走的方式,简单的方式只是因为你假设(在你的话中“相信”)主演的电话将是“昂贵的”。 嗯,事实并非如此。但即便如此,解决方案的收益也可能微不足道。
在Python中,除了“明显的......”之外还有其他的说法 - 其中之一就是“过早优化是万恶之源”。如果“python绝杀”上的一个短语适用于上述列表,那么这是迄今为止匹配最佳的短语。
IF - 和IF - 在你的项目工作之后,你决定有时间和需要进行一些优化,然后你可以使用一个分析器,并检查其他方法之一是否会给你一些收益(暗示:很可能不是 - 上面的“星形”打包/解包或参数是在本机代码中完成的;你用元类方法进行的旋转和转动只是添加了一层间接,并且很难显示在分析中)。但是,如果绝对远离光线,则使用元类方法添加的维护负担。我通常被认为是一个“大”的元类用户和支持者,我必须盯着你在那里做的事情(而且看起来它只是一个很大的无操作)。 (此外,它会隐藏from_iterable
对你的类实例的普通内省 - 通常不是预期的。)
上面还有另一个巨大的反模式,它在初始化时改变了实例类。现在,如果你真的想要它,应该躺在__new__
上,或者,也许,这对于元类来说是合适的用法。换句话说,为了清楚起见:根据输入选择创建的实例类应该在元类或新方法上完成,而不是__init__
,如果你正在这样做。
但要比普通函数更好地用作实例工厂:这个函数根据收到的参数实例化适当类的对象:
def MyUbberTuple(*args, **kwargs):
if len(args) == 2:
return Usual(*args)
return Optimized(*args)