我有很多函数使用一些全局变量来保存要在整个库中重用的对象,例如:
from some.other.lib import Object1, Object2, Object3, Object4, Object5
def function1(input):
global _object1
try:
_object1
except NameError:
_object1 = Object1
return _object1.func1(input)
def function2(input):
global _object2
try:
_object2
except NameError:
_object2 = Object2
# Use function1 to do something to the input
return _object2.func2(function1(input))
def function3(input):
global _object3
try:
_object3
except NameError:
_object3 = Object3
# Use function1 to do something to the input.
return _object3.func3(function1(input))
def function4(input):
global _object4
try:
_object4
except NameError:
_object4 = Object4
return _object4.func4(input)
def function5(input):
global _object5
try:
_object5
except NameError:
_object5 = Object5
# Use function4 to do something to the input.
return _object5.func5(function4(input))
有没有办法编写一个装饰器,使得检查全局变量的代码不需要为每个function*
手动硬编码?
例如,像这样:
def exist_decorator(_object, _class):
try:
_object
except NameError:
_object = _class
return _object
因此,function4()
的实际类实现将如下所示:
def function4(input):
global _object4
exist_decorator(_object4, Object4)
return _object4.func4(input)
在带有装饰器的 Python 中,这样的东西在语法上是可能的吗?如果没有,我还能如何创建用于检查全局变量的装饰器/缓存?
我猜你只是想缓存一些值。 为此,在不滥用任何隐藏功能的情况下,体面的 Python 中的直接方法是:
cache = {}
def function1(input):
try:
object1 = cache['object1']
except KeyError:
object1 = cache['object1'] = Object1
return object1.func1(input)
其他功能也类似。
您还可以通过在函数对象中存储所有内容来避免使用仍然全局变量cache
:
def function1(input):
try:
object1 = function1.object1
except AttributeError:
object1 = function1.object1 = Object1
return object1.func1(input)
这是可能的,因为函数是可以向其添加属性的任意对象。 但有些人可能会称之为滥用和不干净的代码。 与往常一样,在这种情况下,请先与您的团队讨论此问题,并在使用它之前将其添加到该团队或项目的已用技术列表中。
我更喜欢使用可变参数的隐藏功能:
def function(input, object1Cache=[None]):
if object1Cache[0] is None:
object1Cache[0] = Object1
return object1Cache[0].func1(input)
这在 Python 中有效,因为作为函数参数默认值的列表仍然是可变的,并且将保留其值。
同样,这可能被认为是不干净和滥用的,因此请在使用它之前与您的团队讨论此问题,并在您的项目中记录使用这种技术。
我更喜欢使用缓存的工厂函数,并将它们作为依赖于它们的函数的默认参数提供。这消除了全局变量并坚持开放/封闭原则。
my_objects.py
from functools import cache
class Object1:
def __init__(self, a='foo', b='bar'):
self.a = a
self.b = b
@cache
def get_object1():
return Object1()
my_function.py
from my_app.my_objects import get_object1
def function1(dependency_factory=get_object1):
depends_on = dependency_factory()
print(depends_on.a, depends_on.b)
一种更干净的方法是创建一个装饰器函数来传递实例化的依赖项,即依赖项注入。
depends_on.py
def depends_on(**factories):
def outer(func):
def wrapper(*args, **kwargs):
for name, factory in factories.items():
kwargs[name] = factory() # instantiate object by calling factory function or static method
return func(*args, **kwargs)
return wrapper
return outer
my_db.py
from functools import cache
class DB:
def save(self, x):
if x == 'oops':
raise ValueError(f'DB tried to save invalid value: {x}')
print(f'{x} saved to db')
@cache
def get_db():
return DB()
my_logger.py
from functools import cache
class Logger:
def info(self, x):
print(f'INFO: {x}')
@cache
def get_logger():
return Logger()
save_and_log.py
from my_app.depends_on import depends_on
from my_app.my_db import get_db
from my_app.my_logger import get_logger
@depends_on(db=get_db, logger=get_logger)
def save_and_log(thing_to_save, db=None, logger=None):
try:
db.save(thing_to_save)
except ValueError as e:
logger.info(e)
if __name__ == '__main__':
save_and_log('some persistent data')
# outputs "some persistent data saved to db"
save_and_log('oops')
# outputs "INFO: DB tried to save invalid value: oops"