我們談『邏輯編程』與『 pyDatalog』語言的點點滴滴,轉眼之間已過了一個月。希望讀者得以自己繼續探索這個『典範』,有如
《打開黑箱!!》文本所說的︰
【頭】
科學追求真理,為的是打開大自然的黑箱;然而真理明白若昭,就是透明的白箱。我們總在求真的旅途上一知半解,努力化灰箱為白箱。如果偵錯就是科學,除錯即求真理,那這一段話用在『偵錯』與『除錯』上來講依然合適。這也說明為什麼人們喜歡用不同的灰度,來表達對『箱內之物』的認識與了解了。
【尾】
如何打開黑箱?讓我們歸結到胡適之先生的兩句名言︰作學問要,
大膽假設,小心求證。而讀書要,
於不疑處有疑,於有疑處不疑。
有頭有尾貫徹始終。
開放原始碼的最大好處,從學習上來講,就是不只能『自外向內』推敲,還可以『打開黑箱』,『由內往外』研究。因此幾行原始碼 ,足以知道『 pyDatalog.Mixin 』的來歷︰
class metaMixin(type):
“””Metaclass used to define the behavior of a subclass of Mixin”””
# following syntax to declare Mixin is used for compatibility with python 2 and 3
Mixin = metaMixin(‘Mixin’, (object,), {})
#When creating a Mixin object without SQLAlchemy, add it to the list of instances, so that it can be included in the result of queries
def __init__(self): if not self.__class__.has_SQLAlchemy: for cls in self.__class__.__mro__: if cls.__name__ in pyEngine.Class_dict and cls not in (Mixin, object): metaMixin.__refs__[cls].add(self)
Mixin.__init__ = __init__
“”” ****************** support for SQLAlchemy ***************** “””
class sqlMetaMixin(metaMixin, DeclarativeMeta):
“”” metaclass to be used with Mixin for SQLAlchemy”””
pass
“”” attach a method to SQLAlchemy class.attribute,
so that it can answer queries like class.attribute[X]==Y”””
def InstrumentedAttribute_getitem(self, *args): cls = self.class_ method = self.key return type(cls).__getattr__(cls, method).__getitem__(*args) InstrumentedAttribute.__getitem__ = InstrumentedAttribute_getitem
細讀之後可以論斷
2 Object-oriented Datalog
- that the class inherits from pyDatalog.Mixin
- that the predicate is prefixed by the class name, e.g.
( Employee.salary[X] == Y )
.
之宣稱。如是習學將能事半功倍,如虎添翼的耶!
《 pyDatalog.py 》
""" ****************** python Mixin ***************** """ class metaMixin(type): """Metaclass used to define the behavior of a subclass of Mixin""" __refs__ = defaultdict(weakref.WeakSet) def __init__(cls, name, bases, dct): """when creating a subclass of Mixin, save the subclass in Class_dict. """ super(metaMixin, cls).__init__(name, bases, dct) pyEngine.add_class(cls, name) cls.has_SQLAlchemy = any(base.__module__ in ('sqlalchemy.ext.declarative', 'sqlalchemy.ext.declarative.api') for base in bases) def _getattr(self, attribute): """ responds to instance.method by asking datalog engine """ if not attribute == '__iter__' and not attribute.startswith('_sa_'): predicate_name = "%s.%s[1]==" % (self.__class__.__name__, attribute) terms = (pyParser.Term('_pyD_class', forced_type='constant'), self, pyParser.Term("X")) #prefixed literal = pyParser.Literal.make(predicate_name, terms) #TODO predicate_name[:-2] result = literal.lua.ask() return result[0][-1] if result else None raise AttributeError cls.__getattr__ = _getattr def __lt__(self, other): # needed for sorting in aggregate functions using Python 3 return id(self) < id(other) cls.__lt__ = __lt__ def __getattr__(cls, method): """ when access to an attribute of a subclass of Mixin fails, return an object that responds to () and to [] """ if cls in ('Mixin', 'metaMixin') or method in ( '__mapper_cls__', '_decl_class_registry', '__sa_instrumentation_manager__', '_sa_instance_state', '_sa_decl_prepare', '__table_cls__', '_pyD_query'): raise AttributeError return pyParser.Term("%s.%s" % (cls.__name__, method)) def pyDatalog_search(cls, literal): """Called by pyEngine to resolve a prefixed literal for a subclass of Mixin.""" terms = literal.terms attr_name = literal.pred.suffix operator = literal.pred.name.split(']')[1] # what's after ']' or None # TODO check prearity def check_attribute(X): if attr_name not in X.__dict__ and attr_name not in cls.__dict__: raise AttributeError("%s does not have %s attribute" % (cls.__name__, attr_name)) if len(terms)==3: #prefixed X, Y = terms[1], terms[2] if X.is_const(): # try accessing the attribute of the first term in literal check_attribute(X.id) Y1 = getattr(X.id, attr_name) if not Y.is_const() or not operator or pyEngine.compare(Y1,operator,Y.id): yield (terms[0], X.id, Y.id if Y.is_const() else Y1 if operator=='==' else None) elif cls.has_SQLAlchemy: if cls.session: q = cls.session.query(cls) check_attribute(cls) if Y.is_const(): q = q.filter(pyEngine.compare(getattr(cls, attr_name), operator, Y.id)) for r in q: Y1 = getattr(r, attr_name) if not Y.is_const() or not operator or pyEngine.compare(Y1,operator,Y.id): yield (terms[0], r, Y.id if Y.is_const() else Y1 if operator=='==' else None) else: # python object with Mixin for X in metaMixin.__refs__[cls]: check_attribute(X) Y1 = getattr(X, attr_name) if not Y.is_const() or not operator or pyEngine.compare(Y1,operator,Y.id): yield (terms[0], X, Y.id if Y.is_const() else Y1 if operator=='==' else None) return else: raise AttributeError ("%s could not be resolved" % literal.pred.name) # following syntax to declare Mixin is used for compatibility with python 2 and 3 Mixin = metaMixin('Mixin', (object,), {}) #When creating a Mixin object without SQLAlchemy, add it to the list of instances, #so that it can be included in the result of queries def __init__(self): if not self.__class__.has_SQLAlchemy: for cls in self.__class__.__mro__: if cls.__name__ in pyEngine.Class_dict and cls not in (Mixin, object): metaMixin.__refs__[cls].add(self) Mixin.__init__ = __init__ """ ****************** support for SQLAlchemy ***************** """ class sqlMetaMixin(metaMixin, DeclarativeMeta): """ metaclass to be used with Mixin for SQLAlchemy""" pass """ attach a method to SQLAlchemy class.attribute, so that it can answer queries like class.attribute[X]==Y""" def InstrumentedAttribute_getitem(self, *args): cls = self.class_ method = self.key return type(cls).__getattr__(cls, method).__getitem__(*args) InstrumentedAttribute.__getitem__ = InstrumentedAttribute_getitem
就讓我們再踏征途,勇闖此
美麗新世界 Brave New World
其它未知之境的吧!!