我想做一些明确无声的事情。我想创建一个允许其类属性的前向声明的类。 (如果你必须知道,我正试图为解析器组合器制作一些甜蜜的语法。)
这是我想要做的事情:
a = 1
class MyClass(MyBaseClass):
b = a # Refers to something outside the class
c = d + b # Here's a forward declaration to 'd'
d = 1 # Declaration resolved
我目前的方向是创建一个元类,以便在找不到d
时捕获NameError
异常并返回一个虚拟类的实例,我将调用ForwardDeclaration
。我从AutoEnum
中获取了一些灵感,它使用元类魔法来声明带有裸标识符的枚举值,而不是赋值。
以下是我到目前为止的情况。缺少的部分是:如何继续正常的名称解析并捕获NameError
s:
class MetaDict(dict):
def __init__(self):
self._forward_declarations = dict()
def __getitem__(self, key):
try:
### WHAT DO I PUT HERE ??? ###
# How do I continue name resolution to see if the
# name already exists is the scope of the class
except NameError:
if key in self._forward_declarations:
return self._forward_declarations[key]
else:
new_forward_declaration = ForwardDeclaration()
self._forward_declarations[key] = new_forward_declaration
return new_forward_declaration
class MyMeta(type):
def __prepare__(mcs, name, bases):
return MetaDict()
class MyBaseClass(metaclass=MyMeta):
pass
class ForwardDeclaration:
# Minimal behavior
def __init__(self, value=0):
self.value = value
def __add__(self, other):
return ForwardDeclaration(self.value + other)
答案 0 :(得分:1)
首先:
def __getitem__(self, key):
try:
return super().__getitem__(key)
except KeyError:
...
但是这不允许你在类体外检索全局变量。
您还可以使用完全为dict的子类保留的__missin__
方法:
class MetaDict(dict):
def __init__(self):
self._forward_declarations = dict()
# Just leave __getitem__ as it is on "dict"
def __missing__(self, key):
if key in self._forward_declarations:
return self._forward_declarations[key]
else:
new_forward_declaration = ForwardDeclaration()
self._forward_declarations[key] = new_forward_declaration
return new_forward_declaration
正如你所看到的,那不是“UnPythonic” - 高级Python的东西,比如SymPy和SQLAlchemy必须采用这种行为来做他们的好魔法 - 只要确保得到很好的记录和测试。
现在,为了允许全局(模块)变量,你需要一点点 - 并且可能在所有Python实现中可能无法实现的东西 - 即:内省类主体所在的框架得到它的全局:
import sys
...
class MetaDict(dict):
def __init__(self):
self._forward_declarations = dict()
# Just leave __getitem__ as it is on "dict"
def __missing__(self, key):
class_body_globals = sys._getframe().f_back.f_globals
if key in class_body_globals:
return class_body_globals[key]
if key in self._forward_declarations:
return self._forward_declarations[key]
else:
new_forward_declaration = ForwardDeclaration()
self._forward_declarations[key] = new_forward_declaration
return new_forward_declaration
现在你在这里 - 你的特殊字典足以避免NameErrors,但你的ForwardDeclaration
对象远远不够聪明 - 运行时:
a = 1
class MyClass(MyBaseClass):
b = a # Refers to something outside the class
c = d + b # Here's a forward declaration to 'd'
d = 1
c
成为ForwardDeclaration
个对象,但总结为d
的即时值为零。在下一行,d
只是用值1
覆盖,不再是惰性对象。所以你也可以声明c = 0 + b
。
为了解决这个问题,ForwardDeclaration
必须是在smartway中设计的类,因此它的值总是被懒惰地评估,并且它的行为与“反应式编程”方法一样:即:更新值将级联更新到依赖于它的所有其他值。我认为给你一个完整的实现“反应”意识的FOrwardDeclaration类不属于这个问题的范围。 - 不过我在https://github.com/jsbueno/python-react的github上有一些玩具代码。
即使使用正确的“Reactive”ForwardDeclaration类,您也必须再次修复字典,以便d = 1
类起作用:
class MetaDict(dict):
def __init__(self):
self._forward_declarations = dict()
def __setitem__(self, key, value):
if key in self._forward_declarations:
self._forward_declations[key] = value
# Trigger your reactive update here if your approach is not
# automatic
return None
return super().__setitem__(key, value)
def __missing__(self, key):
# as above
最后,有一种方法可以避免havign实现完全反应的感知类 - 您可以解析元类的__new__
方法上的所有挂起的FOrwardDependencies - (以便您的ForwardDeclaration对象被手动“冻结”)在课堂创作时,不再担心 - )
一些事情:
from functools import reduce
sentinel = object()
class ForwardDeclaration:
# Minimal behavior
def __init__(self, value=sentinel, dependencies=None):
self.dependencies = dependencies or []
self.value = value
def __add__(self, other):
if isinstance(other, ForwardDeclaration):
return ForwardDeclaration(dependencies=self.dependencies + [self])
return ForwardDeclaration(self.value + other)
class MyMeta(type):
def __new__(metacls, name, bases, attrs):
for key, value in list(attrs.items()):
if not isinstance(value, ForwardDeclaration): continue
if any(v.value is sentinel for v in value.dependencies): continue
attrs[key] = reduce(lambda a, b: a + b.value, value.dependencies, 0)
return super().__new__(metacls, name, bases, attrs)
def __prepare__(mcs, name, bases):
return MetaDict()
而且,根据您的类层次结构以及您正在做什么,请记住还要使用在其祖先上创建的_forward_dependencies
更新一个类'dict _forward_dependencies
。
如果你需要+
以外的任何算子,你将会注意到,你必须保留关于算子本身的信息 - 此时,侯也可以使用sympy
。