[解決済み] 小さな文字列を反復処理するのは、小さなリストより遅いのはなぜですか?
疑問点
timeitで遊んでいて、小さな文字列に対して単純なリスト内包を行うと、小さな一文字の文字列のリストに対して同じ操作を行うよりも時間がかかることに気づきました。何か説明がありますか?それはほぼ1.35倍の時間です。
>>> from timeit import timeit
>>> timeit("[x for x in 'abc']")
2.0691067844831528
>>> timeit("[x for x in ['a', 'b', 'c']]")
1.5286479570345861
もっと低いレベルで何が起こっているのか?
どうすれば解決するのですか?
TL;DR
-
Python 2 の場合、多くのオーバーヘッドが取り除かれると、実際の速度差は 70% (またはそれ以上) に近くなります。
-
オブジェクトの作成は ではなく のせいです。どちらの方法も、1 文字の文字列がキャッシュされるため、新しいオブジェクトは作成されません。
-
この違いは明らかではありませんが、おそらく、型と整形に関して、文字列インデックスのチェックがより多く行われることから生まれたものでしょう。また、何を返すかをチェックする必要性のおかげで、かなりありそうです。
-
リストのインデックス作成は驚くほど高速です。
>>> python3 -m timeit '[x for x in "abc"]'
1000000 loops, best of 3: 0.388 usec per loop
>>> python3 -m timeit '[x for x in ["a", "b", "c"]]'
1000000 loops, best of 3: 0.436 usec per loop
これは、あなたが発見したものと一致しません...
Python2を使っているのでしょう。
>>> python2 -m timeit '[x for x in "abc"]'
1000000 loops, best of 3: 0.309 usec per loop
>>> python2 -m timeit '[x for x in ["a", "b", "c"]]'
1000000 loops, best of 3: 0.212 usec per loop
バージョンの違いを説明しましょう。コンパイルされたコードを見てみる。
Python 3 の場合。
import dis
def list_iterate():
[item for item in ["a", "b", "c"]]
dis.dis(list_iterate)
#>>> 4 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f4d06b118a0, file "", line 4>)
#>>> 3 LOAD_CONST 2 ('list_iterate.<locals>.<listcomp>')
#>>> 6 MAKE_FUNCTION 0
#>>> 9 LOAD_CONST 3 ('a')
#>>> 12 LOAD_CONST 4 ('b')
#>>> 15 LOAD_CONST 5 ('c')
#>>> 18 BUILD_LIST 3
#>>> 21 GET_ITER
#>>> 22 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
#>>> 25 POP_TOP
#>>> 26 LOAD_CONST 0 (None)
#>>> 29 RETURN_VALUE
def string_iterate():
[item for item in "abc"]
dis.dis(string_iterate)
#>>> 21 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f4d06b17150, file "", line 21>)
#>>> 3 LOAD_CONST 2 ('string_iterate.<locals>.<listcomp>')
#>>> 6 MAKE_FUNCTION 0
#>>> 9 LOAD_CONST 3 ('abc')
#>>> 12 GET_ITER
#>>> 13 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
#>>> 16 POP_TOP
#>>> 17 LOAD_CONST 0 (None)
#>>> 20 RETURN_VALUE
ここで、リストバリアントは毎回リストを構築するため、より遅くなる可能性があることがわかります。
これは
9 LOAD_CONST 3 ('a')
12 LOAD_CONST 4 ('b')
15 LOAD_CONST 5 ('c')
18 BUILD_LIST 3
の部分です。文字列のバリアントには
9 LOAD_CONST 3 ('abc')
これで違いが出ているように見えることが確認できます。
def string_iterate():
[item for item in ("a", "b", "c")]
dis.dis(string_iterate)
#>>> 35 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f4d068be660, file "", line 35>)
#>>> 3 LOAD_CONST 2 ('string_iterate.<locals>.<listcomp>')
#>>> 6 MAKE_FUNCTION 0
#>>> 9 LOAD_CONST 6 (('a', 'b', 'c'))
#>>> 12 GET_ITER
#>>> 13 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
#>>> 16 POP_TOP
#>>> 17 LOAD_CONST 0 (None)
#>>> 20 RETURN_VALUE
これは単に
9 LOAD_CONST 6 (('a', 'b', 'c'))
のように、タプルは不変である。テストしてください。
>>> python3 -m timeit '[x for x in ("a", "b", "c")]'
1000000 loops, best of 3: 0.369 usec per loop
素晴らしい、スピードに戻る。
Python2用です。
def list_iterate():
[item for item in ["a", "b", "c"]]
dis.dis(list_iterate)
#>>> 2 0 BUILD_LIST 0
#>>> 3 LOAD_CONST 1 ('a')
#>>> 6 LOAD_CONST 2 ('b')
#>>> 9 LOAD_CONST 3 ('c')
#>>> 12 BUILD_LIST 3
#>>> 15 GET_ITER
#>>> >> 16 FOR_ITER 12 (to 31)
#>>> 19 STORE_FAST 0 (item)
#>>> 22 LOAD_FAST 0 (item)
#>>> 25 LIST_APPEND 2
#>>> 28 JUMP_ABSOLUTE 16
#>>> >> 31 POP_TOP
#>>> 32 LOAD_CONST 0 (None)
#>>> 35 RETURN_VALUE
def string_iterate():
[item for item in "abc"]
dis.dis(string_iterate)
#>>> 2 0 BUILD_LIST 0
#>>> 3 LOAD_CONST 1 ('abc')
#>>> 6 GET_ITER
#>>> >> 7 FOR_ITER 12 (to 22)
#>>> 10 STORE_FAST 0 (item)
#>>> 13 LOAD_FAST 0 (item)
#>>> 16 LIST_APPEND 2
#>>> 19 JUMP_ABSOLUTE 7
#>>> >> 22 POP_TOP
#>>> 23 LOAD_CONST 0 (None)
#>>> 26 RETURN_VALUE
奇妙なことに、私たちは と同じ を構築していることですが、これでも速くなりました。Python 2 は奇妙に速く動作しています。
内包を削除してリタイムしてみましょう。その
_ =
は、最適化されてしまうのを防ぐためです。
>>> python3 -m timeit '_ = ["a", "b", "c"]'
10000000 loops, best of 3: 0.0707 usec per loop
>>> python3 -m timeit '_ = "abc"'
100000000 loops, best of 3: 0.0171 usec per loop
初期化はバージョン間の違いを説明するほど重要ではないことがわかります(この数字は小さいです)! したがって、Python 3は内包が遅いと結論付けることができます。これは、Python 3がより安全なスコープを持つように内包を変更したため、理にかなっています。
さて、今度はベンチマークを改善します(反復処理でないオーバーヘッドを削除しているだけです)。これは、イテレーション可能なものを事前に代入して構築することを削除します。
>>> python3 -m timeit -s 'iterable = "abc"' '[x for x in iterable]'
1000000 loops, best of 3: 0.387 usec per loop
>>> python3 -m timeit -s 'iterable = ["a", "b", "c"]' '[x for x in iterable]'
1000000 loops, best of 3: 0.368 usec per loop
>>> python2 -m timeit -s 'iterable = "abc"' '[x for x in iterable]'
1000000 loops, best of 3: 0.309 usec per loop
>>> python2 -m timeit -s 'iterable = ["a", "b", "c"]' '[x for x in iterable]'
10000000 loops, best of 3: 0.164 usec per loop
を呼び出すかどうかを確認することができます。
iter
を呼び出すことがオーバーヘッドになっているかどうかを確認することができます。
>>> python3 -m timeit -s 'iterable = "abc"' 'iter(iterable)'
10000000 loops, best of 3: 0.099 usec per loop
>>> python3 -m timeit -s 'iterable = ["a", "b", "c"]' 'iter(iterable)'
10000000 loops, best of 3: 0.1 usec per loop
>>> python2 -m timeit -s 'iterable = "abc"' 'iter(iterable)'
10000000 loops, best of 3: 0.0913 usec per loop
>>> python2 -m timeit -s 'iterable = ["a", "b", "c"]' 'iter(iterable)'
10000000 loops, best of 3: 0.0854 usec per loop
いいえ、そうではありません。特にPython 3ではその差は小さすぎます。
では、さらに不要なオーバーヘッドを取り除きましょう...全体を遅くすることで! 目的は、より長い反復を行うことで、時間がオーバーヘッドを隠してくれることです。
>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' '[x for x in iterable]'
100 loops, best of 3: 3.12 msec per loop
>>> python3 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' '[x for x in iterable]'
100 loops, best of 3: 2.77 msec per loop
>>> python2 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' '[x for x in iterable]'
100 loops, best of 3: 2.32 msec per loop
>>> python2 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' '[x for x in iterable]'
100 loops, best of 3: 2.09 msec per loop
これは実際には変わっていません 多く しかし、少しは役に立っています。
だから、理解度を削除してください。質問の一部ではないオーバーヘッドです。
>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'for x in iterable: pass'
1000 loops, best of 3: 1.71 msec per loop
>>> python3 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'for x in iterable: pass'
1000 loops, best of 3: 1.36 msec per loop
>>> python2 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'for x in iterable: pass'
1000 loops, best of 3: 1.27 msec per loop
>>> python2 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'for x in iterable: pass'
1000 loops, best of 3: 935 usec per loop
という感じです。さらに高速化するために
deque
を使って反復処理することでさらに高速化できます。基本的には同じなのですが、これは
より速く
:
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 777 usec per loop
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 405 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 805 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 438 usec per loop
印象的なのは、Unicodeがbytestringsと競合していることです。これを明示的に確認するには
bytes
と
unicode
の両方で
-
bytes
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = b"".join(chr(random.randint(0, 127)).encode("ascii") for _ in range(100000))' 'deque(iterable, maxlen=0)' :( 1000 loops, best of 3: 571 usec per loop >>> python3 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)).encode("ascii") for _ in range(100000)]' 'deque(iterable, maxlen=0)' 1000 loops, best of 3: 394 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = b"".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)' 1000 loops, best of 3: 757 usec per loop >>> python2 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)' 1000 loops, best of 3: 438 usec per loop
ここで、Python 3を実際に見てみましょう。 より速く Python 2 よりも高速です。
-
unicode
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = u"".join( chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)' 1000 loops, best of 3: 800 usec per loop >>> python3 -m timeit -s 'import random; from collections import deque; iterable = [ chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)' 1000 loops, best of 3: 394 usec per loop
>>> python2 -m timeit -s 'import random; from collections import deque; iterable = u"".join(unichr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)' 1000 loops, best of 3: 1.07 msec per loop >>> python2 -m timeit -s 'import random; from collections import deque; iterable = [unichr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)' 1000 loops, best of 3: 469 usec per loop
これも予想通りではありますが、Python3の方が速いです(
str
は Python 3 で大いに注目されました)。
実は、この
unicode
-
bytes
の差は非常に小さく、印象的です。
では、この1つのケースを分析してみましょう。
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 777 usec per loop
>>> python3 -m timeit -s 'import random; from collections import deque; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'deque(iterable, maxlen=0)'
1000 loops, best of 3: 405 usec per loop
Tim Peterの10回投票された答えを除外することができます。
>>> foo = iterable[123]
>>> iterable[36] is foo
True
これらは新しいオブジェクトではありません!
しかし、これは言及する価値があります:インデックス作成 コスト . この差はおそらくインデックス作成にあるため、反復を削除してインデックスだけを作成します。
>>> python3 -m timeit -s 'import random; iterable = "".join(chr(random.randint(0, 127)) for _ in range(100000))' 'iterable[123]'
10000000 loops, best of 3: 0.0397 usec per loop
>>> python3 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'iterable[123]'
10000000 loops, best of 3: 0.0374 usec per loop
違いは小さいように見えますが 少なくとも コストの半分がオーバーヘッドです。
>>> python3 -m timeit -s 'import random; iterable = [chr(random.randint(0, 127)) for _ in range(100000)]' 'iterable; 123'
100000000 loops, best of 3: 0.0173 usec per loop
ということで、速度差は十分にあるので、非難することにした。と思うのですが、どうでしょうか。
では、なぜリストのインデックス作成が速くなったのでしょうか?
まあ、それについてはまた後で説明しますが、私が思うに、それは インターン 文字列 (または別のメカニズムであればキャッシュされた文字) のチェックに起因していると思われます。これは、最適な速度とは言えないでしょう。でも、ソースを確認してきます(C言語は不安ですが...) :).
というわけで、ソースはこちらです。
static PyObject *
unicode_getitem(PyObject *self, Py_ssize_t index)
{
void *data;
enum PyUnicode_Kind kind;
Py_UCS4 ch;
PyObject *res;
if (!PyUnicode_Check(self) || PyUnicode_READY(self) == -1) {
PyErr_BadArgument();
return NULL;
}
if (index < 0 || index >= PyUnicode_GET_LENGTH(self)) {
PyErr_SetString(PyExc_IndexError, "string index out of range");
return NULL;
}
kind = PyUnicode_KIND(self);
data = PyUnicode_DATA(self);
ch = PyUnicode_READ(kind, data, index);
if (ch < 256)
return get_latin1_char(ch);
res = PyUnicode_New(1, ch);
if (res == NULL)
return NULL;
kind = PyUnicode_KIND(res);
data = PyUnicode_DATA(res);
PyUnicode_WRITE(kind, data, 0, ch);
assert(_PyUnicode_CheckConsistency(res, 1));
return res;
}
上から歩いてきて、いくつかのチェックがあります。これらは退屈なものです。それからいくつかの代入がありますが、これも退屈なものでしょう。最初の面白い行は
ch = PyUnicode_READ(kind, data, index);
が、私たちは
望む
というのは、連続したC言語の配列からインデックスを付けて読み込んでいるからです。その結果
ch
でキャッシュされた文字を返します。
get_latin1_char(ch)
.
というわけで、(最初のチェックを外して)実行します。
kind = PyUnicode_KIND(self);
data = PyUnicode_DATA(self);
ch = PyUnicode_READ(kind, data, index);
return get_latin1_char(ch);
ここで
#define PyUnicode_KIND(op) \
(assert(PyUnicode_Check(op)), \
assert(PyUnicode_IS_READY(op)), \
((PyASCIIObject *)(op))->state.kind)
(これはつまらないことで、デバッグではアサートは無視され [高速であることを確認できる]、また
((PyASCIIObject *)(op))->state.kind)
はインダイレクトとCレベルのキャストであるため、退屈です)。
#define PyUnicode_DATA(op) \
(assert(PyUnicode_Check(op)), \
PyUnicode_IS_COMPACT(op) ? _PyUnicode_COMPACT_DATA(op) : \
_PyUnicode_NONCOMPACT_DATA(op))
(これも同様の理由でつまらないのですが、マクロを想定して(
Something_CAPITALIZED
)がすべて高速であると仮定します)。
#define PyUnicode_READ(kind, data, index) \
((Py_UCS4) \
((kind) == PyUnicode_1BYTE_KIND ? \
((const Py_UCS1 *)(data))[(index)] : \
((kind) == PyUnicode_2BYTE_KIND ? \
((const Py_UCS2 *)(data))[(index)] : \
((const Py_UCS4 *)(data))[(index)] \
) \
))
(これはインデックスを含みますが、実際には全く遅くありません) と
static PyObject*
get_latin1_char(unsigned char ch)
{
PyObject *unicode = unicode_latin1[ch];
if (!unicode) {
unicode = PyUnicode_New(1, ch);
if (!unicode)
return NULL;
PyUnicode_1BYTE_DATA(unicode)[0] = ch;
assert(_PyUnicode_CheckConsistency(unicode, 1));
unicode_latin1[ch] = unicode;
}
Py_INCREF(unicode);
return unicode;
}
という私の疑念を裏付けるものです。
-
これはキャッシュされています。
PyObject *unicode = unicode_latin1[ch];
-
これは速いはずです。その
if (!unicode)
は実行されないので、この場合は文字通り同等のPyObject *unicode = unicode_latin1[ch]; Py_INCREF(unicode); return unicode;
正直なところ、テストした結果
assert
は速いです (それらを無効にすることによって [I
と思う
Cレベルのアサートで動作します...])、唯一もっともらしく遅い部分があります。
PyUnicode_IS_COMPACT(op)
_PyUnicode_COMPACT_DATA(op)
_PyUnicode_NONCOMPACT_DATA(op)
どれが
#define PyUnicode_IS_COMPACT(op) \
(((PyASCIIObject*)(op))->state.compact)
(前回同様、高速)。
#define _PyUnicode_COMPACT_DATA(op) \
(PyUnicode_IS_ASCII(op) ? \
((void*)((PyASCIIObject*)(op) + 1)) : \
((void*)((PyCompactUnicodeObject*)(op) + 1)))
(マクロ
IS_ASCII
が速い)、そして
#define _PyUnicode_NONCOMPACT_DATA(op) \
(assert(((PyUnicodeObject*)(op))->data.any), \
((((PyUnicodeObject *)(op))->data.any)))
(アサート+インダイレクト+キャストなのでこれも速い)。
というわけで、(ウサギの穴に)降りた先は。
PyUnicode_IS_ASCII
というのは
#define PyUnicode_IS_ASCII(op) \
(assert(PyUnicode_Check(op)), \
assert(PyUnicode_IS_READY(op)), \
((PyASCIIObject*)op)->state.ascii)
うーん...これも早そう...。
まあ、いいんですが、比べてみましょう。
PyList_GetItem
. (うん。
感謝
Tim Peters が私に仕事を増やしてくれました :P.)
PyObject *
PyList_GetItem(PyObject *op, Py_ssize_t i)
{
if (!PyList_Check(op)) {
PyErr_BadInternalCall();
return NULL;
}
if (i < 0 || i >= Py_SIZE(op)) {
if (indexerr == NULL) {
indexerr = PyUnicode_FromString(
"list index out of range");
if (indexerr == NULL)
return NULL;
}
PyErr_SetObject(PyExc_IndexError, indexerr);
return NULL;
}
return ((PyListObject *)op) -> ob_item[i];
}
エラーでない場合、これはただ実行されるだけであることがわかります。
PyList_Check(op)
Py_SIZE(op)
((PyListObject *)op) -> ob_item[i]
ここで
PyList_Check
は
#define PyList_Check(op) \
PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LIST_SUBCLASS)
<ストライク ( TABS! TABS!!!! ) ( 問題21587 ) で修正され、マージされました。 5分 . みたいな...うん。くそ Skeetを恥ずかしく思うほどだ。
#define Py_SIZE(ob) (((PyVarObject*)(ob))->ob_size)
#define PyType_FastSubclass(t,f) PyType_HasFeature(t,f)
#ifdef Py_LIMITED_API
#define PyType_HasFeature(t,f) ((PyType_GetFlags(t) & (f)) != 0)
#else
#define PyType_HasFeature(t,f) (((t)->tp_flags & (f)) != 0)
#endif
というわけで、これは通常、本当につまらないものです(2つのインダイレクトといくつかのブール値チェック)。
Py_LIMITED_API
がオンでない限り、その場合は... ???
それから、インデックスとキャスト(
((PyListObject *)op) -> ob_item[i]
) で終わりです。
というわけで、間違いなく が少ない
のチェックが少なく、わずかな速度の違いは、それが関連する可能性があることを確かに示唆しています。
一般的には、より多くの型チェックとインダイレクトがあると思います。
(->)
を使用します。ポイントがずれているような気もしますが
なにごと
?
関連
-
[解決済み】TypeError: 系列を <class 'float'> に変換することができません。
-
[解決済み] PandasでDataFrameの行を反復処理する方法
-
[解決済み] 割り当て後にリストが予期せず変更されました。その理由と防止策を教えてください。
-
[解決済み] B "の印刷が "#"の印刷より劇的に遅いのはなぜですか?
-
[解決済み] 要素ごとの加算は、結合ループよりも分離ループの方がはるかに高速なのはなぜですか?
-
[解決済み] なぜC++はPythonよりもstdinからの行の読み込みが遅いのですか?
-
[解決済み] なぜlist.join(string)ではなくstring.join(list)なのでしょうか?
-
[解決済み] なぜ[]はlist()よりも速いのですか?
-
[解決済み】PyPyが6.3倍速いなら、CPythonよりPyPyを使うべきじゃないのか?
-
[解決済み】float < integerの比較で4倍遅くなるものがあるのはなぜですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Pythonを使って簡単なzipファイルの解凍パスワードを手作業で解く
-
Python Pillow Image.save jpg画像圧縮問題
-
[解決済み】ImportError: sklearn.cross_validation という名前のモジュールがない。
-
[解決済み】pygame.error: ビデオシステムが初期化されていない
-
[解決済み】終了コード -1073741515 (0xC0000135)でプロセス終了)
-
[解決済み】 AttributeError: モジュール 'matplotlib' には属性 'plot' がない。
-
[解決済み】TypeErrorを取得しました。エントリを持つ子テーブルの後に親テーブルを追加しようとすると、 __init__() missing 1 required positional argument: 'on_delete'
-
[解決済み】"No JSON object could be decoded "よりも良いエラーメッセージを表示する。
-
[解決済み】Python: SyntaxError: キーワードは式になり得ない
-
[解決済み】 'numpy.float64' オブジェクトは反復可能ではない