noms, PyObjects, et identification

un bon tuto sur la memoire qui se plante ?

Le problème exposé dans ce sujet a été résolu.

Salut, je fais de l’introspection sur python avant de repartir sur des langages plus bas niveau. voici un bon tuto: https://realpython.com/pointers-in-python/

au moment d’avoir le sourire de quelqu’un qui commence à comprendre, je teste en ligne de commande puis avec idle3. Et ça plante…

le tuto dit celà:

>>> x = 1000
>>> y = 1000
>>> x is y
True

ce qui aurait bien collé avec leurs explications.

Chez moi:

>>> x = 1000
>>> y = 1000
>>> x is y
False
>>> id(x)
140191909473360
>>> id(y)
140191909473424

du coup je ne sais pas quelle partie de leur tuto est correcte (moi qui avais "compris" ;-( ) ???

Salut,

C’est bien là tout le soucis de l’opérateur is utilisé sur des nombres et ce qui est expliqué dans le cours.

Ce sont des optimisations qui font que les nombres vont être internés et donc que la même instance sera renvoyée. On ne peut donc pas se reposer là-dessus pour avoir un comportement fiable.

Salut,

L’exemple du tuto n’est effectivement pas ultra-clair, il n’y a aucune garantie que x et y vont pointer vers le même objet en mémoire. C’est montré de façon un peu détourner avec l’exemple suivant qui créé 1000 de façon détournée avec une somme 501+499 qui se retrouve à une adresse différente du premier 1000. Note que tout ça est fortement dépendent de la version que tu utilises, il n’y a aucune garantie que les exemples de ce tuto vont continuer à fonctionner dans le futur. Peut être qu’un jour, 1000 sera parmi les objets internés au lieu d’être dupliqué.

Par ailleurs, je rebondi un peu sur ce point :

je fais de l’introspection sur python avant de repartir sur des langages plus bas niveau

Si ton but est de disséquer un peu le fonctionnement interne de Python, très bien, c’est intéressant en soi. Si ton but est de t’en servir comme tremplin vers le bas niveau, c’est probablement une mauvaise idée : tu vas surtout te familiariser avec les bizarreries de la machine virtuelle Python et toutes les astuces qui sont là pour améliorer un peu les performances en jouant autour du modèle mémoire. Au final, ce sera très particulier et bourré de pratiques qui ne sont absolument pas nécessaires lorsqu’on fait de la programmation système.

Essentiellement, les objets Python sont des Rc<RefCell<T>> (toi qui a touché un peu à Rust si je ne m’abuse) : des pointeurs vers des objets sur le heap dont l’état interne est mutable. Le reste sont des fioritures pour savoir quand on créé un nouvel objet plutôt que modifier l’existant (mutables vs immutables) et comment on évite (un peu) la duplication d’objets (avec le mécanisme d’objets internés).

+3 -0

@entwanne : objets internés, c’est du nested objects ?

@deuchnord : bonne remarque

@adri1 : dans le mille c’est le cas de le dire. Merci pour la précision sur objets Python = Rc<RefCell<T>> si tu as d' autres points de comparaisons sur Rust ou en Asm ça m' interesse énormément. As tu des conseils de dissection pour aller grater là où c’est le plus interessant ? j’ai remarqué locals, globals, gc, inspect, ctypes peut-être stuct. Je vais essayer de recréer un dict customisé, j’ai eu des erreurs du genre TypeError: unhashable type: 'dict’. Un article de blog qui va en profondeur sur ce genre d' experimentations ? J’ai pensé aussi a exploré depuis un autre processus mais là c’est chaud.

@Geo si tu passes dans le coin, tes lumières sont les bienvenues. Avec bitstring, volatility et capstone il doit il y avoir des super trucs à faire. Je ne sais pas si volatility ne fonctionne que sur de la RAM figée ou si on peut faire un seul processus et en live.

+0 -0

@entwanne : objets internés, c’est du nested objects ?

buffalo974

Non c’est le mécanisme qui fait que des instances de certains objets sont conservées dans la mémoire interne de l’interpréteur pour être renvoyées quand tu demandes à nouveau une valeur similaire.

C’est accessible via la fonction intern du module sys (c’était auparavant une fonction built-in en Python 2) qui te permet d'interner des chaînes de caractères.

Sans intern :

>>> s2 = 'a' * 1000
>>> s2 = 'a' * 1000
>>> s1 is s2
False

Avec intern :

>>> import sys
>>> sys.intern('a' * 1000)
'aaa...'
>>> s1 = 'a' * 1000
>>> s2 = 'a' * 1000
>>> s1 is s2
True

@adri1 : dans le mille c’est le cas de le dire. Merci pour la précision sur objets Python = Rc<RefCell<T>> si tu as d' autres points de comparaisons sur Rust ou en Asm ça m' interesse énormément.

Il n’y a pas forcément grand chose d’intéressant à dire sur le modèle mémoire de Python. Cela dit, le fait que les objets sont en gros des Rc<RefCell<T>> plutôt que des Arc<Mutex<T>> explique la nécessité du GIL: leur design est trop fragile pour garantir que leur manipulation est thread safe. Un autre parallèle, beaucoup plus immédiat, est que les dictionnaires en Python (qui sont présents partout) sont en gros des HashMap<K, V> et demandent que les K soient hashables (ce qui rejoint l’erreur que tu as mentionnée plus bas).

As tu des conseils de dissection pour aller grater là où c’est le plus interessant ? j’ai remarqué locals, globals, gc, inspect, ctypes peut-être stuct.

T’as des trucs assez variés qui touchent à des choses assez différentes. gc est probablement le plus proche de ce que tu as déjà abordé dans ce sujet, il donne accès au garbage collector. Il s’occupe de nettoyer les objets dont le compteur de référence est tombé à 0, et surtout a des heuristiques pour détecter les cycles de références qui ne sont pas joignables de l’extérieur. La comparaison avec Rc s’effrite ici d’ailleurs, puisque le modèle d'ownership de Rust couplé à Rc ne permet pas d’éliminer un cycle de Rc (d’où la nécessité de Weak<T>). Le fait que Python utilise un GC (plutôt que bêtement virer les objets dès que leur nombre de référence tombe à 0 comme le fait Rc) est uniquement pour éviter les fuites lorsqu’il y a un cycle de référence.

locals, globals et inspect te permettent de jouer avec les mécanismes du langage plutôt que de son implémentation, pas sur que ce soit ce que tu cherches.

ctypes et struct seront utiles pour t’interface avec du code C (ou qui respecte l’API C), tu pourrais avoir intérêt à fouiller du côté de l’API C offerte par Python.

Un truc qui me parait plus dans la ligné directe de ce que tu veux faire est le module dis qui te permet de manipuler le bytecode interprété par la machine virtuelle CPython (l’implémentation standard de Python). Le code Python est compilé sous forme de bytecode (et mis dans le dossier __pycache__ que tu as sûrement vu trainer). C’est ce bytecode qui est interprété plutôt que le code Python lui-même (pour des raisons de perf).

Je vais essayer de recréer un dict customisé, j’ai eu des erreurs du genre TypeError: unhashable type: 'dict’. Un article de blog qui va en profondeur sur ce genre d' experimentations ?

Pas facile de donner plus de détails sans un code, mais ton problème est que les clés des dictionnaires doivent implémenter __hash__ (pour pouvoir construire la hash table qui permet le lookup en temps amorti constant), autrement dit être hashable. De manière similaire, pour HashMap<K, V>, tu as besoin de K: Hash (et Eq mais c’est un choix de Rust).

Or, les instances de dictionnaires ne sont pas hashable. Le message d’erreur est pas terrible d’ailleurs, parce que le type dict (qui est une instance de type) lui est parfaitement hashable. De manière générale, les mutables ne sont pas hashable en Python (tes propres types peuvent l’être si tu veux par contre), je pense pour éviter de casser l’attente id(a) == id(b) implique hash(a) == hash(b) (avec des valeurs mutables, ça va dépendre de ce qui se passe entre les deux calculs de hash comme on s’attend à ce que le hash dépende de la valeur).

+1 -0

@adri1 : penses qu’il serait possible avec l' interpréteur rustpython d’utiliser un équivalent de ctypes.CDLL(mon_executable_ecris_en_rust) pour importé un faux objet python, sculté de A à Z côté rust ? Il ne s’agit pas de deserialization mais de "tricotage" d' objet d’un langage vers un autre. Un peu comme on forgerait manuellement des paquet avec scapy ? C’est expérimental pour découvrir par l’erreur…

par exemple:

class Foobar():
    def __init__(self,dico,listo):
        self.mydict = dico
        self.mylist = listo

et d’exporter une instance baz de Foobar de rust vers python ?

EDIT : ça à l’air d’être une usine à gaz, je vais jetter un coup d' oeil à cython et aux pointeurs avant de descendre plus bas.

+0 -0

penses qu’il serait possible avec l' interpréteur rustpython d’utiliser un équivalent de ctypes.CDLL(mon_executable_ecris_en_rust) pour importé un faux objet python, sculté de A à Z côté rust ? Il ne s’agit pas de deserialization mais de "tricotage" d' objet d’un langage vers un autre. Un peu comme on forgerait manuellement des paquet avec scapy ? C’est expérimental pour découvrir par l’erreur…

Je pense que tu ne prends pas le problème du bon angle. La bonne façon de faire ça est d’utiliser directement l’API C de Python, qui fonctionne dans les deux sens. Tu as PyO3 en Rust qui est un binding de cet API. Techniquement, en arrière plan, ça fait tout ce "tricotage" dont tu parles.

je vais jetter un coup d' oeil à cython et aux pointeurs avant de descendre plus bas

Tu pars un peu dans tous les sens là, cython est un langage plus large que Python, que tu peux utiliser pour interface C et Python. C’est encore une approche un peu différente. Pas sur de ce que tu veux dire par "jeter un œil aux pointeurs", si tu ne sais pas ce qu’est un pointeur maintenant, c’est effectivement un bon moment pour apprendre (mais normalement, tu as croisé ça en apprenant Rust). Une façon de faire pourrait être de lire le tuto sur le C qu’on a ici, t’as vite fait le tour de C et ça te rafraîchira la mémoire sur des notions bas niveau.

+1 -0

Oui tu as raison, maintenant je me rappelle d’avoir croisé PyO3 ça va être interessant. Le C, j’aime pas trop … je trouve ça "vieux". Le rust est super mais à un moment je m’étais embrouillé avec les lifetime, Box, Rc, Refcell etc. dernière question pour refermer ce topique, je n’arrive pas à rendre visible dans inspect.locals et inspect.globals des objets contenus dans d’autres objets. Quelles sont les adresses des objets extra et inner à l' interieur de outer ? Et si je met en attribut de outer une liste d' objets, ou un dictionnaire contenant des objets ?

import gc

"""
bigmachinery = gc.get_objects()
for machine in bigmachinery:
    print(f"-  {machine}      type {type(machine)}  ")

print(len(bigmachinery))
"""


import  os
import sys
import inspect

from collections import *
import collections


import ctypes



class Inner():
    

    def __init__(self):
        self.secret = "this is inner secret"



class Extra():
    

    def __init__(self, extramsg):
        self.extramsg = extramsg



class Outer():
    

    def __init__(self, msg: str, nested_obj = Inner()):

        self.msg = msg
        self.nested_obj = nested_obj

        self.extra_obj = None

    def setup(self):
        self.locals = locals()
        self.gobals = globals()

    def make_extra(self):

        x = input("please give a short message : ")

        self.extra_obj = Extra(x)

    def show_loc_and_glob(self):
        print("\n *******")
        print(self.locals)
        print("---------")
        for glob in self.gobals:
        	print(glob)





def main():

    print("inside main fn")

    my_out = Outer("out message")

    print(" my_out = ", my_out)

    my_out.setup()

    my_out.show_loc_and_glob()

    my_out.make_extra()

    my_out.show_loc_and_glob()
    


if __name__ == "__main__":
    main()
+0 -0

Le C, j’aime pas trop … je trouve ça "vieux". Le rust est super mais à un moment je m’étais embrouillé avec les lifetime, Box, Rc, Refcell etc.

Si tu veux te frotter au bas niveau et la programmation système, il est indéniablement utile de pouvoir au moins lire du C en raison de son ubiquité. Il n’est pas question d’écrire des codes sérieux avec, simplement de connaitre le langage dans les grandes lignes. Ça peut avoir des avantages en apprenant Rust, d’ailleurs : en comprenant mieux la montagne de problèmes associés avec les pointeurs nus (et la gestion d’erreur très fragile) en C, tu vas voir d’un autre œil les abstractions offertes par Rust et ce qu’elles apportent. Tu pourrais de fait y voir un peu plus clair et être embrouillé moins facilement.

Pour ton autre problème, considère le code suivant :

class Inner:
    pass


class Outer:
    def __init__(self):
        self.inner = Inner()
        print(locals())


if __name__ == "__main__":
    outer = Outer()
    print(outer.__dict__)
    print(dir(outer))

L’exécuter donne une sortie du genre :

{'self': <__main__.Outer object at 0x7fd6e327d3a0>}
{'inner': <__main__.Inner object at 0x7fd6e327d310>}
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'inner']

locals ne voit que les variables (les "noms" pour reprendre la nomenclature de ton tuto) définies localement. globals est la même chose mais au niveau du module au lieu du scope local. Les membres d’un objet sont situés dans son attribut __dict__. dir vise un peu plus large et sort aussi les attributs spéciaux (dont la gestion est internée par Python).

Note que le fait que la méthode __getattr__ (et __getattribute__) peuvent exécuter du code arbitraire et le mécanisme des descripteurs fait que l’interface effective d’un objet est plus large que ce que dir renvoie.

+1 -0

Au passage, faire ça :

class Outer():
    def __init__(self, msg: str, nested_obj = Inner()):
        self.msg = msg
        self.nested_obj = nested_obj

est une erreur classique de débutant (qui, bien franchement, vient d’un problème de design du langage). Il ne faut jamais mettre un mutable comme argument par défaut. Les valeurs des arguments par défaut ne sont évalués qu’une seule fois, ce qui conduit à ce genre de problèmes :

class Outer:
    def __init__(self, inner=[]):
        self.inner = inner


if __name__ == "__main__":
    outer_a = Outer()
    outer_b = Outer()
    outer_a.inner.append(3)
    print(outer_b.inner)
    print(outer_b.inner is outer_a.inner)

La façon idiomatique est d’utiliser None comme valeur par défaut et documenter le comportement :

class Outer:
    def __init__(self, inner=None):
        self.inner = inner if inner is not None else []
+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte