Épisode 10 - Python turtle + compatibilité
Nous en profitons de plus pour te réaliser le travail titanesque d'étendre les tests aux modèles plus anciens :
- toutes les calculatrices graphiques Texas Instruments (depuis la première TI-81 de 1990)
- les calculatrices graphiques Casio de la génération Icon Menu Power Graphic (depuis 1996)
Mais qu'est-ce que c'est que turtle ? Les interpréteurs Python sur nos calculatrices peuvent offrir usuellement jusqu'à 3 types de modules de tracé :
- tracé par pixels, habituellement propriétaire au constructeur
- tracé dans un repère, plus ou moins proche du standard matplotlib.pyplot
- et tracé relatif à la tortue, plus ou moins proche du standard turtle, le plus proche de ce qui a été pratiqué au collège avec le langage Scratch
Nous allons profiter de l'occasion pour faire d'une pierre deux coup. Nous allons à la fois découvrir ensemble le nouveau turtle des TI-Nspire CX II, et directement le comparer à ce qui existe déjà chez la concurrence, à savoir :
- turtle pour TI-Nspire CX II
- turtle sur Casio Graph 35+E II et Graph 90+E
- turtle sur NumWorks
- turtle via KhiCAS sur NumWorks et TI-Nspire CX
- ce_turtl sur TI-83 Premium CE Edition Python, TI-84 Plus CE-T Python Edition et TI-84 Plus CE Python
Nous allons donc exécuter quelques scripts turtle et comparer leurs affichages à ce que donne le turtle Python standard sur ordinateur, et donc la plus ou moins grande facilité que tu auras à exécuter des scripts Python turtle conçus pour d'autres plateformes. ce_turtl était particulièrement mauvais sur ce dernier point ; nous allons donc voir si Texas Instruments a apporté davantage de soin à son module turtle pour TI-Nspire CX II.
Commençons déjà par détecter quelques problèmes bloquants avant l'exécution, et peut-être même les corriger. Voici notre tout nouveau Turtle Doctor :
- Code: Select all
_turtle_errors = 0
def _turtle_error(k):
global _turtle_errors
_turtle_errors |= 1 << k
# import turtle
try: #TI-83 Premium CE
from ce_turtl import turtle
turtle.clear()
_turtle_error(0)
except ImportError:
import turtle
if not "forward" in dir(turtle):
turtle = turtle.Turtle()
# can turtle be patched ?
_fix_turtle = True
try:
def _fixcolorlist(c): return c
def _fixcolorval(c): return c
def _fixcolorstring(c): return c
def _fixcolor(c): return turtle._fixcolorlist(turtle._fixcolorval(turtle._fixcolorstring(c)))
turtle._fixcolorlist = _fixcolorlist
turtle._fixcolorval = _fixcolorval
turtle._fixcolorstring = _fixcolorstring
turtle._fixcolor = _fixcolor
except:
_fix_turtle = False
# test/fix color() + pencolor()
if not "pencolor" in dir(turtle):
_turtle_error(1)
if _fix_turtle: turtle.pencolor = turtle.color
if not "color" in dir(turtle):
_turtle_error(2)
if _fix_turtle: turtle.color = turtle.pencolor
# test color argument types
_color_types = 0
try:
turtle.pencolor([0, 0, 0])
_color_types |= 1 << 0
except: _turtle_error(4)
try:
turtle.pencolor((0, 0, 0))
_color_types |= 1 << 1
except: _turtle_error(5)
try:
turtle.pencolor(0, 0, 0)
_color_types |= 1 << 2
except: pass
try:
turtle.pencolor("black")
_color_types |= 1 << 3
except: _turtle_error(6)
_fix_color = not _color_types & 1 << 0 or not _color_types & 1 << 1 or not "colormode" in dir(turtle)
# fix list/tuple color argument
if _fix_turtle:
if not _color_types & 1 << 0 and _color_types & 1 << 1:
def _fixcolorlist(c): return type(c) is list and tuple(c) or c
turtle._fixcolorlist = _fixcolorlist
if not _color_types & 1 << 1 and _color_types & 1 << 0:
def _fixcolorlist(c): return type(c) is list and list(c) or c
turtle._fixcolorlist = _fixcolorlist
# fix color() + pencolor()
if _fix_turtle and _fix_color:
turtle._color = turtle.color
turtle._pencolor = turtle.pencolor
if _color_types & 1 << 0 or _color_types & 1 << 1:
def _color(*argv):
if not(len(argv)): return turtle._color()
turtle._color(turtle._fixcolor(argv[0]))
def _pencolor(*argv):
if not(len(argv)): return turtle._pencolor()
turtle._pencolor(turtle._fixcolor(argv[0]))
else:
def _color(*argv):
if not(len(argv)): return turtle._color()
c = turtle._fixcolor(argv[0])
turtle._color(c[0], c[1], c[2])
def _pencolor(*argv):
if not(len(argv)): return turtle._pencolor()
c = turtle._fixcolor(argv[0])
turtle._pencolor(c[0], c[1], c[2])
turtle.color = _color
turtle.pencolor = _pencolor
# test/fix colormode()
_color_mode = 0
if not "colormode" in dir(turtle):
_turtle_error(3)
# test color mode
try:
turtle.pencolor([255, 0, 0])
_color_mode = 255
except: _color_mode = 1.0
if _fix_turtle:
turtle._color_mode = _color_mode
def _colormode(*argv):
if not(len(argv)): return turtle._color_mode
if int(argv[0]) in (1, 255):
turtle._color_mode = int(argv[0]) == 255 and 255 or 1.0
turtle.colormode = _colormode
if _color_mode == 255:
def _fixcolorval(c): return int(turtle._color_mode) == 1 and type(c) in (list, tuple) and [int(c[k] * 255) for k in range(3)] or c
else:
def _fixcolorval(c):
return turtle._color_mode == 255 and type(c) in (list, tuple) and [int(c[k] / 255) for k in range(3)] or c
turtle._fixcolorval = _fixcolorval
# test/fix color strings
_colors_fix={"black":(0,0,0),"blue":(0,0,1),"green":(0,1,0),"red":(1,0,0),"cyan":(0,1,1),"yellow":(1,1,0),"magenta":(1,0,1),"white":(1,1,1),"orange":(1,0.65,0),"purple":(0.66,0,0.66),"brown":(0.75,0.25,0.25),"pink":(1,0.75,0.8),"grey":(0.66,0.66,0.66)}
for c in list(_colors_fix.keys()):
try:
turtle.pencolor(c)
_colors_fix.pop(c)
except: pass
turtle.pencolor((0, 0, 0))
if len(_colors_fix):
if _color_types & 1 << 3:
_turtle_error(7)
if _fix_turtle:
def _fixcolorstring(c):
if type(c) is str and c in _colors_fix:
c = _colors_fix[c]
if turtle.colormode() == 255:
c = [int(c[k] * 255) for k in range(3)]
return c
turtle._fixcolorstring = _fixcolorstring
# test/fix circle(,)
try: turtle.circle(0,0)
except:
_turtle_error(8)
if _fix_turtle:
turtle._circle = turtle.circle
def _circle(r, a=360): turtle._circle(r)
turtle.circle = _circle
if not "write" in dir(turtle):
_turtle_error(9)
if _fix_turtle:
def _write(s): pass
turtle.write = _write
if not "pensize" in dir(turtle):
_turtle_error(10)
if _fix_turtle:
def _pensize(s): pass
turtle.pensize = _pensize
def turtle_diags():
print("Type: " + str(type(turtle)))
print("Patchable: " + (_fix_turtle and "yes" or "no"))
errors_msg = (
"No <import turtle>",
"No pencolor()",
"No color()",
"No colormode(): " + str(_color_mode),
"No color as list",
"No color as tuple",
"No color as string",
"Missing colors strings: ",
"No circle(,angle)",
"No write()",
"No pensize()",
)
errors = 0
for k in range(len(errors_msg)):
if _turtle_errors & 1 << k:
errors += 1
msg = "Err " + str(k) + ": " + errors_msg[k]
if k == 7:
msg += str(len(_colors_fix)) + " " + str(tuple(_colors_fix.keys()))
print(msg)
print(str(errors) + " error" + ((errors > 1) and "s" or ""))
Par exemple, Turtle Doctor ne détecte a priori strictement aucun problème bloquant sur la NumWorks
Aucun problème non plus avec KhiCAS pour NumWorks et TI-Nspire CX !
- absence de la méthode .color()
- absence de la méthode .colormode()
Le but des corrections n'est pour le moment pas d'obtenir quelque chose d'identique au standard, mais juste de permettre l'exécution des scripts qui vont suivre :
- Nous choisissons de créer une méthode .color() synonyme de .pencolor()
- Et pour .colormode(), outre la création de la méthode, il nous faut détecter le format de coordonnées de couleurs attendu par le module, afin de convertir le cas échéant. La méthode .colormode() lorsque présente permet de basculer entre les 2 systèmes de coordonnées suivants :
- mode 255 : couleurs RGB avec chaque composante prenant une valeur entière de 0 à 255
- mode 1.0 : couleurs RGB avec chaque composante prenant une valeur flottante de 0 à 1
Une fois installé correctement dans le dossier /PyLib/ comme expliqué, les fonctions offertes par turtle sont alors rajoutées au menu.
Attention toutefois, comme tout module présent dans le dossier /PyLib/, turtle ne sera pas disponible en mode examen.
Le module s'importe de la façon suivante, qui est bien une des façons standard :
- Code: Select all
from turtle import Turtle
turtle = Turtle()
Si jusqu'à présent les quelques écarts avec le standard pouvaient être qualifiés de quelques détails de cas particuliers, ici cela commence à faire beaucoup. Pas moins de 4 problèmes sont détectés dont un majeur :
- absence de la méthode .colormode(), avec un fonctionnement bloqué en mode 255
- absence de gestion du 2ème argument de la méthode .circle() pour tracer un arc de cercle
- et pire, pour les paramètres de couleur :
- refus des paramètres de type liste, n'accepte que des tuples - est-ce un bug ?...
- accepte les paramètres de type chaîne de caractères, mais ignore plusieurs codes de couleur usuels : "pink", "grey", "brown", "purple"
- déjà, de par son nom il ne s'importe pas de façon standard, c'est-à-dire qu'aucune des 3 méthode suivantes ne fonctionne :
import turtle
,from turtle import *
, ou encore- Code: Select all
from turtle import Turtle
turtle = Turtle()
- absence de la méthode .pencolor(), qui est remplacée ici par .color()
- absence de la méthode .colormode(), avec un fonctionnement bloqué en mode 255
- absence de la méthode .write() pour écrire du texte
- absence de gestion du 2ème argument de la méthode .circle() pour tracer un arc de cercle
- et pire, pour les paramètres de couleur, refus de toute les formes standard : aussi bien liste que tuple ou chaîne de caractère. La méthode color() attend non pas 1 mais 3 arguments, soit un argument par composante.
- Code: Select all
try: # TI-83PCE/84+CE
from ce_turtl import turtle
turtle.clear()
except ImportError:
import turtle # multiplateformes
if not "forward" in dir(turtle): # TI-Nspire CX II
turtle = turtle.Turtle()
Nous allons pour cela prendre plusieurs exemples et lancerons le même code sur différents modèles.
On commence par une petite rosace ; tout possesseur de Graph 35+E II sait que Casio adore ça :
ordi | NumWorks | Graph 90+E Graph 35+E II | TI-Nspire CX II turtle | TI-83PCE/84+CE ce_turtl | |
|
Petit léger détail, le turtle.pensize(1) n'est respecté ni par KhiCAS ni par ce_turtl.
Ceci mis à part, le code passe ici sans problème.
ordi | NumWorks | Graph 90+E Graph 35+E II | TI-Nspire CX II turtle | TI-83PCE/84+CE ce_turtl | |
|
Pas de nouveau problème ici.
ordi | NumWorks | Graph 90+E Graph 35+E II | TI-Nspire CX II turtle | TI-83PCE/84+CE ce_turtl | |
|
Pour ce que l'on obtient pas de problème de tracé avec le module turtle de KhiCAS, le problème vient d'autre chose. Ce module turtle a l'air d'être extrêmement gourmand, arrivant à déclencher une erreur de mémoire en cours d'exécution alors que d'autres modèles avec un heap Python absolument ridicule en comparaison s'en sortent parfaitement.
On comprend mieux ici le problème du .pensize() sur ce_turtl et KhiCAS. Malgré les réglages différents tous les flocons sont ici trop épais d'1 pixel, il y a visiblement un décalage.
Mais notons justement par rapport à ce_turtl, que notre script Turtle Doctor a visiblement correctement injecté l'interception des paramètres de couleurs passés sous la forme de chaînes de caractères.
ordi | NumWorks | Graph 90+E Graph 35+E II | TI-Nspire CX II turtle | TI-83PCE/84+CE ce_turtl | |
|
Notons que Turtle Doctor a réussi à parfaitement corriger les paramètres de couleurs sur ce_turtl, tuples et listes étant maintenant utilisables !
ordi | NumWorks | Graph 90+E Graph 35+E II | TI-Nspire CX II turtle | TI-83PCE/84+CE ce_turtl | |
|
ce_turtl nous fait ici une véritable catastrophe. Le problème vient de la méthode .circle() qui ne respecte pas du tout le standard. Au lieu de tracer un cercle qui passe par la position de la tortue, elle trace un cercle qui prend pour centre la position de la tortue.
ordi | NumWorks | Graph 90+E Graph 35+E II | TI-Nspire CX II turtle | TI-83PCE/84+CE ce_turtl | |
|
Et mince, c'est justement le piège qui fait trébucher pas mal de modèles.
Ici encore, après avoir commencé un tracé parfait, KhiCAS se met à manquer de mémoire.
ordi | NumWorks | Graph 90+E Graph 35+E II | TI-Nspire CX II turtle | TI-83PCE/84+CE ce_turtl | |
|
Et mince, c'est justement le piège qui fait trébucher pas mal de modèles.
Rapidement, très léger détail sur les Casio Graph 35+E II et Graph 90+E. La méthode .write() prend les coordonnées indiquées comme coin supérieur gauche du texte affiché, alors que le standard est de les prendre comme coin inférieur gauche.
Pour les modules qui ne gèrent pas l'appel
.circle(rayon, angle)
les arcs de cercles sont ici remplacés par des cercles, ce qui naturellement perturbe le reste du tracé.Le cas KhiCAS est toutefois plus surprenant, cet appel étant bien géré...
Sur la conformité au standard turtle ce n'est certes pas le meilleur, même si cela reste honorable. Il y a bien pire et plus grave que cela. Texas Instruments a déjà fait un fort bel effort relativement à la catastrophe qu'était ce_turtl.
Nous ignorons si Texas Instruments poursuivra ses efforts, mais à défaut nous avons quand même une excellente nouvelle. Bien que l'on n'ait pas accès au code source du module turtle TI-Nspire CX II celui-ci a le gros avantage de nous présenter des éléments modifiables à chaud. Comme de plus nous bénéficions ici d'un heap Python extrêmement généreux, pas moins de 2 Mo soit l'un des plus larges tous modèles concurrents confondus, une conformité parfaite au standard est bel et bien envisageable, pourvu que quelqu'un se donne le temps de creuser la question.
En attendant donc mieux, les différentes solutions Python turtle disposent désormais dans nos tableaux d'un indice de compatibilité / conformité au standard, basé sur les tests précédents :