[PYTHON] Participé à la première ISUCON avec l'équipe "Ranchu" # ISUCON10 Qualifying

TL;DR L'implémentation des références Rust est rapide Accablant grâce à New Relic.

Merci pour votre travail acharné dans la qualification ISUCON10

localhost_8080_.png

Le score final était de «722 (valeur de référence)». isuumo, je pense que c'était une question intéressante. Merci à la direction qui a pris soin de nous en préparant la journée, et aux membres qui se sont battus ensemble! Je vous remercie pour votre travail acharné!

Ranchu

C'est une équipe de 3 étudiants de M1 seniors (Masapyon, koba) et B3 serviteur (Hiroya_W) de la même université. À ce moment-là, j'ai entendu dire que le quota de qualification ISUCON10 était susceptible de se remplir immédiatement, alors je me suis rapidement inscrit pour participer en solo. Après cela, j'ai recruté des membres, alors on m'a demandé de me joindre!

image.png

"ISUCON Summer Course 2020 Classroom & Mokumokukai" J'ai découvert ISUCON pour la première fois, et depuis que j'ai participé à la fois à la conférence en classe et au Mokumokukai, j'y ai participé. Participons! Je pensais. Au début, les Mokumoku-kai se demandaient si chacun d'eux résoudrait le problème.

Cependant, quand j'y ai effectivement participé, c'était une réunion où tout était préparé et tout le monde pouvait se réunir et m'apprendre à faire des pas. Je pense que c'était un très bon cours car il était uniquement destiné à ceux qui n'ont jamais participé à ISUCON dans le passé.

Préparation préalable

J'avais l'intention de participer à Go parce que j'ai utilisé Go dans un autre cas et il y a beaucoup de gens qui participent à Go. Alors, tout d'abord, j'ai touché "A Tour of Go" pour le moment. À partir de là, j'ai pensé essayer le didacticiel sur le framework Web de Go, mais je ne l'ai pas fait.

C'est parce que j'ai participé à un webinaire organisé par New Relic "Comment utiliser New Relic rapidement avec ISUCON parce que c'est gratuit". Lors de l'installation de l'agent de New Relic dans Go

Quand j'ai appris ça, j'ai eu l'impression que ça allait rester coincé ici. En comparaison, Python et Ruby sont simplement installés en tant que packages dans votre application et dans la plupart des cas, vous n'avez pas à les écrire vous-même. Dans ce cas, tout le monde peut lire en toute confiance en Python, je veux donc m'assurer d'utiliser New Reilc! Je suis passé à la politique.

De là à la performance réelle, j'ai utilisé [^ 1] uniquement pour la qualification ISUCON9 afin d'acquérir les connaissances nécessaires. Lors de la lecture du Résumé des entrées liées aux qualifications en ligne ISUCON9 depuis le début et en pratiquant individuellement, Environnement ISUCON avec Vagrant / matsuu / vagrant-isucon) a été créé, et lorsque les membres se sont réunis et ont pratiqué, j'ai créé un environnement ISUCON dans GCP et pratiqué.

Ce que j'ai fait, c'est simplement écrire ce que j'ai essayé sous forme d'article sur esa.io, et finalement j'ai organisé les éléments dans le Wiki du dépôt GitHub. Je suis désolé de ne pas pouvoir publier la clé de licence de New Relic car elle est écrite en texte brut [^ 2].

image.png

Je pense que les bases telles que la façon de prendre des logs tels que Makefile [^ 3] et New Relic, la sauce secrète, et comment mettre en place INDEX autour de DB sont le principal objectif. Je l'ai résumé afin qu'il puisse être utilisé comme un résumé d'articles qui peuvent être utiles lors du dépannage.

Au jour

Les seniors se sont réunis dans le laboratoire et j'ai participé en ligne depuis chez moi. J'ai utilisé un service appelé whereby parce que je voulais pouvoir voir les écrans de travail de tous les membres de l'équipe, mais c'était très facile à utiliser [^ 4].

Le départ étant reporté à 12h20, nous avons décidé de nous réunir vers 11h30, et à ce moment-là, nous avons eu un repas léger.

image.png

Dans la matinée, je n'ai pu que constater que l'opération était du vrai ISUCON. Je pense qu'il a été difficile de se préparer au problème, mais encore une fois, merci beaucoup.

Après cela, ** ISUCON10 a une implémentation Rust, ce qui est intéressant - cela semble être tôt. Ceci est une ligne cachée. ** **

Division des rôles

Dans le planning

Cependant, grâce au fait que j'ai pu étudier quotidiennement l'infrastructure, la répartition réelle des rôles était

Avec ce sentiment, je me suis aussi un peu occupé de l'infrastructure, et en attendant, j'ai demandé à Masapyon de m'aider avec l'application.

De là, j'écrirai ce que j'ai fait ce jour-là et ce que les membres ont fait pour autant que je me souvienne.

12:20 ~ 13:11 J'ai pu accéder au serveur par SSH et Masapyon l'a poussé sur GitHub. De plus, il a vérifié la configuration, et comme il s'agissait de Nginx et de MySQL, j'étais un peu soulagé que ce soit la même chose que la qualification ISUCON9.

En attendant, Hiroya_W et koba lisent le manuel, résument les points importants du wiki et touchent réellement l'application.

Cependant, j'ai eu du mal à transférer et à afficher l'application sur le navigateur ... Le manuel avait des instructions sur la façon d'utiliser la commande, mais je ne l'avais jamais utilisé de cette façon, donc j'étais inquiet parce que je ne pouvais pas comprendre comment donner les arguments.

Quand j'ai demandé à Google Sensei, j'ai trouvé une histoire sur la redirection de port SSH. Je vois. Maintenant que nous savons que nous pouvons communiquer entre les trois instances permanentes, pourquoi pas SSH dans le serveur 2 et diffuser de là vers le port spécifié par le serveur 1? ?? ?? Comprendre que

$ ssh -L localhost:8080:10.162.41.101:80 isucon-server2 

J'étais heureux d'être connecté. Il est environ 13h11. Après avoir terminé, un autre membre de l'équipe m'a dit que je devrais SSH sur le serveur 1 et transférer le port vers localhost: 80 vu du serveur 1. Cela va certainement se connecter.

$ ssh -L localhost:8080:localhost:80 isucon-server1

J'ai vraiment appris SSH grâce à ISUCON [^ 5].

13:11 ~ 13:30 J'ai en quelque sorte touché l'application, et contrairement à ISUCON dans le passé, isuumo n'était pas si lourd si je le touchais avec mes mains, et je ne pouvais pas immédiatement juger où c'était lent car il n'y avait pas de situation visiblement lente. Donc, tout en disant que je ne peux pas procéder sans journalisation en tournant le banc, je vais d'abord faire tourner le banc plusieurs fois avec Python sans journalisation après confirmation.

Je regardais «htop» sur le banc, mais est-ce vraiment avec CPU1 core et RAM2GB? ?? ?? ?? Je disais. L'environnement dans lequel je pratiquais avait des spécifications de machine, alors j'ai commencé à penser qu'il serait nécessaire de revoir le réglage des paramètres.

De plus, personne n'a pu pratiquer la configuration à deux unités, et j'avais peur que cela échoue au test de redémarrage, donc je prévoyais de régler avec une unité cette fois. Cependant, en regardant cette spécification, je pensais que l'histoire ne se poursuivrait que si j'avais configuré au moins deux unités, alors j'ai tranquillement décidé que Hiroya_W la prendrait en charge.

13:30 ~ 15:00 Quoi qu'il en soit, c'est un journal. Hiroya_W est en charge de la sortie des journaux, de l'agrégation et du partage sur Slack. Tout d'abord, au lieu de New Relic, nous avons introduit wsgi_lineprof, qui est un profileur de ligne pour Python [^ 6]. (Comme vous le verrez plus tard, par conséquent, le journal n'a pas été généré pour une raison quelconque)

Cependant, je suis sûr que je ne peux pas exécuter le benchmark ici en raison d'un dysfonctionnement du banc, et je ne peux pas obtenir le journal.

J'ai décidé d'utiliser ce temps pour mettre en place un environnement de développement localement. Cependant, lorsque j'exécute l'application localement et que j'accède à http: // localhost: 1323, 404 est renvoyé. Flask n'est pas sur Gunicorn et Flask ne renvoie pas render_template, et alors? Ça devient.

J'ai lu nginx.conf et j'ai confirmé que Nginx renvoyait un fichier statique situé dans / www / data /. Le banc semblait être fait pour l'API aussi, j'ai donc décidé de le vérifier localement en POSTing ou en GETting avec curl. Pour vérifier l'opération, coupez la branche avec git et tirez-la sur le serveur comme il convient pour l'essayer.

15:00 ~ 15:40 Il semble que le repère du banc ait été relancé. Immédiatement, j'ai essayé de sortir le journal avec wsgi_lineprof, mais il ne sortait pas bien. Pour le moment, les requêtes lentes de MySQL qui ont été générées en même temps sont agrégées pt-query-digest et partagées sur Slack. Pendant que Koba et Masapyon regardaient autour de la DB, je suis immédiatement passé à essayer New Relic sans rechercher la cause.

Enfin, à 15h40, New Relic a commencé à voir les journaux et toutes les requêtes MySQL sont apparues. New Relic Super, je peux tout voir.

Le score à ce stade est "353". J'ai l'impression que je suis enfin arrivé sur la ligne de départ d'ISUCON. image.png

15:40 ~ 16:50 À partir de là, nous allons contester la configuration à deux unités d'App et de DB.

S'appuyant sur les notes que j'ai écrites après avoir lu l'entrée de qualification ISUCON9

--Définissez la variable d'environnement MYSQL_HOST sur l'adresse IP de l'instance --Commentez l'option d'adresse de liaison MySQL -Ajouter des autorisations à l'utilisateur isucon tout en regardant Autoriser MySQL à être connecté en externe

Je vais essayer. Il y a eu un incident où je voulais me connecter de Server001 à Server002, mais j'ai pu me connecter de Server002 à Server001, mais j'ai réussi à terminer la configuration à deux unités. Ici, le score augmente légèrement à "435". image.png

16:50 ~ 18:33(koba, Masapyon) De là, nous appliquerons les correctifs que Koba et Masapyon ont apportés. Mais comme vous pouvez le voir sur les résultats du banc, rien ne monte vraiment. Même si je le tourne plusieurs fois, cela ne s'améliore pas. image.png

Le commit INDEX que koba a mis en place ressemble à ceci (je verrai plus tard, mais en fait, cela seul n'a pas exécuté la mise en place d'INDEX)

isuumo/webapp/mysql/db/3_IndexEstate.sql


CREATE INDEX idx_door_height ON isuumo.estate(door_height);
CREATE INDEX idx_door_width ON isuumo.estate(door_width); 

isuumo/webapp/mysql/db/init.sh


- cat 0_Schema.sql 1_DummyEstateData.sql 2_DummyChairData.sql | mysql --defaults-file=/dev/null -h $MYSQL_HOST -P $MYSQL_PORT -u $MYSQL_USER $MYSQL_DBNAME
+ cat 0_Schema.sql 1_DummyEstateData.sql 2_DummyChairData.sql 3_IndexEstate.sql | mysql --defaults-file=/dev/null -h $MYSQL_HOST -P $MYSQL_PORT -u $MYSQL_USER $MYSQL_DBNAME

La fonction [^ 7] qui renvoie 503 Service Unavailable à la communication Bot implémentée par Masapyon ressemble à ceci.

app.py


bot_user_agent = re.compile(
    r'ISUCONbot(-Mobile)?|ISUCONbot-Image\/|Mediapartners-ISUCON|ISUCONCoffee|ISUCONFeedSeeker(Beta)?|crawler \(https:\/\/isucon\.invalid\/(support\/faq\/|help\/jp\/)|isubot|Isupider|Isupider(-image)?\+|(bot|crawler|spider)(?:[-_ .\/;@()]|$)/i')

app.py


def block_bot(request):
    user_agent = request.headers.get('User-Agent')
    if user_agent:
        if bot_user_agent.match(user_agent):
            return True
    return False

app.py


    #Faites-le fonctionner en premier sur chaque point de terminaison
    if block_bot(request):
        return jsonify({'message': 'Service Unavailable'}), 503

Cependant, le score n'augmente pas ...

Je l'ai essayé avec curl et j'ai confirmé qu'il retournait correctement 502, mais je me demandais parce que je ne pouvais pas obtenir le journal qui retournait 502 sur le banc, mais la charge était trop faible et le bot n'y avait pas accès Alors, j'ai entendu l'histoire.

De plus, koba change COUNT (*) en COUNT (id),

app.py


-    query = f"SELECT COUNT(*) as count FROM chair WHERE {search_condition}"
+    query = f"SELECT COUNT(id) as count FROM chair WHERE {search_condition}"
-    query = f"SELECT COUNT(*) as count FROM estate WHERE {search_condition}"
+    query = f"SELECT COUNT(id) as count FROM estate WHERE {search_condition}"

Masapyon a imaginé un endroit pour vérifier si la chaise passait par la porte,

app.py


     w, h, d = chair["width"], chair["height"], chair["depth"]
     query = (
         "SELECT * FROM estate"
         " WHERE (door_width >= %s AND door_height >= %s)"
         "    OR (door_width >= %s AND door_height >= %s)"
         "    OR (door_width >= %s AND door_height >= %s)"
-        "    OR (door_width >= %s AND door_height >= %s)"
-        "    OR (door_width >= %s AND door_height >= %s)"
-        "    OR (door_width >= %s AND door_height >= %s)"
         " ORDER BY popularity DESC, id ASC"
         " LIMIT %s"
     )
-    estates = select_all(query, (w, h, w, d, h, w, h, d, d, w, d, h, LIMIT))
+    estates = select_all(query, (w, min(h, d), h, min(w, d), d, min(w, h), LIMIT))

Au lieu de compter en SQL avec / api / estate / search et / api / chair / search, vous pouvez compter avec Python,

app.py


    #C'est la partie domaine
    search_condition = " AND ".join(conditions)

    query = f"SELECT * FROM estate WHERE {search_condition} ORDER BY popularity DESC, id ASC"
    chairs = select_all(query, params)
    count = len(chairs)
    chairs = chairs[per_page * page:per_page * page + per_page]

    return {"count": count, "estates": camelize(chairs)}

J'ai trouvé dans le journal de validation qu'il le faisait. Je n'ai pas pu participer à cette discussion d'amélioration, mais je suis reconnaissant d'avoir essayé diverses choses et je me sens à nouveau en écrivant une entrée.

C'était très douloureux de ne voir aucune amélioration de mon score.

18:33 ~ ?? Je remarque que INDEX n'est pas étiré. Je suis content d'avoir remarqué ...

Même si j'ai vérifié le journal avec New Relic, il n'y avait pas d'atmosphère étrange, est-ce vraiment INDEX? C'est devenu une atmosphère comme ça. Quand MySQL m'a demandé de vérifier si INDEX était configuré avec une commande,

Masapyon a constaté que la partie INDEX n'a pas été exécutée.

app.py


@app.route("/initialize", methods=["POST"])
def post_initialize():
    sql_dir = "../mysql/db"
    sql_files = [
        "0_Schema.sql",
        "1_DummyEstateData.sql",
        "2_DummyChairData.sql",
    ]

Y a-t-il un gars sql_files ... Dans la qualification ISUCON9, ʻinitialize`

subprocess.call(["../sql/init.sh"])

Était en cours d'exécution, alors je me suis demandé si c'était le cas.

koba>INDEX est init.Est-il correct d'écrire en sh?
Hiroya_W, Masapyon>Je pense que ça va ~

Je me souviens avoir eu une conversation. Vous ne pouvez pas l'étirer ...

Puis, pour la première fois, le score a légèrement augmenté. J'avais l'habitude de faire des allers-retours entre 340 et 380, donc j'étais ravi d'être près de 400.

image.png

À ce stade, je suis enfin arrivé à 18h30! Je suis content d'être content! Je disais. Jusqu'à présent, je ne pouvais pas mettre l'INDEX dessus correctement et je ne pouvais pas vérifier le score correctement, alors j'ai remis en place l'INDEX et j'ai tourné le banc pour le mesurer.

16:50 ~ ??(Hiroya_W)

La chronologie est revenue un peu en arrière et Hiroya_W essayait de passer de MySQL 5.7 à MySQL 8 pendant que koba et Masapyon prenaient les correctifs et faisaient tourner le banc. C'est simplement parce que j'ai eu l'information très approximative que "MySQL 8 est plus rapide". Il n'y a aucun moyen de savoir que les fonctionnalités de MySQL 8 peuvent être utilisées dans ISUCON 10.

De plus, personne dans notre équipe ne peut apprendre à configurer 3 unités, que ce soit App, DB, DB ou App, App, DB. Alors, j'en ai laissé un derrière, alors j'ai décidé de relever le défi avec l'idée que je pourrais le casser.

À la suite de la mise à niveau vers MySQL8 et de la transformation du banc, cela ressemble à ceci.

image.png

La finition avec 0 est bloquée dans le contrôle d'intégrité de l'application. Si vous y réfléchissez bien, le banc que vous avez couru immédiatement après obtiendra un score et sera finalement annulé.

D'ailleurs, même s'il obtient un score, il ne semble pas être meilleur que MySQL 5.7 avec 100 unités. Je ne l'ai pas bien compris, je n'ai pas compris pourquoi le contrôle de cohérence de l'application n'a pas réussi et je ne voulais pas échouer dans le test supplémentaire de l'opération car le banc était instable, j'ai donc abandonné la mise à niveau vers MySQL8.

Je pense qu'il aurait peut-être été bon de se renseigner à ce sujet pendant la production. Concernant MySQL8, c'est un point que je voudrais bien revoir.

?? ~ 20:20 J'ai mis la sauce secrète de MySQL et Nginx que j'avais préparée.

koba ajoute INDEX,

3_IndexEstate.sql


  CREATE INDEX idx_door_height ON isuumo.estate(door_height);
- CREATE INDEX idx_door_width ON isuumo.estate(door_width); 
+ CREATE INDEX idx_door_width ON isuumo.estate(door_width);
+ CREATE INDEX idx_latitude ON isuumo.estate(latitude);
+ CREATE INDEX idx_longitude ON isuumo.estate(longitude);
+ CREATE INDEX idx_rent ON isuumo.estate(rent);
+ CREATE INDEX idx_popularity ON isuumo.estate(popularity); 

4_IndexChair.sql


CREATE INDEX idx_popularity ON isuumo.chair(popularity);
CREATE INDEX idx_price ON isuumo.chair(price);
CREATE INDEX idx_color ON isuumo.chair(color);
CREATE INDEX idx_height ON isuumo.chair(height);
CREATE INDEX idx_width ON isuumo.chair(width);
CREATE INDEX idx_kind ON isuumo.chair(kind);
CREATE INDEX idx_stock ON isuumo.chair(stock);
CREATE INDEX idx_features ON isuumo.chair(features); 

Masapyon a appliqué les changements et a retourné le banc. Fusionnez un certain nombre de changements dans le master, faites tourner le banc et enregistrez un score 590 en changeant la chaise avec INDEX. Chaque fois que je l'ai tourné, le score a augmenté et je pense que c'était incroyable.

image.png

Les trois premiers bancs étaient le résultat d'un réglage autour de mon système d'exploitation, mais le score a chuté, alors j'ai décidé de ne pas le remettre immédiatement.

20:20 ~ 21:00 C'était moins d'une heure après la fin, j'ai donc décidé de faire un test de redémarrage, mais avant cela, je me demandais combien le score allait sortir avec l'implémentation de référence de Rust, alors j'ai décidé de lancer le banc.

image.png

** Atteignez en toute sécurité le score le plus élevé. ** ** C'est plus rapide que l'implémentation Python optimisée [^ 8]! !! C'est déjà un gros rire.

Étant donné que les modifications autour de l'application ne fonctionnaient pas pour la partition, j'ai décidé de supprimer toutes les modifications de l'application et de passer à l'implémentation Rust. Modifié pour accéder à MySQL d'une autre instance à partir de la partie où INDEX de DB est placé, 20:30

Après avoir redémarré, tournez le banc pour mettre à jour davantage le score final et «722».

image.png

Redémarrez fermement le test Essayez App → DB, DB → App, et confirmez qu'il fonctionne correctement et terminez.

Réflexions

J'ai lu des informations sur la mise à niveau effective de MySQL 5.7 vers MySQL 8 dans la qualification ISUCON9, donc je pense que j'aurais dû l'essayer à l'avance. À partir de là, j'aurais dû comprendre les fonctionnalités de MySQL 8. De plus, je pense que le fait que tous les membres aient peu de connaissances sur Nginx et MySQL cette fois a été poignardé. Surtout cette fois, j'ai l'impression que DB est le centre, et je sens que je ne pourrais pas me déconnecter de la situation actuelle avec App seule.

Impressions

En conséquence, je viens de mettre INDEX sur l'implémentation initiale de Rust et DB. Pourtant, le processus pour y arriver était intéressant, donc ça va.

Quel que soit le résultat, j'ai l'impression d'avoir pu regarder le journal, rechercher la partie lente et essayer de corriger la partie proche de la bonne réponse. Je pense que la première partie difficile d'ISUCON est de préparer l'environnement et de l'amener à ce stade, donc je suis heureux que cela ait été fait correctement en production.

De plus, en écrivant cette entrée, je suis très reconnaissant de pouvoir voir les membres qui ont essayé diverses choses à partir du journal de validation. Je suis content de vous l'avoir laissé, et au contraire, je tiens à remercier les membres de m'avoir permis de relever le défi.

Je vais profiter de cette expérience pour le prochain ISUCON 11 et relever le défi! Nous avons hâte de vous revoir la prochaine fois!

[^ 1]: J'ai essayé d'autres problèmes, mais j'ai abandonné dans les qualifications ISUCON8 parce que le banc est tombé quand j'ai mis en New Relic (la cause semble être browser_monitoring). J'ai abandonné ISUCON9 parce que je ne comprenais pas Docker. Bien que l'on dise qu'il s'agit uniquement de la qualification ISUCON9, il peut être correct de n'utiliser que la qualification ISUCON9 ** "**" car il y a des choses que vous faites et que vous ne savez pas.

[^ 2]: Je veux le publier un jour.

[^ 3]: Depuis que j'ai eu une session d'étude Make juste avant, ["J'ai obtenu le score le plus élevé le premier jour des qualifications ISUCON9"](https://to-hutohu.com/2019/09/9 09 / isucon9-qual / #% E8% BE% 9E% E9% 80% 80% E3% 81% 97% E3% 81% 9F% E7% 90% 86% E7% 94% B1). C'était opportun.

[^ 4]: Au départ, je prévoyais d'utiliser Zoom, mais même si j'ai essayé de partager les écrans de tout le monde avec Zoom, j'ai abandonné car je ne savais pas comment voir l'écran de l'autre partie tout en partageant l'écran. Cependant, que ce soit par où ou autre, la navigation tout en partageant l'écran et en codant avec VSCode semble être la limite dans les spécifications de mon PC, et on a découvert que cela entraverait mon travail, alors j'ai partagé mon écran du milieu. Est devenu une forme le cas échéant. CPU: Core i5 4210, RAM: 8 Go, mais le taux d'utilisation du processeur est resté bloqué et la température du processeur est maintenue à 80 ℃, alors j'ai rapidement sorti le ventilateur de refroidissement du PC portable.

[^ 5]: J'étais encombré de problèmes tels que le placement des clés, l'autorité et les empreintes digitales à l'avance, donc mes connaissances se sont naturellement accumulées.

[^ 6]: Quand j'ai fait un geste New Relic avec l'implémentation Python de la qualification ISUCON9, le journal des requêtes de MySQL n'est pas apparu sauf pour set, commit, rollBack, et je n'ai pas pu voir le SELECT essentiel du tout. Je suis content de l'avoir écrit sur Explorer Hub de New Relic à des fins de recherche. De plus, lorsque j'ai tourné le banc lors des qualifications ISUCON8, la structure du DOM était censée être ~ et le banc est tombé (la cause semble être le navigateur_monitoring), et c'est devenu une histoire sur la façon d'utiliser New Relic, et c'est différent pour l'époque. J'ai établi la méthode de.

[^ 7]: Je pensais que cela serait implémenté dans une application Python, mais apparemment cela peut être réalisé avec Nginx ... Je ne savais pas.

[^ 8]: J'ai l'impression de ne pas avoir mesuré le score lorsque New Relic est supprimé en Python.

Recommended Posts

Participé à la première ISUCON avec l'équipe "Ranchu" # ISUCON10 Qualifying
J'ai participé au tour de qualification ISUCON10!
Ce que j'ai appris en participant aux qualifications ISUCON10
TensorFlow 2.X & TensorRT est la première étape pour accélérer l'inférence
La première étape de Python Matplotlib
Afficher Python 3 dans le navigateur avec MAMP
MongoDB avec Python pour la première fois
Connectez-vous à un serveur distant avec SSH
[Python] Récupérez les fichiers dans le dossier avec Python
12. Enregistrez la première colonne dans col1.txt et la deuxième colonne dans col2.txt
La première étape du problème de réalisation des contraintes en Python
Déterminez les nombres dans l'image prise avec la webcam
Détecter les dossiers avec la même image dans ImageHash
L'histoire qui s'inscrit dans l'installation de pip
J'ai fréquenté l'école et j'ai participé pour la première fois au concours limité BEGINNER de SIGNATE.