今回はDjangoの get_random_string
関数についてです。
僕のプログラミングコ学習コース
→【中級者向け・Django4対応】Python/DjangoによるECサイト開発講座(Django3.2系にも対応)
内でご質問いただいたので解説してみたいと思います。
ご質問内容は次のような感じです(一部省略・編集してます)。
get_random_string はランダムな値を生成するので、idとして使うと重複してしまうのではないでしょうか?
コースでの使用例
コースでの使用を簡単な例に書き換えたものが次のコードです。
from django.db import models
from django.utils.crypto import get_random_string
def create_id():
return get_random_string(22)
class Item(models.Model):
id = models.CharField(default=create_id, primary_key=True, max_length=22, editable=False)
...
モデルを定義する時に、idフィールドに対して、CharFieldを設定し、そのdefault値にget_random_string関数で実行した値を渡しています。
Itemモデルのid(pk)に対してランダムな22文字の文字列を渡しているということになります。
get_random_string を使う
get_random_string がどんなものかを確認するために使ってみたいと思います。
Djangoでは次のようにインポートして get_rangom_string を使うことができます。
from django.utils.crypto import get_random_string
get_random_stringはランダムな文字列を生成してくれる関数です。
基本的な使い方は簡単で次のように文字数を引数に指定してあげるだけです。
get_random_string(12)
この場合は12文字のランダムな文字列を生成することになります。
試しに3回ほど実行してみた結果です。
# 1回目: 'Paz3kzrlnREK'
# 2回目: 'Yj4M6vRaze0R'
# 3回目: 'zJZ8Lf3AZFEx'
get_random_stringは重複するか検証
ご質問の内容において重複しそうか検証してみます。
次のコードでは100万回get_random_stringを実行し、既に実行された値を集合として保持し、重複しているか確認します。
集合ではなくリストに保持し、in演算子を使っても検証できますが、100万回のループではすごく時間がかかるのでここでは集合を使い重複した場合は要素数にずれが生じることを利用してチェックしています。
from django.utils.crypto import get_random_string
r = set()
for n in range(1000000):
s = get_random_string(12)
r.add(s)
if (n+1) != len(r):
print(f"Duplicate found: {s} at {n}")
break
検証の結果、100万回実行しても重複しないことが確認できました。
また、コースでは22文字なので、さらに重複しない可能性は格段に上昇しています。
どれぐらい重複しないのか
ランダムに生成している以上、被る可能性は理論的にあります。ただ現実的な時間内では被らないことをほぼ保証できます。
例えばget_random_stringを12文字で実行した場合を考えてみます。
get_random_stringのデフォルトで設定されている文字は
- 大文字英字(26文字)
- 小文字英字(26文字)
- 数字(10)
となります。
つまり、全部で62文字の中から1文字が選ばれることになります。
それを12回繰り返すことになるので、組み合わせ数を計算すると、
62の12乗、つまり3垓2262京のパターンが存在することになります。
これは一説には同じものを当てるためにはコンピュータでも2000年かかるぐらいの規模のようです。
この時点で、重複に関してはほぼ無視していい確率となります。さらにコースでは12文字ではなく22文字にしているので、被らないと考えて大丈夫です。
逆に考えると、被った場合は宝くじの1等を当てるよりも奇跡を引き当てたことになるので喜ばしいことかもしれませんね。
idにget_random_stringを使用した理由
djangoではid(pk)を明示的に指定しない場合は連番になります。これも被らないことが保証されるので、一番楽かもしれません。
ただ、連番の場合はそのidをURLパスなどに使用すると、いくつデータ(レコード)があるのかを大まかに推測できることになります。
例えばuserのidが連番になっていて、userのidが15なら、15番目のuserということが推測できたりします。
気にしないならば良いのですが、大抵のユーザーIDやECサイトの商品IDなどはランダムな文字列になっています。
本コースでもその点を考慮してget_random_stringを使用しました。
idにはよくUUIDを使ったりしますが、UUID も基本的に同じ考え方です。UUIDでも良かったのですが、ハイフンが入るので見た目文字列が長くなるので使いませんでした。この辺りは好みの問題になります。
get_random_stringやuuidを使うと何が良いかというと、重複することがまずありえないので、毎回のIDの重複チェックを不要にできることです。
ちなみに今回取り上げたget_random_string関数はDjangoのソースコード内部でも、session_keyやsecret_keyに対しても(文字数などは異なりますが)使用されています。
以上のように、極端に文字数を少なくしないのであれば積極的に使ってもいい関数だと思いますので、ぜひ使ってみてください。