隐藏关闭环境

时间:2019-03-06 03:26:09

标签: python python-3.x closures

我正在观看此视频Object-Oriented Programming is Bad。该主题与我要问的问题并没有真正的联系,但是视频引导了它。恰恰在41:10,他谈到了python中似乎不存在的功能,他谈到了具有嵌套函数/块的能力,这些函数/块不了解完整的本地上下文,而是知道手动选择一些变量。基本上,他要求能够从封闭中删除/部分隐藏环境。

以下是所需功能在python中的外观(例如,使用use装饰器):

def foo():
  x, y, z = 1, 2, 3

  def baz():
    print(x, y)  # prints "1 2"

  @use(y)
  def bar():
    print(y)  # Should print "2"
    z = 4     # Should create a local variable named `z`
    print(x)  # Should raise a NameError (`x` not defined)

  bar(), baz())

foo()

我尝试实现该装饰器,但没有成功,因此我尝试了一个更简单的目标,即不完全隐藏环境,而是完全隐藏环境。这是我找到的解决方案:

import ctypes

def foo():
  x, z = 1

  def baz():
    print(x)  # prints 1

  @hide_environment
  def bar(y):
    print(y)  # prints 2
    z = 3
    print(z)  # prints 3
    print(x)  # NameError is raised

  baz(), bar(2)

def hide_environment(f):
  def environment_agnostic_f(*args, **kwargs):
    remove_environment(f)
    return f(*args, **kwargs)
  return environment_agnostic_f

def remove_environment(f):
  if f.__closure__ is None: 
    return
  for v in f.__closure__:
    py_obj = ctypes.py_object(v)
    ctypes.pythonapi.PyCell_Set(py_obj, None)  # that look like it could go wrong

foo()

我有四个问题:

  1. 它已经存在于python中吗?
  2. 是否可以实现视频中描述的use机制(在python中)?
  3. 我对hide_environment的实现正确吗?

我不知道2.怎么可能,因为我不知道是否有可能截获/找到y值所在的单元格地址。我不确定是如何处理的,但是据我了解,当读取foo时,“编译器”(不确定是否应该这样调用)会将所有{{1}来自locals对象中foo的},以后将由__closure__bar引用。这导致了我的最后一个问题:

  1. 文档中是否有任何地方描述了关闭的处理方式?

1 个答案:

答案 0 :(得分:0)

这不是Python附带的,但是实现它的最简单方法是只检查函数使用了什么,如果使用过多则抛出异常:

def allow_closure_vars(*okay_names):
    def checker(f):
        used_names = set(f.__code__.co_freevars)
        bad_names = used_names - set(okay_names)
        if bad_names:
            raise ValueError(f"Function uses the following variables it shouldn't: {bad_names}")
        return f
    return checker

def f():
    x, y = 1, 2
    @allow_closure_vars('x')
    def g():
        print(y)
    g()

f()

尝试定义g将引发ValueError

您的实现实际上并未禁止闭包变量访问。它只是取消分配变量,就像del语句一样。该函数仍然可以访问变量,但尚未分配。这很可能与外部函数有关,该外部函数可能并不期望未分配这些变量,并且不会阻止任何代码为变量分配新值。