начать с:
def __getitem__(self, key):
try:
return super().__getitem__(key)
except KeyError:
...
Но это не позволит вам получить глобальные переменные за пределами класс корпус. Вы также можете использовать __missin__
метод, который зарезервирован именно для подклассов Dict:
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
должен быть классом, разработанным в смартфоне, так что его значения всегда лениво оцениваются, и он ведет себя так же, как в подходе «реактивный программирование»: то есть: обновление значения будет каскадным обновлением в все остальные значения, которые зависят от него. Я думаю, что полная реализация рабочего «реактивного» класса FOrwardDeclaration падает с точки зрения этого вопроса. - У меня есть код игрушек, чтобы сделать это на github на https://github.com/jsbueno/python-react.
Даже при правильном «Реактивном» 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 реализовать полностью реактивный знает класс - вы можете разрешить все ожидающие FOrwardDependencies на __new__
методе метакласса - (так что ваши объекты 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()
И, в зависимости от вашей иерархии классов, и что именно вы делаете, rememebr также обновить один класс Словарь _forward_dependencies
с _forward_dependencies
созданного на его предок. И если вам нужен какой-либо оператор, отличный от +
, как вы уже отметили, вам нужно будет хранить информацию о самом операторе - на данный момент, возможно, также будет использовать sympy
.
Не могли бы вы устранить это, используя аргументы по умолчанию (атрибуты в этом случае)? – Rockybilly
Если я не понял вас, это не сработает, потому что я не буду знать, когда я определяю «MyBaseClass», какие атрибуты «MyClass» будут. Если бы я это сделал, вы были бы правы; Я мог бы просто объявить их как 'd = ForwardDeclaration()' в базовом классе. – drhagen
Это звучит смешно отвратительно! Отлично сработано! Неясно, какова конкретная проблема/вопрос? –