【问题标题】:How do I make a macro in Python?如何在 Python 中创建宏?
【发布时间】:2019-12-09 16:12:49
【问题描述】:

所以我需要在一个大型函数中进行一些重复的操作。啊哈!嵌套函数来救援!哦,但是大多数重复都涉及到对函数中局部变量的修改!啊哈!非本地的救援!但是,非本地语句几乎与嵌套函数内容一样多。我真正需要的是一个宏?

嗯。 nonlocal * (意味着所有变量都是非本地引用)会很好,然后嵌套函数可以将其所有引用都指向外部范围......但这不会仅限于外部范围,这可能会很糟糕一种通用技术。哦,是的,而且非本地 * 不存在。

怎么办?唔。代替def,编译()一些代码,稍后再执行()呢?

nestedfunc = compile("some code", "nestedfunc", "exec")

那么以后

exec( nestedfunc )

但是文档中的这个注释呢?

注意:默认局部变量的作用与以下函数 locals() 的描述相同: 不应该修改默认的本地字典 尝试过。如果您需要查看,请传递明确的本地字典 函数 exec() 返回后代码对本地人的影响。

“一些代码”确实想修改当前范围内的局部变量。这行得通吗?

不,即使是像某些代码这样的简单案例

y=y+1

证明警告的有效性:y 在以后的使用中保持不变。

嗯。如果外部函数也是一块已编译的代码,并且将本地函数传递给它怎么办?这似乎适用于粗略测试,每次从编译的外部函数调用嵌套函数时,y 的值都会增加。

a_global = 10
outer_func = compile('''
print( f'{a_global}, {y}')
exec( nested_func )
print( f'{a_global}, {y}')
exec( nested_func )
print( f'{a_global}, {y}')
exec( nested_func )
print( f'{a_global}, {y}')
exec( nested_func )
print( f'{a_global}, {y}')
''', 'outer_func', 'exec')
nested_func = compile('''
global a_global
a_global += 10
y += 1
''', 'nested_func', 'exec')

locs = {'y': 1 }
exec( outer_func, globals(), locs )
exec( outer_func, globals(), locs )
exec( outer_func, globals(), locs )

结果:

10, 1
20, 2
30, 3
40, 4
50, 5
50, 5
60, 6
70, 7
80, 8
90, 9
90, 9
100, 10
110, 11
120, 12
130, 13

所以这段代码似乎满足了要求:nested_func 可以引用和更新outer_func 中的局部变量,而不需要nonlocal 或nonlocal *,不能(除非使用nonlocal 或global)访问其他外部作用域中的变量,并且只定义在一个持续更新的地方。

不过确实很丑。有没有人看到任何漏洞,或者有更好的解决方案?

【问题讨论】:

  • 一个好的解决方案首先不使用全局变量。
  • 这太丑了。很难理解你试图达到的目标。如果您需要以某种方式保留函数的内部状态以供以后调用,请将 dict 中的本地变量返回到外部并再次将它们传回。
  • @Jean-FrançoisFabre 使用全局变量只是为了演示在nested_func 中使用全局变量或非局部变量(如果需要)的可能性,而不是建议使用全局变量。我正在探索这里的操作方法。
  • @PatrickArtner 目标是避免在大型函数中的不同位置重复出现相同代码块。这个例子不是特别有趣,只是展示了一个丑陋的技术来达到目的。代替 y += 1,替换为 2-5 行代码块,该代码块依赖并更改函数中的本地状态。代替对nested_func 的简单顺序调用,添加周围的条件逻辑。该示例只是提炼出试图避免代码重复的问题/解决方案。

标签: python-3.x


【解决方案1】:

将所需的内容作为变量字典提供给内部函数。不需要全局变量,不需要 exec。

返回更改后的状态并将其提供给进一步调用:

import random

def some_func(): 
    def senseless(d = None):
        d = d or {}                # empty dict if nothing provided
        k = d.get("k", 42)         # get "local state" from dict for variables 
        u = d.get("u", "0")        #     or use appropriate default values 
        # do something with variables                           
        for i in range(k): 
            print(u if i%2 == 0 else '?', end="")
        print()
        # mutate variable states
        k = random.randint(5,42)
        u = chr(random.randint(0,ord("z")-ord("a"))+ord("a"))
        print(f"next {k} and {u}")

        # return mutated variable states
        return {"k":k, "u":u} 

    # call with defauls, store mutated states
    state = senseless()
    # call with mutated state twice
    state = senseless(state)
    state = senseless(state)
    # call with predefined state
    state = senseless({"k":6,"u":"YeHa"}) 

some_func()

输出:

0?0?0?0?0?0?0?0?0?0?0?0?0?0?0?0?0?0?0?0?0?
next 17 and s
s?s?s?s?s?s?s?s?s
next 14 and s
s?s?s?s?s?s?s?
next 6 and o
YeHa?YeHa?YeHa?
next 26 and y

【讨论】:

  • 有趣的想法。正如您的示例中编码的那样,它几乎与激增的非本地语句一样烦人,并且可能需要更多的代码行。但是,结合stackoverflow.com/a/14620633/1265955 之类的东西,dict 可以用来代替outer_func 中的局部变量,并且可以通过nested_func 传入或非本地化,并为dict 使用像l 或lv 这样的短名称,变量引用都可以是 l.goodname 和 l.descriptivename,而无需使用庞大的 dict 语法。修改后的状态不需要返回,直接修改即可。
  • 我尝试了这种技术(连同我上面评论中的 dottable-dict 想法),并从我的大函数中分离出 4 个嵌套函数。它工作得很好。领先的lv。阅读或输入并不太烦人,它肯定会节省非本地语句,并确保非本地不会意外获取外部范围。嵌套函数(和父函数)中真正短暂的值仍然可以使用真正的局部变量。
  • 哦,我应该提到变量的 lv "dict" 只是被引用,所以它不需要传递给嵌套函数。所有感兴趣的分配都是对 lv 中的项目,并且不需要非本地引用或传递的参数即可工作。可以将这种技术用于多级静态范围,或使用传递的参数,或对外部字典的引用,以及动态范围。但是,许多这样的用例都可以通过默认范围规则和参数传递很好地处理,因此过于复杂可能并不实用。不过,它是盒子里的工具
猜你喜欢
  • 2023-04-08
  • 2013-12-01
  • 1970-01-01
  • 2021-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-20
  • 1970-01-01
相关资源
最近更新 更多