代码片段的一般含义。
给定的代码片段
x = 'wow'
x.__add__( x )
在 Python 2.x 和 Python 3.x 中有不同的含义。
在 Python 2.x 中,字符串默认是窄字符串,每个编码单元一个字节,对应于基于 C++char
的字符串。
在 Python 3.x 中,字符串是宽字符串,保证代表 Unicode,对应于基于 C++
wchar_t
的字符串的实际使用,同样每个编码单元具有未指定的 2 或 4 个字节。
忽略效率,该__add__
方法在两个主要 Python 版本中的行为相同,对应于 C+++
运算符 for std::basic_string
(ie, for std::string
and std::wstring
),例如引用CPython 3k 文档:
object.__add__(self, other)
... 评估表达式x + y
,其中x
是具有__add__()
方法的类的实例,x.__add__(y)
被调用。
例如,CPython 2.7 代码
x = 'wow'
y = x.__add__( x )
print y
通常会写成
x = 'wow'
y = x + x
print y
并对应于此 C++ 代码:
#include <iostream>
#include <string>
using namespace std;
int main()
{
auto const x = string( "wow" );
auto const y = x + x;
cout << y << endl;
}
与为原始问题给出的许多不正确答案的主要区别
在于,C++ 对应是一个表达式,而不是一个更新。
可能很自然地认为方法名称__add__
表示字符串对象的值的更改,更新,但就可观察行为而言,Python 字符串是不可变字符串。只要可以在 Python 代码中直接观察到,它们的值就不会改变。这与 Java 和 C# 中的相同,但与 C++ 的可变std::basic_string
字符串非常不同。
CPython 中的二次到线性时间优化。
CPython 2.4 添加
了以下
优化,仅适用于窄字符串:
s = s + "abc"
表单语句中的字符串连接s += "abc"
现在在某些情况下可以更有效地执行。这种优化不会出现在其他 Python 实现中,比如 Jython,所以你不应该依赖它;join()
当您想有效地将大量字符串粘合在一起时,仍然建议使用字符串的方法。(由阿明·里戈提供。)
这听起来可能不多,但在适用的情况下,这种优化会减少从二次时间O( n 2 ) 到线性时间O( n ) 的串联序列,最终结果的长度为n。
首先,优化用更新替换串联,例如
x = x + a
x = x + b
x = x + c
或就此而言
x = x + a + b + c
被替换为
x += a
x += b
x += c
在一般情况下,将有许多对所引用的字符串对象的x
引用,并且由于 Python 字符串对象必须看起来是不可变的,因此第一次更新分配不能更改该字符串对象。因此,通常,它必须创建一个全新的字符串对象,并将该(引用)分配给x
.
此时x
持有对该对象的唯一引用。这意味着对象可以通过附加的更新分配来更新b
,因为没有观察者。同样对于c
.
这有点像量子力学:你无法观察到这个肮脏的事情正在发生,而且当有可能有人观察到这些阴谋时,它永远不会发生,但你可以通过收集的有关性能的统计数据推断它一定是在进行,因为线性时间与二次时间完全不同!
线性时间是如何实现的?好吧,通过更新,可以完成与 C++ 中相同的缓冲区加倍策略std::basic_string
,这意味着只需在每次缓冲区重新分配时复制现有缓冲区内容,而不是每次追加操作。这意味着复制的
总成本在最坏的情况下与最终字符串大小呈线性关系,就像总和(表示每次缓冲区翻倍时的复制成本)1 + 2 + 4 + 8 + ... + N 小于2*N。
C++ 中的线性时间字符串连接表达式。
为了忠实再现 C++ 中的 CPython 代码片段,
应该捕获操作的最终结果和表达式性质,
并且应该捕获它的性能特征!
__add__
CPython到 C++的直接翻译std::basic_string
+
无法可靠地捕获 CPython 线性时间。编译器可以以与 CPython 优化相同的方式优化C+++
字符串连接
。或者不是——这意味着有人告诉初学者,Python 线性时间运算的 C++ 等价物是具有二次时间的东西——嘿,这就是你应该使用的……
对于性能特征,C+++=
是基本答案,但是,这并没有抓住 Python 代码的表达性质。
自然的答案是一个线性时间 C++字符串构建器类,它将连接表达式转换为一系列+=
更新,以便 Python 代码
from __future__ import print_function
def foo( s ):
print( s )
a = 'alpha'
b = 'beta'
c = 'charlie'
foo( a + b + c ) # Expr-like linear time string building.
大致对应
#include <string>
#include <sstream>
namespace my {
using std::string;
using std::ostringstream;
template< class Type >
string stringFrom( Type const& v )
{
ostringstream stream;
stream << v;
return stream.str();
}
class StringBuilder
{
private:
string s_;
template< class Type >
static string fastStringFrom( Type const& v )
{
return stringFrom( v );
}
static string const& fastStringFrom( string const& s )
{ return s; }
static char const* fastStringFrom( char const* const s )
{ return s; }
public:
template< class Type >
StringBuilder& operator<<( Type const& v )
{
s_ += fastStringFrom( v );
return *this;
}
string const& str() const { return s_; }
char const* cStr() const { return s_.c_str(); }
operator string const& () const { return str(); }
operator char const* () const { return cStr(); }
};
} // namespace my
#include <iostream>
using namespace std;
typedef my::StringBuilder S;
void foo( string const& s )
{
cout << s << endl;
}
int main()
{
string const a = "alpha";
string const b = "beta";
string const c = "charlie";
foo( S() << a << b << c ); // Expr-like linear time string building.
}