I named the design pattern "global variables that can only be accessed in with" that I sometimes see in the Python library.
I'm sorry if it already has a name.
Write a program that performs an experiment (function ʻexperiment`) based on a certain setting.
The experiment is divided into multiple functions (first`` second
), and both perform experimental operations with reference to the settings.
I want to experiment twice based on config0
and config1
.
Write it straight like this.
#There are actually more settings
config0 = {
"id": 0
}
config1 = {
"id": 1
}
def first(conf):
#Do something
print(f"{conf['id']}: first")
def second(conf):
#Do something
print(f"{conf['id']}: second")
def experiment(conf):
first(conf)
second(conf)
def main():
experiment(config0)
experiment(config1)
main()
However, with this writing method, it is a little troublesome to bucket-relay the settings when the program becomes complicated. Can't you do without it?
One solution is to use global variables ...
conf = config0
def first():
print(f"{conf['id']}: first")
def second():
print(f"{conf['id']}: second")
def experiment():
first()
second()
def main():
global conf
experiment()
conf = config1
experiment()
main()
Obviously this is crazy.
conf
is config1
after calling main
and execute main
again with the intention of config0
, it is dangerous.Since we want to avoid bucket relay and avoid introducing global variables, we will introduce the Glocal Variable pattern as an intermediate way of writing.
config.py
from contextlib import contextmanager
_config = None
_initialized = False
@contextmanager
def configure(data):
global _config
global _initialized
before = _config
before_initialized = _initialized
_config = data
_initialized = True
try:
yield
finally:
_config = before
_initialized = before_initialized
def get_config():
if _initialized:
return _config
else:
#Actually, I should throw an exception a little more seriously
raise RuntimeError
from config import get_config, configure
def first():
print(f"{get_config()['id']}: first")
def second():
print(f"{get_config()['id']}: second")
def experiment():
first()
second()
def main():
with configure(config0):
experiment()
with configure(config1):
experiment()
main()
configure
, so there are limits to the degrees of freedom.get_config
other than" scope "(in with), the error cannot be picked up by static analysis.in this way,
Let's call the pattern ** Glocal Variable **.
It is used in some Python libraries.
With Racket, there is a syntax called parametrize
, which provides general-purpose Glocal Variable functionality.
The initial value can also be determined in advance. In mxnet mentioned earlier, the calculation on the CPU is the default value.
If you also have a setter, you can change the Glocal Variable in with.
param.py
_param = None
_initialized = False
@contextmanager
def parametrize(data):
global _param
global _initialized
before = _param
before_initialized = _initialized
_param = data
_initialized = True
try:
yield
finally:
_param = before
_initialized = before_initialized
def set_param(data):
if _initialized:
global _param
_param = data
else:
raise RuntimeError
def get_param():
if _initialized:
return _param
else:
raise RuntimeError
from param import parametrize, set_param, get_param
with parametrize(3):
with parametrize(4):
print(get_param())
set_param(2)
print(get_param())
print(get_param())
get_param()
# 4
# 2
# 3
# RuntimeError
Compared to the case where only reading is possible, the risk is higher because of the effort required to track the state.
However, the effect of state changes can be limited to within with, so it is safer than global variables.
When writing a parser etc., it may be convenient to write "sentences that have not been read yet" as Glocal Variable and gradually consume from the beginning so that you can write without a bucket relay.
Note that the value of Glocal Variable is determined when the getter is ** executed, not where it is ** described **.
def get_print_config():
#Not this 2
with configure(2):
def print_config():
print(get_config())
return print_config
print_config = get_print_config()
#This 3 is referenced
with configure(3):
print_config()
# 3
Originally, I thought that I could write it in Python as if it were a state monad do notation, so I remembered flask and mxnet and realized that there was such a pattern.
Recommended Posts