在 Hy 中使用 let 进行动态绑定?

发布于 2025-01-16 20:34:16 字数 875 浏览 2 评论 0原文

来自 Common Lisp,我尝试使用 let 动态地隐藏全局变量的值。

(setv glob 18)

(defn callee []
  (print glob))

(defn nonl [x]
  (callee)
  (let [glob x]
    (callee))
  (callee))

(nonl 39)

=>
18
18
18

有没有办法让这项工作正常进行,以便对被调用者的第二次调用给出 39?

[编辑]

根据 gilch 的回复,我使用 contextvars 编写了以下草稿:

(import contextvars :as cv)

(defmacro defparam [symbol value]
  (let [command (. f"{symbol} = cv.ContextVar('{symbol}')")]
    `(do (exec ~command)
         (.set ~symbol ~value))))

(defmacro parameterize [symbol value #* body]
  `(. (cv.copy-context)
      (run (fn [] (do (.set ~symbol ~value)
                      ~@body)))))

(defn callee []
  (glob.get))

;;;;;;;;;

(defparam glob 18)

(callee)                  => 18
(parameterize glob 39
  (callee))               => 39
(callee)                  => 18

感谢您的答案!

Comming from Common Lisp, I'm trying to use let to shadow the value of a global variable dynamically.

(setv glob 18)

(defn callee []
  (print glob))

(defn nonl [x]
  (callee)
  (let [glob x]
    (callee))
  (callee))

(nonl 39)

=>
18
18
18

Is there a way to make this work so that the second call to callee gives 39?

[EDIT]

Based on gilch's response i wrote the following draft using contextvars:

(import contextvars :as cv)

(defmacro defparam [symbol value]
  (let [command (. f"{symbol} = cv.ContextVar('{symbol}')")]
    `(do (exec ~command)
         (.set ~symbol ~value))))

(defmacro parameterize [symbol value #* body]
  `(. (cv.copy-context)
      (run (fn [] (do (.set ~symbol ~value)
                      ~@body)))))

(defn callee []
  (glob.get))

;;;;;;;;;

(defparam glob 18)

(callee)                  => 18
(parameterize glob 39
  (callee))               => 39
(callee)                  => 18

Thanks for the answers!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

葬シ愛 2025-01-23 20:34:16

Python 中没有内置方法可以为全局变量提供动态范围的临时值(并且 Hy 不会为其添加宏或任何内容),因此您必须自己执行此操作:

(setv glob 18)

(defn callee []
  (print glob))

(defn nonl [x]
  (callee)
  (global glob)
  (setv old-glob glob)
  (try
    (setv glob x)
    (callee)
    (finally
      (setv glob old-glob)))
  (callee))

(nonl 39)

为此的宏可能如下所示

(defmacro dyn-rebind-global [symbol new-value #* body]
  (setv old-value (hy.gensym))
  `(do
    (global ~symbol)
    (setv ~old-value ~symbol)
    (try
      (setv ~symbol ~new-value)
      ~@body
      (finally
        (setv ~symbol ~old-value)))))

:你可以这样使用它:

(setv glob 18)

(defn callee []
  (print glob))

(defn nonl [x]
  (callee)
  (dyn-rebind-global glob x
    (callee))
  (callee))

(nonl 39)

There's no built-in way to give a global variable a dynamically scoped temporary value in Python (and Hy doesn't add a macro for it or anything), so you have to do it yourself:

(setv glob 18)

(defn callee []
  (print glob))

(defn nonl [x]
  (callee)
  (global glob)
  (setv old-glob glob)
  (try
    (setv glob x)
    (callee)
    (finally
      (setv glob old-glob)))
  (callee))

(nonl 39)

A macro for this might look like:

(defmacro dyn-rebind-global [symbol new-value #* body]
  (setv old-value (hy.gensym))
  `(do
    (global ~symbol)
    (setv ~old-value ~symbol)
    (try
      (setv ~symbol ~new-value)
      ~@body
      (finally
        (setv ~symbol ~old-value)))))

Then you could use it like this:

(setv glob 18)

(defn callee []
  (print glob))

(defn nonl [x]
  (callee)
  (dyn-rebind-global glob x
    (callee))
  (callee))

(nonl 39)
恬淡成诗 2025-01-23 20:34:16

动态绑定最接近的 Python 等价物是 contextvars unittest.mock.patch

patch 主要用于单元测试,几乎适用于任何东西,但不是线程安全的。如果您需要动态地重新绑定库代码中的某些内容,patch 可以做到。用作上下文管理器或装饰器。

>>> from unittest.mock import patch
>>> name = 'Alice'
>>> def greet(): print("Hi", name)
...
>>> greet()
Hi Alice
>>> with patch('__main__.name', 'Bob'):
...     greet()
...
Hi Bob
>>> greet()
Hi Alice

使用 contextvars 时,必须首先在模块顶层将其设置为 ContextVar,并且必须使用 .get()< 显式取消引用/code> 调用,但它在线程和异步代码中正常工作。

如果您想在 Python 中声明自己的动态变量,最好在可以使用异步或线程时将其设为上下文变量。异步任务管理器将自动为每个任务交换新的上下文。您还可以使用 copy_context 手动执行此操作。 run() 方法接受一个可调用函数,因此它可以用作装饰器:

>>> import contextvars as cv
>>> name = cv.ContextVar('name', default='Alice')
>>> def greet(): print('Hi', name.get())
...
>>> greet()
Hi Alice
>>> @cv.copy_context().run
... def _():
...   name.set('Bob')
...   greet()
...
Hi Bob
>>> greet()
Hi Alice

这在 Hy 中可以以完全相同的方式工作,但宏可以使其更好。

The nearest Python equivalents of dynamic bindings are contextvars and unittest.mock.patch.

patch is mainly intended for unit testing and works on pretty much anything, but is not thread safe. If you need to dynamically rebind something in library code, patch can do it. Use as a context manager or decorator.

>>> from unittest.mock import patch
>>> name = 'Alice'
>>> def greet(): print("Hi", name)
...
>>> greet()
Hi Alice
>>> with patch('__main__.name', 'Bob'):
...     greet()
...
Hi Bob
>>> greet()
Hi Alice

With contextvars it has to be set up as a ContextVar in the first place at the module top level, and has to be explicitly dereferenced with .get() calls, but it works properly in threaded and async code.

If you want to declare your own dynamic variables in Python, it's best to make it a context var when async or threading is a possibility. An asyncio task manager will automatically swap in a new context for each task. You can also do this manually with copy_context. The run() method takes a callable, so it can work as a decorator:

>>> import contextvars as cv
>>> name = cv.ContextVar('name', default='Alice')
>>> def greet(): print('Hi', name.get())
...
>>> greet()
Hi Alice
>>> @cv.copy_context().run
... def _():
...   name.set('Bob')
...   greet()
...
Hi Bob
>>> greet()
Hi Alice

This can all work exactly the same way in Hy, but a macro could make it nicer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文