Essayez d'écrire du code python pour générer du code go - Essayez de porter JSON-to-Go et ainsi de suite

Essayez d'écrire du code python pour générer du code go - Essayez de porter JSON-to-Go et ainsi de suite

Cet article est l'article du 7ème jour du Calendrier de l'Avent WACUL 2016. Je travaille chez WACUL depuis septembre de cette année, écrivant principalement du code go et parfois python sur le backend.

Au fait, cela a rallongé pendant que j'écrivais ceci et cela, donc si vous voulez juste savoir ce que vous avez fait [Introduction](http://qiita.com/podhmo/items/a12952b07648e42911b4#%E3%81%AF%E3 Après avoir lu% 81% 98% E3% 82% 81% E3% 81% AB), [Enfin le sujet principal](http://qiita.com/podhmo/items/a12952b07648e42911b4#%E3%82%88%E3 Veuillez passer à environ% 81% 86% E3% 82% 84% E3% 81% 8F% E6% 9C% AC% E9% A1% 8C).

introduction

Récemment, j'écris du code go tous les jours dans mon entreprise. Parfois, je voulais écrire du code go en python. Vous pouvez penser que c'est étrange. Mais attendez s'il vous plait. Pour le moment, c'est le résultat de suivre le flux de pensée suivant.

  1. Vérifiez s'il y a quelque chose que vous pouvez faire dans les fonctionnalités normales du langage go
  2. Voyez si vous pouvez utiliser les fonctionnalités légèrement dangereuses du langage go
  3. Vérifiez si vous pouvez faire quelque chose avec la gamme d'outils go
  4. Générer du code go dans une autre langue (à laquelle je suis habitué)

Bien sûr, il est certainement préférable de suivre les étapes ci-dessus autant que possible.

La différence entre * 1. * et * 2. * est subjective et peut être difficile à comprendre.

Quant à * 3. *, je me sens chanceux. Sinon, il peut être bon de le faire avec go.

Cela dit, je pense qu'il y a des moments où c'est une quête axée sur les intérêts, ou quand vous faites un peu de travail dans une langue familière. Il peut être plus rapide de créer rapidement quelque chose qui se comporte selon vos goûts personnels, plutôt que d'essayer de créer quelque chose à usage général. Personnellement, j'étais familier avec python, j'ai donc choisi python.

Génération de code ordinaire

Lorsque vous pensez à la génération de code go, vous pensez généralement aux deux choses suivantes.

--Générer du code incorporé en utilisant text / template etc. --Générer du code en traçant AST et en le façonnant dans la forme requise

Cette fois, c'est une autre histoire.

Écrivons du code python pour générer du code go

prestring?

En fait, j'utilise ma propre bibliothèque pour des travaux tels que la génération de code. C'est une bibliothèque appelée prestring. Je ne pense pas que quiconque le sache, alors je vais vous expliquer un peu cette bibliothèque.

Cette bibliothèque n'est pas une bibliothèque (transpile) qui génère du code go à partir de python ou d'un DSL, mais en réalité juste une bibliothèque pour écrire directement le code d'un langage spécifique Y (ici) sur python. C'est la même chose que d'utiliser un moteur de modèle pour la génération automatique de code. Le sentiment d'utiliser la fonction de langage complet peut être différent.

En tant que fonctionnalité, j'ai mis l'accent sur la zone qui facilite la gestion des retraits. L'idée originale elle-mêmeLangue D pour la victoire|la programmation| POSTDApparaîtdanslesarticles,etc.srcgenLa bibliothèque est dérivée de.

Un objet qui deviendra finalement une chaîne

Le prestring est fourni par une classe appelée «Module» pour chaque module. Le code est généré lorsque le résultat de l'exécution de diverses opérations sur l'objet de cette classe est sorti. Au fait, bien que ce soit le nom de prestring, je me souviens que cela signifiait l'état avant la chaîne de caractères.

m = Module()
m.stmt("hai")
print(m)  # => "hai\n"

Code qui génère du code qui produit Hello World

Écrivons bonjour le monde comme un simple code. bonjour le monde ressemble à ceci:

from prestring.go import Module

m = Module()
m.package("main")

with m.import_group() as im:
    im.import_("fmt")

with m.func("main"):
    m.stmt("fmt.Println(`hello world`)")

print(m)

Pour l'utiliser, utilisez la syntaxe with lorsque vous souhaitez une indentation. Vous pouvez l'écrire avec le sentiment d'utiliser une méthode avec un nom similaire au mot réservé de chaque langue. Une fois que vous vous y serez habitué, vous pourrez voir la langue de sortie telle qu'elle est. Probablement. sûrement. peut être.

En fait, le code ci-dessus génère un code comme celui-ci: Même si la sortie est un peu maladroite, c'est pratique car gofmt la formatera.

package main

import (
	"fmt"
)

func main() {
	fmt.Println(`hello world`)
}

Code qui génère du code qui calcule le produit direct

Générons automatiquement un code un peu plus compliqué. Je voudrais écrire un code qui calcule le produit direct d'une liste et d'une liste. Par exemple, le produit direct des deux listes xs et ys est le suivant.

xs = [1, 2, 3]
ys = ["a", "b", "c"]
[(x, y) for x in xs for y in ys]
# => [(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c'), (3, 'a'), (3, 'b'), (3, 'c')]

De même, considérons deux cas, trois cas, ... N cas, et ainsi de suite. Normalement, vous écririez du code un nombre illimité de fois en utilisant récursif, etc. Cette fois, je voudrais écrire un code qui génère le code pour chaque cas.

Code pour calculer le produit direct lorsque N = 2 Code pour calculer

Tout d'abord, écrivons le code qui génère directement le code go pour les deux cas. Cela ressemblera à ce qui suit.

from prestring.go import GoModule  #Identique au module


# cross product
def cross2(m):
    with m.func("cross2", "vs0 []string", "vs1 []string", return_="[][]string"):
        m.stmt("var r [][]string")
        with m.for_("_,  v0 := range vs0"):
            with m.for_("_,  v1 := range vs1"):
                m.stmt("r = append(r, []string{v0, v1})")
        m.return_("r")
    return m

m = GoModule()
m.package("main")
print(cross2(m))

Si avec est attaché, vous pouvez le lire d'une manière ou d'une autre si vous gardez à l'esprit qu'il s'agit d'un retrait. Le résultat de sortie est le suivant.

func cross2(vs0 []string, vs1 []string) [][]string {
        var r [][]string
        for _, v0 := range vs0 {
                for _, v1 := range vs1 {
                        r = append(r, []string{v0, v1})
                }
        }
        return r
}

Code pour calculer le produit direct lorsque N = 5 Code pour calculer

Considérons maintenant le cas où le nombre de listes passées est 3, 4, ... et n'importe quel nombre de listes passées. Il peut être un peu ennuyeux de voir combien de nids de boucles changent en fonction du nombre de listes passées. Dans un tel cas, vous ne pouvez pas l'écrire directement tel quel, mais si vous l'écrivez de manière récursive, vous pouvez générer du code en boucle imbriqué à tout moment tout en conservant la structure d'indentation.

def crossN(m, n):
    def rec(m, i, value):
        if i >= n:
            m.stmt("r = append(r, []string{{{value}}})".format(value=", ".join(value)))
        else:
            v = "v{}".format(i)
            vs = "vs{}".format(i)
            with m.for_("_, {} := range {}".format(v, vs)):
                value.append(v)
                rec(m, i + 1, value)

    args = ["vs{} []string".format(i) for i in range(n)]
    with m.func("cross{}".format(n), *args, return_="[][]string"):
        m.stmt("var r [][]string")
        rec(m, 0, [])
        m.return_("r")
    return m

C'est un peu difficile à lire. Étant donné que l'intérieur avec est écrit avant de quitter l'extérieur avec et que l'appel est imbriqué, la structure est la suivante, veuillez donc vous y habituer.

boucle de scène avec v0
boucle de scène avec v1
boucle avec scène v2
...
boucle avec étape vN

Si vous essayez de sortir le résultat de crossN (m, 5) lorsque N = 5, ce sera comme suit.

package main

func cross5(vs0 []string, vs1 []string, vs2 []string, vs3 []string, vs4 []string) [][]string {
	var r [][]string
	for _, v0 := range vs0 {
		for _, v1 := range vs1 {
			for _, v2 := range vs2 {
				for _, v3 := range vs3 {
					for _, v4 := range vs4 {
						r = append(r, []string{v0, v1, v2, v3, v4})
					}
				}
			}
		}
	}
	return r
}

Submodule () pour Module ()

Il y avait une autre particularité. C'est une fonction appelée sous-module. Cette fonction est une fonction que vous pouvez ajouter un marqueur à une position spécifique dans une certaine sortie et incorporer l'expression de chaîne de caractères que vous souhaitez insérer à cette position plus tard. Cela ressemble à ce qui suit.

m = Module()

m.stmt("begin foo")
sm = m.submodule()
m.stmt("end foo")

m.stmt("bar")

sm.sep()
sm.stmt("** yay! **")
sm.sep()

print(m)

Créez un sous-module appelé sm à la position entourée par foo de m. J'intégrerai un saut de ligne et une formulation plus tard. Le résultat de sortie est le suivant.

begin foo

** yay! **

end foo
bar

Cette fonction est utilisée dans la pièce d'importation. Ainsi, vous pouvez écrire l'importation du package dont vous aurez besoin plus tard.

from prestring.go import Module

m = Module()
m.package('main')
with m.import_group() as im:
    pass

with m.func('main'):
    im.import_('log')
    m.stmt('log.Println("hmm")')

print(m)

"Journal" est importé pour la première fois dans la fonction principale. En regardant le résultat de sortie, il est inséré à la position spécifiée par ʻimport_group () `.

package main

import (
	"log"
)

func main()  {
	log.Println("hmm")
}

Cependant, c'est une bonne idée d'utiliser goimports au lieu de gofmt pour insérer automatiquement la partie d'importation, de sorte qu'elle ne soit pas beaucoup utilisée en cours de route.

Le code génère un abus de code

Générer du code Maintenant que j'ai la capacité de générer du code, j'ai parfois l'impression que j'allais tout faire avec la génération de code. En conclusion, je ne le recommande pas beaucoup. Il est préférable de faire ce que vous pouvez calculer au moment de l'exécution (ne pas utiliser la réflexion).

Par exemple, fizzbuzz écrit par * personne sérieuse * Je ne suis pas si content de le faire. Parce que je peux le faire La distinction entre le bien et le mal est basée sur ma propre paresse. Veuillez juger.

Personnellement, je pense que les critères suivants sont bons.

―― Avant de commencer la génération de code, demandez-vous si vous en avez vraiment besoin.

Le dernier est une version légèrement générique du modèle d'écart de génération (que j'ai appris récemment).

De plus, essayer de compenser les fonctionnalités de langue manquantes échouera probablement. En prenant les génériques comme exemple, il est possible de générer une définition de type TContainer monophasée correspondant àContainer <T>. Je ne peux pas définir quelque chose comme une fonction qui prend Container <T> comme argument. En effet, T, qui est traitée comme une variable de type T dans les génériques d'origine, disparaît en même temps qu'elle est générée et ne se propage pas. Il faut beaucoup de travail boueux pour que cela fonctionne sérieusement, et à la fin, on a l'impression que cela n'en vaut pas la peine.

Enfin le sujet principal

Cela faisait longtemps, mais c'est finalement le sujet principal. C'est à peu près à mi-chemin ici.

JSON-to-GO?

Il semble que Qiita ait eu l'article suivant.

J'ai lu l'article ci-dessus et je l'ai découvert. Il semble y avoir un service pratique appelé JSON-to-Go. Cela signifie que si vous transmettez le JSON de API Response, la définition de type go correspondante sera sortie.

Vous pouvez voir à quoi il ressemble en cliquant sur l'exemple de lien dans l'API GitHub.

json-to-go-screenshot

Au fait, l 'implémentation de la partie Conversion utilisée dans ce service semble être sur Github. C'était du code écrit en js.

1. Portage de JSON-to-GO vers python

Il est bon de l'utiliser tel quel. C'est un bon sujet, donc je vais essayer de le porter sur python. Cependant, il s'agit d'une tentative d'utiliser la bibliothèque appelée prestring mentionnée ci-dessus pour créer quelque chose qui produit approximativement la même sortie plutôt qu'un port complet.

Le code porté ressemblera au lien ci-dessous.

C'est environ 120 lignes, donc ce n'est pas ce code dur. Le code js original est également d'environ 230 lignes, un peu plus que le code python, mais pas si gros. La raison de la différence de taille de code est que l'implémentation js d'origine définit le processus de conversion en un nom pour aller. C'est réduit, donc c'est environ 120 lignes. (À propos, le processus de conversion vers le nom de go utilisé dans le json-to-go original semblait pratique. Il y a une histoire d'incorporation de la même chose dans la pré-chaîne)

Eh bien, j'aimerais réellement faire le travail de portage, mais si je peux faire les opérations suivantes, il semble que je puisse convertir de JSON en code.

Pour chacun, jetez un œil au code json-to-go.js original et écrivez ce que vous trouvez intéressant.

Conversion en nom pour aller

Certaines des meilleures conversions de nom pour go sont:

>>> from prestring.go import goname
>>> goname("foo_id")
'FooID'
>>> goname("404notfund")
'Num404Notfund'
>>> goname("1time")
'OneTime'
>>> goname("2times")
'TwoTimes'
>>> goname("no1")
'No1'

Conversions telles que la suppression des traits de soulignement ou l'ajout d'un préfixe spécial aux noms commençant par un nombre. Quelque chose comme Camel Case.

Par exemple, URL, API, ASCII, ... semble être spécialement traité pour un acronyme spécifique. Je fais de mon mieux. Pour une liste de ces acronymes spécialement traités tirés du code golint.

Devinez le type de go à partir de la valeur de chaque champ dans JSON

Fondamentalement, on a l'impression que le type est ramifié un à un avec une instruction if de la valeur du résultat de la désérialisation de JSON.

Je pensais que c'était les trois choses suivantes.

  1. Convertissez ".0" en ".1" avant de désérialiser la chaîne JSON transmise
  2. En ce qui concerne int et float, les types sont classés dans l'ordre afin que le meilleur type soit sélectionné.
  3. Pour chaque champ de la structure, si la fréquence d'occurrence du champ ne correspond pas à la fréquence d'occurrence de la structure parent elle-même, omitempty

Générer une définition de type go

Cela peut être écrit proprement tel quel. Je veux dire, je n'ai pas lu sérieusement le code js original. C'est court donc je le posterai ici. Il est généré par la procédure suivante.

  1. json load
  2. Génération de struct info Convertit le résultat de l'analyse de json en un état intermédiaire facile à utiliser
  3. Génération de code

Pour la charge json de 1., chargez simplement le json passé. Comme pré-processus, je vais inclure un processus pour convertir le ".0" ci-dessus en ".1" (probablement pas nécessaire en python, mais cela semble être juste au cas où).

C'est la génération de struct info de «2.». struct info n'est pas une exagération, c'est le dictionnaire suivant. C'est une image qui analyse le JSON entier une fois et prend les informations suivantes.

{"freq": 1, "type": "int", "children": {}, "jsonname": "num"}

freq est la fréquence d'occurrence, type est le type dans go, children contient des éléments enfants s'il s'agit d'un dictionnaire (struct) et jsonname est le nom json d'origine (la clé du dictionnaire est le nom de type de go).

C'est la partie de génération de code de "3." C'est la partie où je peux écrire magnifiquement tel quel. N'est-ce pas un niveau de code qui peut être lu tel quel?

def emit_code(sinfo, name, m):
    def _emit_code(sinfo, name, m, parent=None):
        if sinfo.get("type") == "struct":
            with m.block("{} struct".format(name)):
                for name, subinfo in sorted(sinfo["children"].items()):
                    _emit_code(subinfo, name, m, parent=sinfo)
        else:
            m.stmt('{} {}'.format(name, to_type_struct_info(sinfo)))

        # append tag
        if is_omitempty_struct_info(sinfo, parent):
            m.insert_after('  `json:"{},omitempty"`'.format(sinfo["jsonname"]))
        else:
            m.insert_after('  `json:"{}"`'.format(sinfo["jsonname"]))

    with m.type_(name, to_type_struct_info(sinfo)):
        for name, subinfo in sorted(sinfo["children"].items()):
            _emit_code(subinfo, name, m, parent=sinfo)
    return m

C'est pourquoi j'ai un convertisseur JSON to go struct.

En fait, en utilisant le code porté, la [réponse de l'API github](https: // github. Le résultat de la conversion de com / podhmo / advent2016 / blob / master / json / github.json) est voici à quoi il ressemble .aller). Puisqu'il s'agit d'une sortie longue, j'en ai fait un lien séparé.

Le convertisseur JSON-to-go que j'ai obtenu, modifions-le un peu.

Après cela, je continuerai à vérifier le résultat de sortie en utilisant le JSON de la réponse API de github, qui a également été utilisé dans JSON-to-Go.

2. Essayez de changer la correspondance de type (utilisez par exemple strfmt.Uri)

Vous souhaiterez peut-être attribuer une chaîne spécifique, telle qu'un URI ou une adresse e-mail, à un type différent. Par exemple, changez pour utiliser Uri de strfmt.

Essayez d'utiliser strfmt.Uri si ": //" est inclus. J'ai également essayé d'ajouter l'importation lorsque strfmt.Uri est utilisé. Modifiez quelques lignes.

Si vous voulez traiter sérieusement différents types, il semble que vous écrirez la correspondance dans la partie qui devine le type d'aller.

La partie de sortie suivante

package autogen

type AutoGenerated struct {
	CloneURL        string      `json:"clone_url"`
	CreatedAt       time.Time   `json:"created_at"`
...

C'est devenu comme suit.

package autogen

import (
	"github.com/go-openapi/strfmt"
	"time"
)

type AutoGenerated struct {
	CloneURL        strfmt.Uri  `json:"clone_url"`
	CreatedAt       time.Time   `json:"created_at"`
...

3. Essayez de sortir avec une structure plate

A partir de là, c'est le début de l'hésitation. Les changements que je pensais possibles si je prenais un peu de soin avec un sentiment désinvolte ont commencé à causer divers problèmes gênants.

[Article lié](http://qiita.com/minagoro0522/items/dc524e38073ed8e3831b#%E8%A4%87%E9%9B%91%E3%81%AA%E6%A7%8B%E9%80% A0% E3% 81% AE-json-% E5% 87% A6% E7% 90% 86% E3% 81% A7% E7% 9B% B4% E9% 9D% A2% E3% 81% 99% E3% 82 % 8B% E5% 95% 8F% E9% A1% 8C) était plutôt dissed, mais j'ai eu un générateur de code. Je voulais incorporer des changements qui changeraient considérablement les résultats de sortie. Essayez de changer la structure de la structure de sortie d'une structure imbriquée à une structure plate. Pour le moment, j'ai décidé d'utiliser le nom du champ à ce moment-là comme nom de la structure.

Les changements concernent environ 10 lignes.

La définition suivante

type AutoGenerated struct {
	CloneURL        strfmt.Uri  `json:"clone_url"`
...
	Name            string      `json:"name"`
	OpenIssuesCount int         `json:"open_issues_count"`
	Organization    struct {
		AvatarURL         strfmt.Uri `json:"avatar_url"`
		EventsURL         strfmt.Uri `json:"events_url"`

Il a changé comme suit.

type AutoGenerated struct {
	CloneURL         strfmt.Uri   `json:"clone_url"`
...
	Name             string       `json:"name"`
	OpenIssuesCount  int          `json:"open_issues_count"`
	Organization     Organization `json:"organization"`
...

type Organization struct {
	AvatarURL         strfmt.Uri `json:"avatar_url"`
	EventsURL         strfmt.Uri `json:"events_url"`

Certes, si vous regardez le Résultat de sortie, vous pouvez voir le [Sortie précédente](https: // github. Puisque la structure de la relation d'imbrication obtenue dans com / podhmo / advent2016 / blob / master / dst / jsontogo / github2.go) a disparu, j'ai l'impression que la relation parent-enfant de la valeur est difficile à comprendre. Faire.

4. Donnez la valeur de la relation parent-enfant dans un commentaire

Étant donné que la structure de la relation parent-enfant des valeurs est difficile à comprendre en sortie plate, j'ai décidé d'ajouter un commentaire au début de la définition de la structure afin que la structure de la relation imbriquée puisse être négligée.

Le changement est d'environ 20 lignes. Le changement de ʻemit_code () `lui-même de la fonction de sortie d'origine est d'environ 2 lignes.

Les commentaires qui ont été ajoutés au début de la définition sont les suivants. Vous pouvez maintenant voir la relation d'imbrication.

/* structure
AutoGenerated
	Organization
	Owner
	Parent
		Owner
		Permissions
	Permissions
	Source
		Owner
		Permissions
*/

5. Évitez les conflits de noms

Au fait, je l'ai remarqué, et je suis sûr que si vous êtes une bonne personne, vous le remarquerez tout de suite. Il ya un problème. Les noms peuvent entrer en conflit. Dans la sortie de la structure imbriquée d'origine, la structure n'avait pas besoin d'être nommée car il s'agissait d'une définition immédiate. En raison de la mise à plat de la sortie imbriquée, la définition de structure a maintenant besoin d'un nom. J'utilisais directement le nom du champ. Par exemple, dans le JSON suivant, les noms seront en conflit.

{
  "title": "Premier journal",
  "author": "foo",
  "content": {
    "abbrev": "Je vais commencer à bloguer à partir d'aujourd'hui....",
    "body": "Je vais commencer à bloguer à partir d'aujourd'hui. Cet article est le Xème jour du calendrier de l'Avent.... ....."
  },
  "ctime": "2000-01-01T00:00:00Z",
  "comments": [
    {
      "author": "anonymous",
      "content": {
        "body": "hmm"
      },
      "ctime": "2000-01-01T00:00:00Z"
    }
  ]
}

Le contenu de la partie article et le contenu de la partie commentaire entrent en conflit.

/* structure
Article
	Comments
		Content
	Content
*/

//Contenu de la partie article
type Content struct {
	Abbrev string `json:"abbrev"`
	Body   string `json:"body"`
}

//Le contenu de la partie commentaire
type Content struct {
	Body string `json:"body"`
}

Pour le moment, peu importe si c'est maladroit, alors évitez les conflits de noms. Utilisez un objet appelé prestring.NameStore. Il s'agit d'un objet de type dictionnaire qui si vous mettez un nom dans la valeur, il retournera un nom qui évite les conflits pour les noms en double (remplacez NameStore.new_name () pour générer un nom Vous pouvez changer les règles).

>>> from prestring import NameStore
>>> ns = NameStore()
>>> ob1, ob2 = object(), object()
>>> ns[ob1] = "foo"
>>> ns[ob2] = "foo"
>>> ns[ob1]
'foo'
>>> ns[ob2]
'fooDup1'

Les changements concernent environ 10 lignes. Une valeur qui semble être unique pour la forme du dictionnaire pour struct est générée dans le texte, et le nom est géré en utilisant cela comme clé.

/* structure
Article
	Comments
		ContentDup1
	Content
*/

type Content struct {
	Abbrev string `json:"abbrev"`
	Body   string `json:"body"`
}

type ContentDup1 struct {
	Body string `json:"body"`
}

CotentDup1 n'est pas un bon nom. Pour le moment, la collision peut être évitée. C'est génial de dire que les packages sont séparés pour que les structures du même nom puissent être utilisées ensemble. Par exemple, ce serait pratique s'il y avait une fonction telle que le module ruby qui pourrait facilement introduire un espace de noms. Il ne semble y avoir aucune fonction qui puisse être utilisée pour aller, donc je vais la laisser.

6. Combinez les définitions en double en une seule

Maintenant que le conflit de nom a été résolu, regardons à nouveau la sortie en passant le JSON de l'API Github.

/* structure
AutoGenerated
	Organization
	Owner
	Parent
		Owner
		Permissions
	Permissions
	Source
		Owner
		Permissions
*/

Je sens que Parent et Source ont la même forme. Peeking inside Cela semblait être la même définition de type. Et, en fait, la définition de type des autorisations est apparue à plusieurs reprises. Le niveau supérieur Auto Generated lui-même ressemble à un sur-ensemble tel que Source, mais je le laisserai pour le moment. J'ai l'impression de vouloir combiner des définitions en double en une seule. Faisons le.

La partie modifiée fait environ 10 lignes. Depuis l'analyse de la partie commentaire qui sort la structure imbriquée de la structure qui a été sortie dans la modification précédente (4. Ajoutez la relation parent-enfant de la valeur en tant que commentaire) et l'analyse lors de la sortie de la définition de structure sont différentes, en premier lieu Séparé les fonctions.

Le résultat de sortie ressemble à lien, et j'ai pu supprimer la définition en double. Mais. Il y a un problème. Il sera corrigé à l'étape suivante.

7. Utilisez un meilleur nom pour le nom du type de structure

Il reste un problème. Vous pouvez le voir en regardant les parties Parent et Source. Il a la structure suivante, et Source et Parent ont la même forme.

/* structure
AutoGenerated
	Parent
		Owner
		Permissions
	Source
		Owner
		Permissions
*/

Le fait que Parent et Source aient la même forme signifie que le même type est utilisé. Le nom de ce type n'est pas bon. Si quoi que ce soit, Source est toujours un meilleur nom, mais un type nommé Parent a été défini et utilisé. triste.

type AutoGenerated struct {
...
	Parent           Parent       `json:"parent"`
...
	Source           Parent       `json:"source"`
...

}

Dans le JSON-to-GO d'origine, il s'agit d'une structure anonyme qui est définie immédiatement, vous n'avez donc pas à vous en soucier, mais en faisant une structure plate, vous devez lui donner un nom et également spécifier ce nom Vous ne disposez peut-être pas des informations appropriées pour le faire.

J'essaierai de résister un peu. C'est un peu délicat, mais je déciderai plus tard du nom de type à générer. Dans de tels cas, utilisez prestring.PreString.

from prestring import PreString
from prestring.go import GoModule

m = GoModule()
p = PreString("")
m.stmt("foo")
m.stmt(p)
m.stmt("bar")

p.body.append("*inner*")

print(m)

Bien sûr, vous pouvez utiliser le sous-module présenté précédemment. PreString est l'objet le plus basique, donc si vous souhaitez simplement définir une chaîne plus tard, vous devez l'utiliser.

foo
*inner*
bar

Le changement était d'environ 10 lignes

C'est un peu difficile à comprendre, mais le nom du modèle est décidé à la fin. Plus précisément, il est décidé sous la forme suivante.

#Passer une table de mots qui seront déduits comme argument
name_score_map={"parent": -1, '': -10}

#Déterminez le nom du modèle avant d'émettre. Nouveau calculé ici_Utiliser le nom comme nom du type
new_name = max(candidates, key=lambda k: name_score_map.get(k.lower(), 0))

Changé pour utiliser Source au lieu de Parent dans le résultat de sortie. La mise en œuvre ici peut être décidée en fonction du goût et du goût individuel. Par exemple, vous pourrez peut-être spécifier directement le nom converti. Dans ce cas, comme condition de réduction, il peut être pratique de passer quelque chose comme chemin au moment de la recherche, y compris le type de niveau supérieur au lieu de simplement le nom de l'argument.

Quoi qu'il en soit, j'ai pu faire du nom du modèle au moment de la sortie un meilleur nom.

type AutoGenerated struct {
...
	Parent           Source       `json:"parent"`
...
	Source           Source       `json:"source"`
...
}

8. Ajouter un commentaire au début de la définition de la structure (compatible golint)

Il existe de nombreux outils qui génèrent du code. C'est juste un petit grognement, mais le code généré par mockgen dans golang / mock n'ajoute pas de commentaire au début de la définition. Ceci est grondé par golang / lint, ce qui me rend très dérangé.

Et c'est toujours une génération automatique, mais si vous ajoutez un commentaire au code généré par golang / mock et que vous le faites correspondre, tout disparaîtra lorsque la définition d'interface originale sera remplacée et régénérée. .. Comme cela ne peut pas être aidé, nous devons faire un traitement spécial tel que l'exclure de la cible de golint. Je suis fatigué.

Au fait, ajoutons le résultat de cette génération de code car il n'y a pas de commentaire au début de la définition.

La partie modifiée est une ligne. J'ai besoin d'utiliser prestring.LazyFormat car j'utilise prstring.PreString. Ajoutez simplement une ligne. C'est proche d'un match de digestion.

9. Comme c'est un gros problème, ajoutez la valeur JSON d'origine à la balise à titre d'exemple

De plus, incluons la valeur JSON d'origine comme exemple dans la balise pour savoir quelle valeur sera entrée.

Les changements se font sur quelques lignes. J'en ai marre, alors je vais le terminer.

Il est maintenant sorti dans le format suivant.

// AutoGenerated : auto generated JSON container
type AutoGenerated struct {
	CloneURL         strfmt.Uri  `json:"clone_url" example:"https://github.com/octocat/Hello-World.git"`
	CreatedAt        time.Time   `json:"created_at" example:"2011-01-26T19:01:12Z"`
	DefaultBranch    string      `json:"default_branch" example:"master"`
	Description      string      `json:"description" example:"This your first repo!"`
...

Le code final est ici.

Seuls le premier et le dernier code et les résultats de sortie sont répertoriés ci-dessous.

(Effets secondaires de l'élimination des définitions en double)

C'est un petit détour. J'étais perdu pendant un moment et j'ai décidé de choisir une sortie au format plat. Dans le processus, nous avons ajouté une fonction pour supprimer les définitions en double. Cette suppression des définitions en double a eu un effet secondaire inattendu. C'est comme un petit plus, mais je vais vous le présenter.

Par exemple, jetez un œil au JSON suivant.

{
  "total": 100,
  "left": {
    "total": 75,
    "left": {
      "total": 25,
      "left": {
        "total": 20
      },
      "right": {
        "total": 5
      }
    },
    "right": {
      "total": 50,
      "left": {
        "total": 25
      },
      "right": {
        "total": 25
      }
    }
  },
  "right": {
    "total": 25,
    "left": {
      "total": 20,
      "left": {
        "total": 10
      },
      "right": {
        "total": 10
      }
    },
    "right": {
      "total": 5
    }
  }
}

C'est quelque chose qui est un bel arbre de deux quarts.

La sortie de ceci dans le format imbriqué d'origine est la suivante. Puisque le type qui correspond directement à la valeur JSON passée a été généré, non seulement il y a une définition de structure inutile, mais si la structure change même un peu, il ne sera pas possible d'analyser. mal.

type Tree struct {
	Left struct {
		Left struct {
			Left struct {
				Total int `json:"total"`
			} `json:"left"`
			Right struct {
				Total int `json:"total"`
			} `json:"right"`
			Total int `json:"total"`
		} `json:"left"`
		Right struct {
			Left struct {
				Total int `json:"total"`
			} `json:"left"`
			Right struct {
				Total int `json:"total"`
			} `json:"right"`
			Total int `json:"total"`
		} `json:"right"`
		Total int `json:"total"`
	} `json:"left"`
	Right struct {
		Left struct {
			Left struct {
				Total int `json:"total"`
			} `json:"left"`
			Right struct {
				Total int `json:"total"`
			} `json:"right"`
			Total int `json:"total"`
		} `json:"left"`
		Right struct {
			Total int `json:"total"`
		} `json:"right"`
		Total int `json:"total"`
	} `json:"right"`
	Total int `json:"total"`
}

D'un autre côté, si cela est sorti dans un format plat qui élimine les définitions en double, ce sera comme suit (les commentaires qui spécifient la structure sont omis car ils sont ennuyeux). C'est juste une définition récursive, donc c'est naturel. bien. (Cependant, si cela est laissé tel quel, la valeur zéro ne sera pas déterminée et elle se répétera indéfiniment, donc ce sera une erreur. * Il est inutile à moins que cela ne devienne Tree. Est-il ennuyeux d'utiliser des références à toutes les structures comme pointeurs? Sérieusement Recherche d'un cycle de référence pour déterminer la fin)

// Tree : auto generated JSON container
type Tree struct {
	Left  Tree `json:"left"`
	Right Tree `json:"right"`
	Total int  `json:"total" example:"100"`
}

à la fin

Ça fait longtemps, mais c'est tout.

Dans cet article, j'ai écrit le code python qui génère le code go. Plus précisément, j'ai essayé d'implémenter un processus similaire à la conversion de JSON-to-Go JSON pour aller à la définition de structure en python. Après cela, je l'ai laissé à l'ambiance de l'époque et j'ai changé le code et changé le résultat de sortie tout en marmonnant mes pensées.

Quand j'ai écrit un peu sur ce que je pensais dans le processus, c'était que la réinvention de la roue (réimplémentation dans ce cas) pourrait ne pas être étonnamment mauvaise. Tout d'abord, vous obtiendrez une implémentation qui a une compréhension complète de tout. Vous saurez où et quelles modifications apporter à cette implémentation, et vous aurez l'impression que c'est la vôtre, et vous voudrez tout modifier. De là, c'est le début d'une exploration personnelle. Que diriez-vous de changer cette partie comme ça? Vous pourrez peut-être voir les divisions et les idées faites par la personne qui a créé l'implémentation d'origine tout en apportant de petites modifications et en comparant la différence entre le résultat du changement et le résultat d'origine. .. Par exemple, dans cet exemple, il est étrange de sélectionner une sortie imbriquée.

Et cette fois, il a été généré automatiquement sans tenir compte du type de go. La prochaine fois, j'aimerais pouvoir écrire sur la génération automatique en tenant compte du type de go.

À propos, en ce qui concerne cette utilisation, il n'est pas nécessaire de le réimplémenter en python, et il existe les outils suivants pour générer la définition de go struct à partir de GO made JSON.

Post-scriptum:

Certaines personnes ont mentionné gojson dans le calendrier de l'Avent de cette année.

Recommended Posts

Essayez d'écrire du code python pour générer du code go - Essayez de porter JSON-to-Go et ainsi de suite
Écrivons du code python qui analyse le code go et génère du code go
Python 3.6 sous Windows ... et vers Xamarin.
Comment écrire du code pour accéder à python dashDB sur Bluemix ou local
Premier python ② Essayez d'écrire du code tout en examinant les fonctionnalités de python
Portage et modification du solveur de doublets de python2 vers python3.
Compressez les données python et écrivez sur sqlite
Comprenez les listes Python, les dictionnaires, etc.
Mettez Cabocha 0.68 dans Windows et essayez d'analyser la dépendance avec Python
[Python] Créez un linebot pour écrire le nom et l'âge sur l'image
Essayez de créer un environnement python et anaconda sur Mac (avec pyenv, conda)
Je veux formater et vérifier le code Python à mon goût sur VS Code
Essayez de porter le programme «Programmation informatique numérique FORTRAN77» vers C et Python (partie 1)
3.14 π jour, alors essayez de sortir en Python
Essayez de porter le programme "FORTRAN77 Numerical Computing Programming" vers C et Python (partie 3)
Essayez de générer automatiquement des documents Python avec Sphinx
Essayez CI le code python poussé sur GitHub.
Essayez de porter le programme "FORTRAN77 Numerical Computing Programming" vers C et Python (partie 2)
Pour écrire dans Error Repoting en Python sur GAE
Liste de code Python à déplacer et à mémoriser
Essayez d'importer des données MLB sur Mac et Python
Effectuez une recherche Twitter à partir de Python et essayez de générer des phrases avec la chaîne de Markov.
L'histoire du portage du code de C vers Go (et vers la spécification du langage)
Je veux écrire en Python! (1) Vérification du format de code
Essayez le fonctionnement de la base de données avec Python et visualisez avec d3
Conseils et précautions lors du portage des programmes MATLAB vers Python
Ce n'est pas facile d'écrire Python, c'est facile d'écrire numpy et scipy
Écrire des tests en Python pour profiler et vérifier la couverture
Écrire du code dans UnitTest une application Web Python
Comment profiter de Python sur Android !! Programmation en déplacement !!
Bon code et mauvais code à comparer avec la mini-carte
Tutoriel "Cython" qui rend Python explosif: lorsque le code C ++ dépend de la bibliothèque. Écrivez setup.py.
[Python / Ruby] Comprendre le code Comment obtenir des données en ligne et les écrire au format CSV
Une histoire sur le portage du code de "Essayez de comprendre comment fonctionne Linux" sur Rust
Installez pyenv sur MacBookAir et basculez Python à utiliser
Essayez d'ouvrir une sous-fenêtre avec PyQt5 et Python
Installer python et Visual Studio Code sur Windows10 (version d'avril 2020)
[Python] Utilisez ceci pour lire et écrire des fichiers wav [wavio]
Comment écrire une classe méta qui prend en charge à la fois python2 et python3
C'est Halloween donc je vais essayer de le cacher avec Python
[ROS] Comment écrire un éditeur et un abonné sur un nœud
Essayez simplement de recevoir un webhook avec ngrok et Python
Aller au langage pour voir et se souvenir de la partie 8 Appeler le langage GO à partir de Python
PyArmor ~ Un moyen facile de chiffrer et de fournir du code source Python ~
Python sur Ruby et Ruby en colère sur Python
Essayez de comprendre Python soi
Commande pour générer un code QR
Mettez à jour le python que vous aviez sur votre Mac à 3.7-> 3.8
Écrire en csv avec Python
Générer du code QR en Python
Convertir le code python 3.x en python 2.x
Faites fonctionner Jupyter avec l'API REST pour extraire et enregistrer le code Python
Comment installer OpenCV sur Cloud9 et l'exécuter en Python
Comment écrire un exemple d'implémentation E11 Ruby et Python en temps réel hors ligne
Un jeu pour partir à l'aventure en mode interactif de python
[PEP8] Reprenez le code source Python et écrivez-le proprement
IME On / Off est affiché par LED en coopération avec Python et Arduino
Essayez de créer un environnement python avec Visual Studio Code et WSL
Installez rapidement OpenCV2.4 (+ python) sur OS X et essayez l'exemple