2014-05-15 4 views
1

Après avoir expérimenté en essayant de mettre en œuvre des C pour boucles en Python, la fonction suivante a été développée:Pourquoi mes sections locales ne sont-elles pas mises à jour en dehors de rof?

import sys 

def rof(init, cond, post): 
    init, cond, post, context = compile(init, '<rof>', 'exec'), \ 
           compile(cond, '<rof>', 'eval'), \ 
           compile(post, '<rof>', 'exec'), \ 
           sys._getframe(1) 
    context = context.f_globals, context.f_locals 
    exec(init, *context) 
    while eval(cond, *context): 
     yield None 
     exec(post, *context) 

Comme tout programmeur sait, la nouvelle fonction nécessaire à tester pour vous assurer qu'il fonctionne:

Configuration

class Employee: 

    def __init__(self, employee_id, category, hired, salary, years): 
     vars(self).update(locals()) 

    def __repr__(self): 
     return '{}({})'.format(self.__class__.__name__, 
           ', '.join(map(repr, self))) 

    def __iter__(self): 
     yield self.employee_id 
     yield self.category 
     yield self.hired 
     yield self.salary 
     yield self.years 

database = [Employee(123, 'P', 2014, 2000, 0), 
      Employee(234, 'F', 2000, 20000, 14), 
      Employee(123, 'F', 2010, 10000, 4)] 

le code fonctionne sans erreurs dans certains cas (comme celui ci-dessous):

Essai 1

for _ in rof('a = 0', 'a < len(database)', 'a += 1'): 
    employee_id = database[a].employee_id 
    for _ in rof('b = len(database) - 1', 'b > a', 'b -= 1'): 
     if database[b].employee_id == employee_id: 
      print(database[b], 'is being removed.') 
      del database[b] 

Cependant, cela ne fonctionne pas lorsque les boucles sont dans une fonction séparée.

Essai 2

def remove_duplicates(database): 
    a = b = int 
    for _ in rof('a = 0', 'a < len(database)', 'a += 1'): 
     employee_id = database[a].employee_id 
     for _ in rof('b = len(database) - 1', 'b > a', 'b -= 1'): 
      if database[b].employee_id == employee_id: 
       print(database[b], 'is being removed.') 
       del database[b] 

remove_duplicates(database) 

Une erreur est generatated place (TypeError: list indices must be integers, not type).


Nous pouvons tous convenir que ce code à ne pas Pythonic, mais quelqu'un peut-il identifier ce qui est à l'origine du problème et comment y remédier?

+0

Puis-je demander pourquoi essayez-vous exactement de réaliser ce genre de comportement? –

+0

Veuillez inclure le retraçage complet. – dano

+2

Qu'essayez-vous d'accomplir avec 'a = b = int'? Je suis sûr que c'est la cause première. –

Répondre

3

En Python 3, il n'est pas possible de créer de nouvelles variables locales dans locals() car l'ensemble des variables locales est déduit lors de la compilation. Surtout si vous modifiez remove_duplicates pour qu'il n'ait pas la ligne a = b = int, Python ne considère pas ces noms comme faisant référence à une variable locale mais globale. Avec la présence de cette ligne, ils sont considérés comme une variable locale, oui.

De plus, il n'est pas possible de changer les locals via l'objet frame, car dans Python 3, les variables locales ne sont plus stockées dans un dictionnaire. Au lieu de cela, sur CPython 3, l'accès frame.f_locals crée une copie des variables en utilisant PyFrame_FastToLocals, mais il s'agit normalement d'un trajet à sens unique. Ainsi, alors que vous pouvez lire les valeurs des variables, aucune modification ne sera propagée, et a et b continueront à is int. Cependant (module) les variables globales sont toujours stockées dans un dictionnaire qui est directement accessible par le frame.f_globals; et le dictionnaire est ouvert aux changements.

Cependant, il y a a blog post par le responsable du PyDev sur la façon d'y parvenir sur CPython 3. Ainsi, la rof mise en œuvre suivante semble faire l'affaire pour moi:

def apply(frame): 
    ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame), ctypes.c_int(0)) 

def rof(init, cond, post): 
    init, cond, post, context = compile(init, '<rof>', 'exec'), \ 
           compile(cond, '<rof>', 'eval'), \ 
           compile(post, '<rof>', 'exec'), \ 
           sys._getframe(1) 

    exec(init, context.f_globals, context.f_locals) 
    apply(context) 
    while eval(cond, context.f_globals, context.f_locals): 
     apply(context) 
     yield None 
     exec(post, context.f_globals, context.f_locals) 
     apply(context) 

Je pense que ce code est en est une abomination, et recommande que, au lieu de cela, les programmeurs hypothétiques sachent comment transformer une boucle C for en une boucle C while ... et la transforment en Python à partir de là. Et cela ne peut toujours pas fonctionner sans donner de valeur initiale à ces variables dans le corps de la fonction.

Ainsi, je propose une implémentation rof autre:

def rof(init, cond, post): 
    print(init) 
    print('while {}:'.format(cond)) 
    print(' # code goes here') 
    print(' ' + post) 

rof('b = len(database) - 1', 'b > a', 'b -= 1') 

impressions:

b = len(database) - 1 
while b > a: 
    # code goes here 
    b -= 1 

qui est ce qui doit être écrit de toute façon.

bien qu'il n'y ait pas beaucoup de mal dans ce cas:

for a in range(len(database)): 
    for b in range(len(database) - 1, a, -1): 
     ... 
+0

'liste (plage (10, 20, -1))' -> '[]' –

Questions connexes