[PYTHON] Essayez de refactoriser tout en prenant des mesures

Je voulais essayer quelque chose comme ça une fois

supposition

Première édition

code

f.py


def write(lines):
    print 'write:', lines

csv.py


# -*- coding: utf-8 -*-

import f


def as_kvs(rows):
    keys = rows[0]
    value_rows = rows[1:]

    return [dict(zip(keys, value_row)) for value_row in value_rows]


def write_first_one_or_empty(rows, sort_key, filter_key, filter_value, write_key):
    #Faire plusieurs kv
    kvs = as_kvs(rows)

    #Trier par clé spécifiée
    _sorted = sorted(kvs, key=lambda row: row[sort_key])

    #Filtre uniquement là où la clé spécifiée a la valeur spécifiée
    _filtered = filter(lambda row: row[filter_key] == filter_value, _sorted)

    if 0 < len(_filtered):
        #Extraire la première ligne avec la clé spécifiée
        _mapped_one = map(lambda row: row[write_key], _filtered)[0]
        #Écrire
        f.write([_mapped_one])
    else:
        #Créer un fichier vide
        f.write([])


def write_all_or_empty(rows, filter_key, filter_value, write_key):
    #Faire plusieurs kv
    kvs = as_kvs(rows)

    #Filtre uniquement là où la clé spécifiée a la valeur spécifiée
    _filtered = filter(lambda row: row[filter_key] == filter_value, kvs)

    if _filtered:
        #Extraire toutes les lignes avec la clé spécifiée
        _mapped = map(lambda row: row[write_key], _filtered)
        #Écrire
        f.write(_mapped)
    else:
        #Créer un fichier vide
        f.write([])


def write_all_or_error(rows, filter_key, filter_value, write_key):
    #Faire plusieurs kv
    kvs = as_kvs(rows)

    #Filtre uniquement là où la clé spécifiée a la valeur spécifiée
    _filtered = filter(lambda row: row[filter_key] == filter_value, kvs)

    if _filtered:
        #Extraire toutes les lignes avec la clé spécifiée
        _mapped = map(lambda row: row[write_key], _filtered)
        #Écrire
        f.write(_mapped)
    else:
        #Erreur
        raise Exception("no result")

Votre interlocuteur

main.py


# status,Étant donné un csv composé de code
#Trier par code
#Le premier cas où le statut est actif
#Écrire le code
#Sinon, créez simplement un fichier

csv.write_first_one_or_empty(
    [['status', 'code'], ['dead', '001'], ['active', '003'], ['active', '002']],
    'code', 'status', 'active', 'code'
)

#résultat
# write: ['002']

main.py


# name,Étant donné un csv composé de genre
#Tous les cas où le sexe est masculin
#Écrivez le nom
#Sinon, créez simplement un fichier

csv.write_all_or_empty(
    [['name', 'gender'], ['Eva', 'female'], ['Sunny', 'female']],
    'gender', 'male', 'name'
)

#résultat
# write: []

main.py


# status,Donner csv composé de tel
#Tous les cas où le statut est mort
#Exporter tel
#Sinon une erreur

csv.write_all_or_error(
    [['status', 'tel'], ['dead', '090-1111-1111'], ['active', '090-9999-9999']],
    'status', 'dead', 'tel'
)

#résultat
# write: ['090-1111-1111']

problème

step-1 J'écrirai un commentaire en anglais pour le moment

Différence

 def write_first_one_or_empty(rows, sort_key, filter_key, filter_value, write_key):
-    #Faire plusieurs kv
+    # as kvs
     kvs = as_kvs(rows)

-    #Trier par clé spécifiée
+    # sort by specified key
     _sorted = sorted(kvs, key=lambda row: row[sort_key])

-    #Filtre uniquement là où la clé spécifiée a la valeur spécifiée
+    # filter by specified key and value
     _filtered = filter(lambda row: row[filter_key] == filter_value, _sorted)

     if 0 < len(_filtered):
-        #Extraire la première ligne avec la clé spécifiée
+        # extract by specified key and first one
         _mapped_one = map(lambda row: row[write_key], _filtered)[0]
-        #Écrire
+        # write
         f.write([_mapped_one])
     else:
-        #Créer un fichier vide
+        # write empty
         f.write([])
 def write_all_or_empty(rows, filter_key, filter_value, write_key):
-    #Faire plusieurs kv
+    # as kvs
     kvs = as_kvs(rows)

-    #Filtre uniquement là où la clé spécifiée a la valeur spécifiée
+    # filter by specified key and value
     _filtered = filter(lambda row: row[filter_key] == filter_value, kvs)

     if _filtered:
-        #Extraire toutes les lignes avec la clé spécifiée
+        # extract by specified key
         _mapped = map(lambda row: row[write_key], _filtered)
-        #Écrire
+        # write
         f.write(_mapped)
     else:
-        #Créer un fichier vide
+        # write empty
         f.write([])
 def write_all_or_error(rows, filter_key, filter_value, write_key):
-    #Faire plusieurs kv
+    # as kvs
     kvs = as_kvs(rows)

-    #Filtre uniquement là où la clé spécifiée a la valeur spécifiée
+    # filter by specified key and value
     _filtered = filter(lambda row: row[filter_key] == filter_value, kvs)

     if _filtered:
-        #Extraire toutes les lignes avec la clé spécifiée
+        # extract by specified key
         _mapped = map(lambda row: row[write_key], _filtered)
-        #Écrire
+        # write
         f.write(_mapped)
     else:
-        #Erreur
+        # error
         raise Exception("no result")

Problème suivant

étape-2 Organisez les commentaires et découpez les méthodes en fonction des commentaires en anglais

politique

Différence

La méthode qui a été découpée est comme ça

+def sort_by_specified_key(kvs, key):
+    return sorted(kvs, key=lambda row: row[key])
+def filter_by_specified_key_and_value(kvs, key, value):
+    return filter(lambda row: row[key] == value, kvs)
+def extract_by_specified_key_and_first_one(kvs, key):
+    return kvs[0][key]

+def extract_by_specified_key(kvs, key):
+    return map(lambda row: row[key], kvs)
+def error():
+    raise Exception("no result")

Le corps principal est comme ça

-def write_first_one_or_empty(rows, sort_key, filter_key, filter_value, write_key):
+def write_first_one_or_empty(rows, sort_key, filter_key, filter_value, extraction_key):
-    # as kvs
     kvs = as_kvs(rows)

-    # sort by specified key
-    _sorted = sorted(kvs, key=lambda row: row[sort_key])
+    _sorted = sort_by_specified_key(kvs, sort_key)

-    # filter by specified key and value
-    _filtered = filter(lambda row: row[filter_key] == filter_value, _sorted)
+    _filtered = filter_by_specified_key_and_value(_sorted, filter_key, filter_value)

     if 0 < len(_filtered):
-        # extract by specified key and first one
-        _mapped_one = map(lambda row: row[write_key], _filtered)[0]
-        # write
-        f.write([_mapped_one])
+        extracted_one = extract_by_specified_key_and_first_one(_filtered, extraction_key)
+        f.write([extracted_one])
     else:
-        # write empty
         f.write([])
-def write_all_or_empty(rows, filter_key, filter_value, write_key):
+def write_all_or_empty(rows, filter_key, filter_value, extraction_key):
-    # as kvs
     kvs = as_kvs(rows)

-    # filter by specified key and value
-    _filtered = filter(lambda row: row[filter_key] == filter_value, kvs)
+    _filtered = filter_by_specified_key_and_value(kvs, filter_key, filter_value)

     if _filtered:
-        # extract by specified key
-        _mapped = map(lambda row: row[write_key], _filtered)
-        # write
-        f.write(_mapped)
+        extracted = extract_by_specified_key(_filtered, extraction_key)
+        f.write(extracted)
     else:
-        # write empty
         f.write([])
-def write_all_or_error(rows, filter_key, filter_value, write_key):
+def write_all_or_error(rows, filter_key, filter_value, extraction_key):
-    # as kvs
     kvs = as_kvs(rows)

-    # filter by specified key and value
-    _filtered = filter(lambda row: row[filter_key] == filter_value, kvs)
+    _filtered = filter_by_specified_key_and_value(kvs, filter_key, filter_value)

     if _filtered:
-        # extract by specified key
-        _mapped = map(lambda row: row[write_key], _filtered)
-        # write
-        f.write(_mapped)
+        extracted = extract_by_specified_key(_filtered, extraction_key)
+        f.write(extracted)
     else:
-        # error
-        raise Exception("no result")
+        error()

Problème suivant

étape-3 Faites un peu de refactoring local

Différence

J'ai préparé head et tail car l'accès à l'index est un peu hostile.

+def head(xs):
+    return xs[0]
+def tail(xs):
+    return xs[1:]

Où utiliser

 def as_kvs(rows):
-    keys = rows[0]
-    value_rows = rows[1:]
+    keys = head(rows)
+    value_rows = tail(rows)
 def extract_by_specified_key_and_first_one(kvs, key):
-    return kvs[0][key]
+    return head(kvs)[key]

Changement de filter_by_specified_key_and_value pour recevoir predicate au lieu de key, value pour être un peu plus flexible

-def filter_by_specified_key_and_value(kvs, key, value):
-    return filter(lambda row: row[key] == value, kvs)
+def filter_by_predicate(kvs, predicate):
+    return filter(predicate, kvs)

Ensuite, filter_by_predicate ne fait rien de plus que filter et le rejette.

-def filter_by_predicate(kvs, predicate):
-    return filter(predicate, kvs)

Ensuite, j'ai créé head, donc je n'aurais peut-être pas à préparer ʻextract_by_specified_key_and_first_one`. Si les noms des méthodes divisées en petites parties sont corrects, il est difficile que le traitement ne devienne pas clair même s'ils sont utilisés en combinaison.

-def extract_by_specified_key_and_first_one(kvs, key):
-    return head(kvs)[key]

Voici le corps principal qui reflète ce qui précède

-def write_first_one_or_empty(rows, sort_key, filter_key, filter_value, extraction_key):
+def write_first_one_or_empty(rows, sort_key, predicate, extraction_key):
     kvs = as_kvs(rows)

     _sorted = sort_by_specified_key(kvs, sort_key)

-    _filtered = filter_by_specified_key_and_value(_sorted, filter_key, filter_value)
+    _filtered = filter(predicate, _sorted)

     if 0 < len(_filtered):
-        extracted_one = extract_by_specified_key_and_first_one(_filtered, extraction_key)
+        extracted_one = head(_filtered)[extraction_key]
         f.write([extracted_one])
     else:
         f.write([])
-def write_all_or_empty(rows, filter_key, filter_value, extraction_key):
+def write_all_or_empty(rows, predicate, extraction_key):
     kvs = as_kvs(rows)

-    _filtered = filter_by_specified_key_and_value(kvs, filter_key, filter_value)
+    _filtered = filter(predicate, kvs)

     if _filtered:
         extracted = extract_by_specified_key(_filtered, extraction_key)
         f.write(extracted)
     else:
         f.write([])
-def write_all_or_error(rows, filter_key, filter_value, extraction_key):
+def write_all_or_error(rows, predicate, extraction_key):
     kvs = as_kvs(rows)

-    _filtered = filter_by_specified_key_and_value(kvs, filter_key, filter_value)
+    _filtered = filter(predicate, kvs)

     if _filtered:
         extracted = extract_by_specified_key(_filtered, extraction_key)
         f.write(extracted)
     else:
         error()

Problème suivant

étape-4 Rendre les parties communes du code qui se ressemblent (mauvaise stratégie)

politique

Différence

+def filter_and_extract_and_write_if_not_empty(kvs, predicate, extraction_key):
+    _filtered = filter(predicate, kvs)
+
+    if _filtered:
+        extracted = extract_by_specified_key(_filtered, extraction_key)
+        f.write(extracted)

Depuis qu'il a été coupé, le nombre de lignes dans le corps principal a diminué

 def write_all_or_empty(rows, predicate, extraction_key):
     kvs = as_kvs(rows)

-    _filtered = filter(predicate, kvs)
+    filter_and_extract_and_write_if_not_empty(kvs, predicate, extraction_key)

-    if _filtered:
-        extracted = extract_by_specified_key(_filtered, extraction_key)
-        f.write(extracted)
-    else:
+    if not kvs:
         f.write([])
 def write_all_or_error(rows, predicate, extraction_key):
     kvs = as_kvs(rows)

-    _filtered = filter(predicate, kvs)
+    filter_and_extract_and_write_if_not_empty(kvs, predicate, extraction_key)

-    if _filtered:
-        extracted = extract_by_specified_key(_filtered, extraction_key)
-        f.write(extracted)
-    else:
+    if not kvs:
         error()

Problèmes avec cette politique

Ce qui était faux

étape-4 Organiser le flux de traitement (bonne politique)

La mauvaise politique mentionnée ci-dessus est réduite et refaite

politique

Différence

Faites attention à la deuxième méthode corporelle

Cette fois, il n'est pas nécessaire de séparer explicitement le traitement pour le cas avec le contenu de la liste et le cas avec la liste vide Fondamentalement, si le traitement auquel la liste est passée est le même, il n'est pas nécessaire de connaître la longueur de la liste (cela ne devrait pas être)

Il vaut mieux passer ceci à f.write sans savoir s'il s'agit d'un contenu ou d'une liste vide

 def write_all_or_empty(rows, predicate, extraction_key):
     kvs = as_kvs(rows)

     _filtered = filter(predicate, kvs)

-    if _filtered:
-        extracted = extract_by_specified_key(_filtered, extraction_key)
-        f.write(extracted)
-    else:
-        f.write([])
+    extracted = extract_by_specified_key(_filtered, extraction_key)
+    f.write(extracted)

Ensuite, faites attention à la première méthode corporelle.

S'il ne s'agit pas d'une liste vide, le premier élément est retiré, traité et de nouveau répertorié.

cette

Le flux

Je changerai l'idée en flux

Tout d'abord, préparez une méthode pour obtenir le premier élément correspondant de la liste avec une longueur maximale de 1.

+def find_first(kvs, predicate):
+    for kv in kvs:
+        if predicate(kv):
+            return [kv]
+    else:
+        return []

Ensuite, vous n'avez pas à vous soucier du contenu comme le deuxième de la méthode du corps principal. De plus, comme il s'agit d'une conversion de liste, vous pouvez utiliser ʻextract_by_specified_key au lieu de headet d'un accèskey`.

 def write_first_one_or_empty(rows, sort_key, predicate, extraction_key):
     kvs = as_kvs(rows)

     _sorted = sort_by_specified_key(kvs, sort_key)
+    first = find_first(_sorted, predicate)

-    _filtered = filter(predicate, _sorted)
-
-    if 0 < len(_filtered):
-        extracted_one = head(extract_by_specified_key(_filtered, extraction_key))
-        f.write([extracted_one])
-    else:
-        f.write([])
+    extracted = extract_by_specified_key(first, extraction_key)
+    f.write(extracted)

La clause ʻif et la clause ʻelse des méthodes du corps principal 1 et 2 s'inscrivent dans la granularité du "processus d'écriture sans tenir compte de la longueur de la liste" Dans un mauvais exemple, ʻif et ʻelse étaient un groupe de processus d'exportation, mais c'était une mauvaise idée de ne supprimer que ʻif`.

Problème suivant

étape-5 Organisez les méthodes de découpe

politique

Différence

l.py


+# -*- coding: utf-8 -*-
+
+
+def head(xs):
+    return xs[0]
+
+
+def tail(xs):
+    return xs[1:]
+
+
+def find_first(xs, predicate):
+    for x in xs:
+        if predicate(x):
+            return [x]
+    else:
+        return []

d.py


+# -*- coding: utf-8 -*-
+
+
+def extract_by_specified_key(xs, key):
+    return map(lambda x: x[key], xs)
+
+
+def sort_by_specified_key(xs, key):
+    return sorted(xs, key=lambda x: x[key])
-def as_kvs(rows):
+def __as_kvs(rows):
-def error():
+def __error():

Pourquoi ne pas supprimer les «as_kvs» apparemment pratiques

kvs est juste une structure de données temporaire préparée en convertissant la structure de données attendue par csv.py uniquement pour faciliter le traitement en interne.

kvs est en fait dict, mais le type dict n'apparaît pas dans les arguments publiés ou la valeur de retour de csv.py. En d'autres termes, le type dict n'existe nulle part en tant que fonction fournie par le module, il ne doit donc pas être exposé.

De plus, le type d'argument de ʻas_kvs est list, mais en raison de restrictions telles que la configuration de header + [body]et du fait que toutes les colonnes correspondent,l.py et J'ai pensé qu'il était préférable de faire du traitement privé csv.py plutôt que d.py`

Problème suivant

step-6 Refactoriser la méthode privée qui n'est utilisée qu'une seule fois (cela peut être pour et contre)

Quand il s'agit de modules légèrement plus grands, il peut être extrêmement difficile de savoir où et combien de modules privés sont utilisés.

Si vous le faites, il peut être difficile de refactoriser la méthode «privée» ou de comprendre la gamme d'influence, ce qui peut être assez difficile.

Il existe de nombreux "privés", mais chacun n'est utilisé qu'une seule fois.

public 1 -> private 1 -> private 2 -> private 3
public a -> private a -> private b
public x -> private x

Il est beaucoup plus facile de saisir le tableau d'ensemble si vous pensez qu'il y a en fait trois «public». Vous pouvez refactoriser «privé» en toute tranquillité.

Un exemple d'un mélange de «privé», qui n'est utilisé qu'une seule fois, et de «privé», qui dépend de diverses parties.

public 1 -> private 1
            |
            V
public a -> private a -> private b
                         ^
                         |
public x -> private x -> private y

«private 1» et «private x» peuvent être modifiés dans une certaine mesure facilement, mais si vous corrigez légèrement «private b», cela affectera tout «public».

politique

Essayez de définir private dans la méthode qui n'est utilisée qu'à partir d'un seul endroit

C'est la méthode que j'ai essayée sans permission quand j'étais là-bas, mais il n'y a pas d'exposition ou de discussion proprement dite, mais je pense que c'est une bonne idée et je l'utilise beaucoup dans les cordons jetables et les projets solo

Différence

Puisque __error n'est utilisé qu'à un seul endroit, je l'ai préparé avec def dans def

Au fait, j'ai supprimé l'ennuyeux __ parce qu'il devenait de toute façon invisible de l'extérieur.

 def write_all_or_error(rows, predicate, extraction_key):
+    def write_or_error(kvs):
+        if kvs:
+            f.write(kvs)
+        else:
+            raise Exception("no result")
+
     kvs = __as_kvs(rows)

     _filtered = filter(predicate, kvs)

     extracted = d.extract_by_specified_key(_filtered, extraction_key)
-    if extracted:
-        f.write(extracted)
-    else:
-        __error()
+    write_or_error(extracted)

Bien sûr, __as_kvs est utilisé à de nombreux endroits, alors ne le changez pas.

Dernier problème

étape 7 Ecrivez le test séparément pour la conversion et l'exportation

politique

La responsabilité du module se limite donc à la "conversion" et le test est mis en œuvre.

Différence

Il est décidé de finir par «return» sans «sortie» Parallèlement à cela, le nom de la méthode a été changé de write_ à ʻextract_`.

-def write_first_one_or_empty(rows, sort_key, predicate, extraction_key):
+def extract_first_one_or_empty(rows, sort_key, predicate, extraction_key):
     kvs = __as_kvs(rows)

     _sorted = d.sort_by_specified_key(kvs, sort_key)
     first = l.find_first(_sorted, predicate)

-    extracted = d.extract_by_specified_key(first, extraction_key)
-    f.write(extracted)
+    return d.extract_by_specified_key(first, extraction_key)
-def write_all_or_empty(rows, predicate, extraction_key):
+def extract_all_or_empty(rows, predicate, extraction_key):
     kvs = __as_kvs(rows)

     _filtered = filter(predicate, kvs)

-    extracted = d.extract_by_specified_key(_filtered, extraction_key)
-    f.write(extracted)
+    return d.extract_by_specified_key(_filtered, extraction_key)
-def write_all_or_error(rows, predicate, extraction_key):
-    def write_or_error(kvs):
-        if kvs:
-            f.write(kvs)
+def extract_all_or_error(rows, predicate, extraction_key):
+    def it_or_error(xs):
+        if xs:
+            return xs
         else:
             raise Exception("no result")

     kvs = __as_kvs(rows)

     _filtered = filter(predicate, kvs)

     extracted = d.extract_by_specified_key(_filtered, extraction_key)
-    write_or_error(extracted)
+    return it_or_error(extracted)

En conséquence, ʻimport disparaît, il peut donc être confirmé que csv.py` ne traite plus file-io.

-from private import f

Le test ressemble à ceci

csv_test.py


# -*- coding: utf-8 -*-

import csv

assert csv.extract_first_one_or_empty(
    [['status', 'code'], ['dead', '001'], ['active', '003'], ['active', '002']],
    'code', lambda kvs: kvs['status'] == 'active', 'code'
) == ['002']

assert csv.extract_first_one_or_empty(
    [['status', 'code'], ['dead', '001']],
    'code', lambda kvs: kvs['status'] == 'active', 'code'
) == []

La fin

Formulaire rempli

csv.py


# -*- coding: utf-8 -*-

import l
import d


def __as_kvs(rows):
    keys = l.head(rows)
    value_rows = l.tail(rows)

    return [dict(zip(keys, value_row)) for value_row in value_rows]


def extract_first_one_or_empty(rows, sort_key, predicate, extraction_key):
    kvs = __as_kvs(rows)

    _sorted = d.sort_by_specified_key(kvs, sort_key)
    first = l.find_first(_sorted, predicate)

    return d.extract_by_specified_key(first, extraction_key)


def extract_all_or_empty(rows, predicate, extraction_key):
    kvs = __as_kvs(rows)

    _filtered = filter(predicate, kvs)

    return d.extract_by_specified_key(_filtered, extraction_key)


def extract_all_or_error(rows, predicate, extraction_key):
    def it_or_error(xs):
        if xs:
            return xs
        else:
            raise Exception("no result")

    kvs = __as_kvs(rows)

    _filtered = filter(predicate, kvs)

    extracted = d.extract_by_specified_key(_filtered, extraction_key)
    return it_or_error(extracted)

Par rapport à la première édition

Contraction

Le point

Je ne peux pas le dire en un mot ...

N'écrivez pas de commentaires sans valeur

Alors quel est le commentaire à écrire au contraire?

Même ainsi, si tu penses que c'est douloureux sans commentaires

Mauvaise méthode private

Bonne méthode privée

Différences dans la gestion des méthodes «privées» et «publiques»

Que tester

Ne pas standardiser car le code est similaire

def in def

scala peut être obéissant

def upperJoin(xs: List[String], sep: String): String = {
    def upper(xs: List[String]): List[String] = xs.map(_.toUpperCase)
    def join(xs: List[String]): String = xs.mkString(sep)

    join(upper(xs))
}

upperJoin(List("hello", "world"), " ") // HELLO WORLD

Même avec java, il est possible d'utiliser Function <T, R> etc.

public static String upperJoin(List<String> xs, String sep) {
    Function<List<String>, List<String>> upper = _xs -> _xs.stream().map(String::toUpperCase).collect(toList());
    Function<List<String>, String> join = _xs -> _xs.stream().collect(joining(sep));

    return join.apply(upper.apply(xs));
}

upperJoin(asList("hello", "world"), " "); // HELLO WORLD

haskell peut aussi être Ou plutôt, l'idée vient du sens de définir une fonction haskell par valeur.

upperJoin xs sep = (join . upper) xs
  where
    upper = map (map toUpper)
    join = intercalate sep

upperJoin ["hello", "world"] " " -- HELLO WORLD

Ainsi, l'exemple java est proche de haskell, et vous pouvez l'écrire en python comme ceci: (Dans PEP 8, c'est une violation de la norme de codage de nommer et de lier lambda, mais je déteste l'imbrication, donc il y a plus de gens ici si je suis seul)

def upper_join(xs, sep):
    upper = lambda xs: [x.upper() for x in xs]
    join = lambda xs: sep.join(xs)

    return join(upper(xs))

upper_join(['hello', 'world'], ' ') # HELLO WORLD

Réflexion

La fin

Je voulais juste l'essayer, donc j'étais satisfait

Cela n'a pas d'importance, mais quand je vois les lettres noires sur les zones rouges et vertes, je pense toujours que cela ressemble à une pastèque.

Recommended Posts

Essayez de refactoriser tout en prenant des mesures
Étape par étape pour créer un Dockerfile
Essayer d'implémenter et de comprendre les arborescences de segments étape par étape (python)
Approximation de bas rang des images par HOSVD étape par étape
Essayez de faire face à la somme partielle
Essayez de classer les livres d'O'Reilly en les regroupant