勇闖新世界︰ 《 pyDatalog 》【專題】之物件導向設計‧五

我們談『邏輯編程』與『 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

This fact is stored in pyDatalog’s knowledge base (not in the Employee object).  All the principles explained in Tutorial 1 apply to such predicates whose terms are python objects.
Instead of asserting facts in the pyDatalog knowledge base, it is often more efficient for logic predicates to directly refer to the attribute of an object.  For example, we may want to have a clause concerning the salary attribute of Employee instances.  This requires 2 things:
  1. that the class inherits from pyDatalog.Mixin
  2. 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

其它未知之境的吧!!