为什么我的当地人没有在rof之外更新?

时间:2014-05-15 20:18:45

标签: python c for-loop python-3.x

尝试在Python中实现C-for循环后,开发了以下函数:

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)

正如任何程序员所知,新功能需要进行测试以确保其有效:

设置

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)]

在某些情况下(例如下面的代码),代码运行时没有错误:

试用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]

但是,当循环处于单独的函数中时,它不起作用。

试用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)

改为生成错误(TypeError: list indices must be integers, not type)。


我们都同意这个代码不是Pythonic,但任何人都可以确定导致问题的原因以及如何修复它?

1 个答案:

答案 0 :(得分:3)

在Python 3中,无法在locals()中创建新的局部变量,因为在编译时会扣除一组局部变量。特别是如果您修改remove_duplicates以使其没有a = b = int行,则Python不会将这些名称视为本地变量而是全局变量。有了该行的存在,它们被认为是一个局部变量,是的。

此外,通过框架对象无法更改局部变量,因为在Python 3中,局部变量不再存储在字典中。相反,在CPython 3上,frame.f_locals访问使用PyFrame_FastToLocals创建变量的副本,但它通常是单向行程。因此,虽然您可以读取变量的值,但不会传播任何更改,ab会继续is int。但是(模块)全局变量仍存储在可通过frame.f_globals直接访问的字典中;这本词典可供更改。

然而,PyDev维护者有a blog post如何在CPython 3上实现这一点。因此,以下rof实现似乎为我做了 :< / p>

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)

我认为这是代码是令人厌恶的,如果有的话,并建议不是这样,假设的程序员将知道如何将C for循环更改为C while循环...并从那里将其工作到Python。如果没有在函数体内为这些变量赋予初始值,它仍然无法工作。

因此,我提出了另一种rof实施方案:

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')

打印:

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

无论如何都应该写出来。

虽然在这种情况下没有太大的错误:

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