Mikrodienst mit Python (Übersicht)

1. Zuallererst

Vor kurzem hatte ich die Gelegenheit, die Entwicklung von Microservices (sogenannten REST-APIs) mit Lightweight Language (LL) zu untersuchen. Es gibt verschiedene Sprachen wie Ruby, Go, Python usw., auch wenn es LL heißt. Da es jedoch viele Bekannte gab, die Python im Bereich des maschinellen Lernens und des Internet der Dinge verwenden, wird diesmal über die Implementierung von Microservices mit Python gesprochen Ich möchte es als Memorandum behalten. Ich lese Python zum ersten Mal, daher hoffe ich, dass Sie es in diesem Sinne lesen.

1.1 Überprüfungsumgebung

Die in dieser Überprüfung verwendete Umgebung ist wie folgt. Ich habe die virtuelle Python-Umgebung diesmal nicht ausprobiert. (Weil gesagt wird, dass es unter Windows nicht funktioniert)

1.2 Zu verwendende Bibliothek

Diesmal handelt es sich um eine Bibliothek, mit der ein Makrodienst in Python erstellt wird. Alle sind einfache Bibliotheken, die einfach zu bedienen sind und mit "pip install" installiert werden können.

Artikelnummer Bibliothek Überblick Seite? ˅
1 flask Leichtes MVC-Framework http://flask.pocoo.org/
2 peewee O/R-Zuordnung http://docs.peewee-orm.com/en/latest/
3 psycopg2 Erforderlich für den PostgreSQL-Zugriff https://pypi.python.org/pypi/psycopg2
4 cerberus Eingabeprüfungsbibliothek http://docs.python-cerberus.org/en/stable/
5 requests HTTP-Client http://docs.python-requests.org/en/master/

2 Umweltbau

2.1 Python installieren

Ich habe es laut Installer installiert. Das Installationsziel ist das Standardziel "C: \ Python27".

2.2 Pass

Die folgenden zwei Stellen wurden der Umgebungsvariablen "Pfad" hinzugefügt. (Als Referenz ist das "Pfad" -Trennzeichen in Windows ";")

2.3 Umgebungsvariablen so einstellen, dass sie Proxy überschreiten

Wenn aufgrund der Netzwerkkonfiguration der Organisation ein Proxy für den Internetzugang erforderlich ist, legen Sie die Umgebungsvariable so fest, dass Proxy überschritten wird.

Dies kann entweder eine Benutzerumgebungsvariable oder eine Systemumgebungsvariable sein. Wenn Sie es nur bei der Installation von pip einstellen möchten, empfehlen wir Ihnen, es vorübergehend mit set einzustellen.

set HTTPS_PROXY=userid:[email protected]:8080
set HTTP_PROXY=userid:[email protected]:8080

Passen Sie den Proxy-Host, die Portnummer, die Benutzer-ID und das Kennwort an Ihre Umgebung an. Wenn für den Proxy keine Benutzerauthentifizierung erforderlich ist, muss er nicht vor "@" stehen.

2.4 Legen Sie die URL, die Proxy nicht durchlaufen soll, als Umgebungsvariable fest

Möglicherweise gibt es URLs, die Sie nicht über Proxy verwenden möchten, z. B. wenn Sie auf einen Server in einem privaten Netzwerk zugreifen möchten. Verwenden Sie in diesem Fall die Umgebungsvariable "NO_PROXY".

set NO_PROXY=127.0.0.1;localhost;apserver

Wenn Sie versuchen, mit "Anfragen" auf einen von Ihnen vorbereiteten Server zuzugreifen Aus irgendeinem Grund konnte ich nicht über Proxy darauf zugreifen, daher habe ich beschlossen, die Umgebungsvariable NO_PROXY festzulegen.

2.5 Installieren Sie die erforderlichen Bibliotheken mit pip

Auch dieses Mal habe ich die virtuelle Python-Umgebung nicht ausprobiert. Also habe ich es direkt mit pip installiert.

pip install peewee
pip install psycopg2
pip install requests
pip install cerberus
pip install flask

3 Konzeptanwendung

Lassen Sie uns eine Anwendung implementieren, die die minimale Verwendung der diesmal eingeführten Bibliotheken (flask, cerberus, peewee) einführt.

Dieses Mal werden wir die Funktionsaufteilung oder Dateidivision nicht entsprechend der Verantwortung berücksichtigen. Zur besseren Sichtbarkeit verwenden wir einen Quellcode für die Microservice-Webanwendung und einen Quellcode für den Client, der den Microservice verwendet.

3.1 Übersicht über die Konzept-App

Es handelt sich um eine Microservice- (oder REST-API-) Anwendung mit einer API, die nur Datensätze zur unten gezeigten Fragentabelle hinzufügt.

create_table.sql


DROP TABLE IF EXISTS question;

CREATE TABLE question (
    question_code   VARCHAR(10)    NOT NULL,
    category        VARCHAR(10)    NOT NULL,
    message         VARCHAR(100)   NOT NULL,
    CONSTRAINT question_pk PRIMARY KEY(question_code)
);

Die Anwendungsspezifikationen und Einschränkungen sind wie folgt. Es scheint, dass die Spezifikationen gemischte Zwecke haben, aber das Folgende sind die Umsetzungspunkte.

3.2 Demo-Mikroservice

3.2.1 Quellcode

demoapp.py


# -*- coding: utf-8 -*-
import os
from flask import Flask, abort, request, make_response, jsonify
import peewee
from playhouse.pool import PooledPostgresqlExtDatabase
import cerberus

# peewee
db = PooledPostgresqlExtDatabase(
    database = os.getenv("APP_DB_DATABASE", "demodb"),
    host = os.getenv("APP_DB_HOST", "localhost"),
    port = os.getenv("APP_DB_PORT", 5432),
    user = os.getenv("APP_DB_USER", "postgres"),
    password = os.getenv("APP_DB_PASSWORD", "postgres"),
    max_connections = os.getenv("APP_DB_CONNECTIONS", 4),
    stale_timeout = os.getenv("APP_DB_STALE_TIMEOUT", 300),
    register_hstore = False)

class BaseModel(peewee.Model):
    class Meta:
        database = db

# model
class Question(BaseModel):
    question_code = peewee.CharField(primary_key=True)
    category = peewee.CharField()
    message = peewee.CharField()

# validation schema for cerberus
question_schema = {
    'question_code' : {
        'type' : 'string',
        'required' : True,
        'empty' : False,
        'maxlength' : 10,
        'regex' : '^[0-9]+$'
    },
    'category' : {
        'type' : 'string',
        'required' : True,
        'empty' : False,
        'maxlength' : 10
    },
    'message' : {
        'type' : 'string',
        'required' : True,
        'empty' : False,
        'maxlength' : 100
    }
}

# flask
app = Flask(__name__)

# rest api
@app.route('/questions', methods=['POST'])
def register_question():
    #Überprüfen Sie die Eingabe
    v = cerberus.Validator(question_schema)
    v.allow_unknown = True
    validate_pass = v.validate(request.json)
    if not validate_pass:
        abort(404)
    
    #Geschäftslogik aufrufen
    result = register(request.json)
    #Rückgabe des Verarbeitungsergebnisses (JSON-Format)
    return make_response(jsonify(result))

# error handling
@app.errorhandler(404)
def not_found(error):
    return make_response(jsonify({'error' : 'Not Found'}), 404)

@app.errorhandler(500)
def server_error(error):
    return make_response(jsonify({'error' : 'ERROR'}), 500)

#Geschäftslogik
@db.atomic()
def register(input):
    # create instance
    question = Question()
    question.question_code = input.get("question_code")
    question.category = input.get("category")
    question.message = input.get("message")
    # insert record using peewee api
    question.save(force_insert=True)
    result = {
        'result' : True,
        'content' : question.__dict__['_data']
    }
    return result

# main
if __name__ == "__main__":
    app.run(host=os.getenv("APP_ADDRESS", 'localhost'), \
    port=os.getenv("APP_PORT", 3000))

3.2.2 DB-Einstellung in Peewee

3.2.2.1 Entsprechende Datenbank und Verbindungspool

Mit peewee können Sie auf DBs wie SQL Lite, MySQL und PostgreSQL zugreifen. Weitere Informationen finden Sie unter http://docs.peewee-orm.com/de/latest/peewee/database.html.

Da Mikrodienste natürlich Web-Apps sind, denke ich, dass die Verbindungspoolfunktion wesentlich sein wird. Natürlich unterstützt Peewee auch Verbindungspools. Die Einstellungen sind für jede Datenbank unterschiedlich. Verwenden Sie im Fall von PostgreSQL die Klasse "PooledPostgresqlExtDatabase".

Bei Verwendung der Klasse "PooledPostgresqlExtDatabase" tritt zur Laufzeit ein Fehler auf, wenn "psycopg2" nicht installiert ist.

Obwohl dies nichts mit der Peewee-Funktion zu tun hat, wird empfohlen, die DB-Verbindungsinformationen (Host, Portnummer, Benutzer-ID, Kennwort usw.) von außerhalb des Programms festzulegen (Umgebungsvariable), da dies von der Umgebung abhängt.

3.2.2.2 HSTORE-Funktion von PostgreSQL

Bei Verwendung von PostgreSQL mit Peewee wird davon ausgegangen, dass standardmäßig die Funktion HSTORE von PostgreSQL verwendet wird. Wenn HSTORE in der zu verwendenden Datenbank nicht aktiviert ist, tritt daher ein Fehler beim DB-Zugriff von Peewee auf.

Aktivieren Sie als Gegenmaßnahme HSTORE mit "CREATE EXTENSION hstore" in der Datenbank. Alternativ müssen Sie "register_hstore = False" setzen, um zu verhindern, dass Peewee HSTORE verwendet.

3.2.2.3 Definition des Basismodells

Um die O / R-Zuordnungsfunktion von Peewee zu verwenden, definieren Sie eine Klasse, die das Model von Peewee erbt. Diesmal handelt es sich um eine Klasse namens BaseModel, aber diese Klasse muss die Meta-Klasse intern definieren und das oben erwähnte DB-Definitionsobjekt im Feld database festlegen.

3.2.3 Definition des Peewee-Modells

Die O / R-Zuordnungsfunktion von peewee ordnet Tabellen Klassen und Spalten Feldern zu. Dann wird das Modellobjekt von peewee einem Datensatz in der Tabelle zugeordnet.

Das Peewee-Modell ist als eine Klasse definiert, die das oben erwähnte "BaseModel" erbt. Das Feld legt den Peewee-Feldtyp entsprechend dem Datentyp der Tabelle fest.

Weitere Informationen finden Sie unter http://docs.peewee-orm.com/de/latest/peewee/models.html#fields.

Setzen Sie im Primärschlüsselfeld primary_key = True. Wenn Sie primary_key nicht festlegen, geht peewee davon aus, dass es eine Primärschlüsselspalte mit dem Namen id gibt. Wenn die Spalten-ID in der Tabelle nicht vorhanden ist, tritt daher natürlich ein Fehler auf.

Wenn der Feldname und der Spaltenname unterschiedlich sind, setzen Sie die Spalte explizit mit "db_column = Spaltenname".

3.2.4 Mapping nach Flasche anfordern

Um eine Flask-Anwendung zu erstellen, erstellen Sie zunächst eine Flask-Instanz mit dem Argument __name__. Verwenden Sie danach den Funktionsdekorator route (), um die HTTP-Anforderung mit der Funktion zu verknüpfen, indem Sie die Anforderungszuordnung nach flask in der definierten Funktion festlegen.

Für diejenigen, die Erfahrung mit dem Spring Framework (Spring MVC) in Java haben, ist es möglicherweise einfacher, sich die Annotation "@ RequestMapping" vorzustellen. (Flask hat keine Zuordnungsfunktion in Anforderungsparametern oder Headern)

@app.route('/questions/<string:question_code>', methods=['GET'])
def reference_question(question_code):

Weitere Informationen zur Anforderungszuordnung finden Sie unter http://flask.pocoo.org/docs/0.12/quickstart/#routing.

3.2.5 Fehlerbehandlung durch Kolben

falsk bietet einen errorhandler () Funktionsdekorator zum Einstellen der Fehlerbehandlung. Legen Sie den HTTP-Statuscode fest, der im Argument behandelt werden soll. Die Demo-App verarbeitet jetzt sowohl 404 als auch 500.

3.2.6 Eingabeprüfung durch cerberus

3.2.6.1 Definition des Regelschemas für die Eingabeprüfung

In cerberus werden Eingabekontrollregeln als Schemas bezeichnet. (Vielleicht bestimmt es die Datenstruktur, also denke ich, dass es ein Schema wie DB genannt wird)

Das Schema wird durch den Python-Wörterbuchtyp definiert. Ich kannte den Wörterbuchtyp von Python nicht und als ich ihn zum ersten Mal sah, dachte ich, ich würde ihn in JSON schreiben.

Beschreiben Sie den Datentyp (Typ), erforderlich / nicht erforderlich ( erforderlich) und die anzuwendenden Eingabekontrollregeln (maxlength, regex usw.) für jedes Feld. Ich denke, es ist einfach genug, um auf einen Blick einen Sinn zu ergeben.

Unter http://docs.python-cerberus.org/en/stable/validation-rules.html finden Sie die standardmäßig bereitgestellten Regeln für die Eingabeprüfung.

3.2.6.2 Durchführung der Eingabeprüfung

Die Eingabeprüfung durch cerberus erstellt zuerst eine Instanz von Validator gemäß dem Schema und verwendet diese Instanz, um eine Eingabeprüfung durchzuführen. (Es gibt auch eine Methode, die verwendet werden kann, ohne für jedes Schema eine Instanz zu erstellen, aber ich werde sie diesmal weglassen.)

In der Demo-App haben wir ein Schema mit dem Namen "question_schema" definiert und daher eine Validator-Instanz mit diesem Argument erstellt.

Das Ausführen der Eingabeprüfung ist einfach, und die "validate" -Methode wird mit den Wörterbuchtypdaten ausgeführt, die das Ziel der Eingabeprüfung als Argument sind. Als Rückgabewert wird "True" zurückgegeben, wenn bei der Eingabeprüfung kein Fehler vorliegt, und "False" wird zurückgegeben, wenn ein Fehler vorliegt.

Bei Verwendung in Zusammenarbeit mit flask kann auf die Anforderungsdaten im Wörterbuchtyp (JSON-Format) mit der Eigenschaft "request.json" zugegriffen werden. Legen Sie dies als Argument für die Methode "validate" fest. Da es sich um eine REST-Anwendung handelt, wird sie diesmal nicht verwendet, aber auf die Eingabeformulardaten kann mit "request.form" zugegriffen werden.

validate_pass = v.validate(request.json)
validate_pass = v.validate(request.form)

Wenn Sie die Instanz nur unabhängig von der Flasche überprüfen möchten, verwenden Sie __dict__, da die Methode validate nur Argumente vom Typ Wörterbuch annehmen kann.

question_ng = Question()
question_ng.question_code = "abc0123456789xyz"
question_ng.category = None
question_ng.message = ""

validate_pass = v.validate(question_ng.__dict__)

Standardmäßig führt cerberus zu einem Eingabeprüfungsfehler, wenn die Eingabedaten Felder enthalten, die nicht im Schema definiert sind. Um dieses Verhalten zu ändern, setzen Sie die Eigenschaft allow_unknown der Validator-Instanz auf True. (Allow_unknown = True)

Wie Sie unten unter "Informationen zu Fehler bei der Eingabeprüfung" sehen können, sind ** cerberus Validator-Instanzen statusbehaftete Instanzen, die ihren Status intern beibehalten. Nicht staatenlos. ** ** **

Aus diesem Grund erstellt die Demo-App für jede Anforderung eine Validator-Instanz. Es sollte erwogen werden, den Instanziierungsprozess und den thread-sicheren Validator zu rationalisieren (zu reduzieren).

3.2.6.3 Informationen zum Fehlerinhalt des Eingabeprüfungsfehlers

In cerberus wird der Fehlerinhalt des Eingabeprüfungsfehlers in der Eigenschaft error der Validator-Instanz gespeichert. Es wird in den Eigenschaften der Instanz gespeichert, daher wird es natürlich jedes Mal aktualisiert, wenn Sie es überprüfen. Jetzt wissen Sie, dass Sie eine Validator-Instanz nicht für mehrere Threads freigeben können (sie ist nicht threadsicher).

if not validate_pass:
    print v.errors
    abort(404)

Versuchen Sie, die Eingabedaten zu überprüfen, die den folgenden Fehler verursachen, um den Inhalt der Eigenschaft error zu überprüfen.

Geben Sie Daten ein, die einen Fehler verursachen


question = {
    'question_code' : '99999999990000000',
    'category' : '',
    'message' : 'hello'
}

Der Inhalt von Fehlern ist ein Wörterbuchtyp, bei dem das Feld, in dem der Fehler aufgetreten ist, der Schlüssel und das Array von Fehlermeldungen der Wert ist. Wenn daher mehrere Eingabeprüfungsfehler auf ein Feld angewendet werden, werden mehrere Fehlermeldungen gespeichert.

V angezeigt.Inhalt der Fehler


{u'category': ['empty values not allowed'], u'question_code': ['max length is 10']}

3.2.7 Transaktionskontrolle in Peewee

peewee bietet einen Funktionsdekorator für die Transaktionssteuerung. Sie können Transaktionsgrenzen festlegen, indem Sie der Funktion einfach einen atomar () - Dekorator hinzufügen. Es sieht einer deklarativen Transaktion mit "@ Transactional" von Spring Framework ähnlich, daher denke ich, dass es denjenigen bekannt sein wird, die Spring verwendet haben. (Der Mechanismus ist unterschiedlich, da es sich um die Spezifikation der Entwicklungssprache und des AOP handelt.)

Natürlich können Sie Transaktionen verschachteln und Start, Rollback und Commit explizit steuern. Weitere Informationen zur Transaktionssteuerung finden Sie unter http://docs.peewee-orm.com/de/latest/peewee/transactions.html.

3.2.8 In Peewee einsetzen

Es gibt zwei Möglichkeiten, Peewee einzufügen. Daher scheint es bei der Entwicklung mit mehreren Mitgliedern notwendig zu sein, die Methoden anzuordnen.

3.2.8.1 Registrierung und Instanziierung gleichzeitig aufzeichnen

Verwenden Sie die Methode create () der Modellklasse, um Datensätze zu registrieren und gleichzeitig Instanzen zu erstellen. Legen Sie die zum Einfügen erforderlichen Daten (alle Daten in der Spalte Nicht Null) als Argument für die Methode "create" fest.

question = Question.create(question_code = input.get("question_code"), \
    category = input.get("category"), \
    message = input.get("message"))

Die Demo-App verwendet diese Methode nicht, da sie anscheinend nicht zur O / R-Zuordnungsfunktion von Zuordnungsinstanzen und Datensätzen passt.

Weitere Informationen finden Sie unter http://docs.peewee-orm.com/de/latest/peewee/api.html#Model.create.

3.2.8.2 Datensätze können jederzeit nach dem Erstellen einer Instanz registriert werden

Registrieren Sie nach dem Erstellen der zu registrierenden Instanz den Datensatz, indem Sie jederzeit die Methode "save ()" der Instanz ausführen. Zu diesem Zeitpunkt besteht der Punkt darin, "True" auf die Eigenschaft "force_insert" des Arguments zu setzen. (Force_insert = True)

Ursprünglich dient die Methode "save ()" zum Aktualisieren (Ausgeben eines Updates), aber durch Setzen von "force_insert = True" wird ein Einfügen ausgegeben.

Weitere Informationen finden Sie unter http://docs.peewee-orm.com/de/latest/peewee/api.html#Model.save.

3.3 Demo-Client

3.3.1 Quellcode

democlient.py


# -*- coding: utf-8 -*-
import requests

# if proxy pass error occurred, try "set NO_PROXY=127.0.0.1;localhost" at console
def register_question():
    print("[POST] /questions")
    
    question = {
        'question_code' : '9999999999',
        'category' : 'demo',
        'message' : 'hello'
    }
    headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
    response = requests.post('http://localhost:3000/questions',
        json=question, headers=headers)
    print(response.status_code)
    print(response.content)

# main
if __name__ == "__main__":
    register_question()

3.3.2 Senden von HTTP-Anfragen über Anfragen

Anfragen haben Methoden, die HTTP-Methoden wie "post" und "get" entsprechen. Diesmal habe ich "post" verwendet, um die Anfrage per POST zu senden.

Wenn Sie Daten im Format "json" senden, setzen Sie die Daten im Argument "json" wie "json = question". Wenn Sie den HTTP-Header zum Zeitpunkt der Anforderung angeben möchten, legen Sie ihn im Argument "headers" fest.

Wenn das Argument "json" verwendet wird, wird "application / json" automatisch auf "content-type" gesetzt. Sie müssen es also nicht wirklich explizit wie den Demo-Quellcode einstellen. (Ich wollte wissen, wie man den HTTP-Header setzt, also habe ich ihn diesmal gesetzt)

3.4 Funktionsprüfung

3.4.1 Starten von Microservices

C:\tmp>python demoapp.py
 * Running on http://localhost:3000/ (Press CTRL+C to quit)

Beim Skalieren werden möglicherweise mehrere Prozesse auf demselben Computer ausgeführt. Zu diesem Zeitpunkt muss eine Änderung vorgenommen werden, damit der Abhörport nicht abgedeckt wird. Mit der Demo-App können Sie den Überwachungsport von einer Umgebungsvariablen überschreiben, sodass Sie ihn beim Start ändern können.

C:\tmp>set APP_PORT=3001
C:\tmp>python demoapp.py
 * Running on http://localhost:3001/ (Press CTRL+C to quit)

3.4.2 Starten des Demo-Clients

Führen Sie den Demo-Client ohne die relevanten Daten in der Datenbank aus. Ich denke, dass es normal ausgeführt wird und die registrierten Daten als JSON zurückgegeben werden.

C:\tmp>python democlient.py
[POST] /questions
200
{
  "content": {
    "category": "demo",
    "message": "hello",
    "question_code": "9999999999"
  },
  "result": true
}

Versuchen Sie als Nächstes, den Demo-Client in diesem Status erneut auszuführen. Natürlich erhalten Sie eine Fehlermeldung, da Sie in eine eindeutige Einschränkung geraten. Wenn der HTTP-Status von "{'error': 'ERROR'}" zurückgegeben wird, funktioniert auch die Fehlerbehandlung.

C:\tmp>python democlient.py
[POST] /questions
500
{
  "error": "ERROR"
}

Löschen Sie abschließend die relevanten Daten aus der Datenbank und versuchen Sie es erneut. Es sollte gut funktionieren, da der eindeutige Einschränkungsfehler nicht mehr auftritt.

C:\tmp>python democlient.py
[POST] /questions
200
{
  "content": {
    "category": "demo",
    "message": "hello",
    "question_code": "9999999999"
  },
  "result": true
}

4 Schließlich

Ich erklärte, wie man mit Python einen Mikrodienst mit Flask, Cerberus und Peewee erstellt. Ich glaube, ich konnte anhand der diesmal eingeführten Bibliothek vorstellen, wie einfach es ist, die Funktionen für die Verarbeitung von HTTP-Anforderungen, die Eingabeprüfung und den DB-Zugriff zu implementieren, die für eine Webanwendung mindestens erforderlich sind.

Wie ich zu Beginn der Konzept-App erklärt habe, berücksichtigen wir die Funktions- und Dateidivision nicht nach den Verantwortlichkeiten, die für die eigentliche Systementwicklung wichtig sind. Darüber hinaus müssen Zertifizierung, Autorisierung, Flusskontrolle, Überwachung, Protokollierung usw., die für die Offenlegung von Mikrodiensten nach außen unerlässlich sind, gesondert berücksichtigt werden.

5 Referenzinformationen

Markdown Notation Cheet Sheet Markdown-Schreibnotiz Schnelle Implementierung der REST-API in Python Versuchen Sie, mit der Datenbank mit Peewee aus Pythons ORM zu arbeiten

Recommended Posts

Mikrodienst mit Python (Übersicht)
Python: Vorverarbeitung beim maschinellen Lernen: Übersicht
Quadtree in Python --2
CURL in Python
Metaprogrammierung mit Python
Python 3.3 mit Anaconda
Geokodierung in Python
SendKeys in Python
Metaanalyse in Python
Unittest in Python
Zwietracht in Python
DCI in Python
Quicksort in Python
nCr in Python
N-Gramm in Python
Programmieren mit Python
Plink in Python
Konstante in Python
SQLite in Python
Schritt AIC in Python
LINE-Bot [0] in Python
CSV in Python
Reverse Assembler mit Python
Reflexion in Python
Konstante in Python
nCr in Python.
Format in Python
Scons in Python 3
Puyopuyo in Python
Python in Virtualenv
PPAP in Python
Quad-Tree in Python
Reflexion in Python
Chemie mit Python
Hashbar in Python
DirectLiNGAM in Python
LiNGAM in Python
In Python reduzieren
In Python flach drücken
Sortierte Liste in Python
Täglicher AtCoder # 36 mit Python
Clustertext in Python
AtCoder # 2 jeden Tag mit Python
Täglicher AtCoder # 32 in Python
Täglicher AtCoder # 6 in Python
Bearbeiten Sie Schriftarten in Python
Singleton-Muster in Python
Dateioperationen in Python
Lesen Sie DXF mit Python
Täglicher AtCoder # 53 in Python
Tastenanschlag in Python
Verwenden Sie config.ini mit Python
Täglicher AtCoder # 33 in Python
Löse ABC168D in Python
Logistische Verteilung in Python
Täglicher AtCoder # 7 in Python
Ein Liner in Python
Einfacher gRPC in Python