fc rules and objects - new requirements

78 views
Skip to first unread message

egon.w...@siemens.com

unread,
Aug 18, 2008, 9:30:16 AM8/18/08
to PyKE
I came across the following when working with fc rules and objects. I
would like to come up with something like a solution approach. But
there
are still some open question I would like to discuss.

Please consider the following example from the features and
application
configuration stuff I'm currently working on in the context of
software product lines.
I haven't got it working until now since I just translated these rules
from prolog in
order to identify any new probable requirements to pyke.

each_script_engine_requires_xml_component_entry
foreach
$feat in Feature.Instances

# $feat.Selected = True does not work,
# specifying a kb fact makes other relevant
# assert parts trigger this foreach part
Feature.Selected($feat, True)

check $feat.Tags.contains("ScriptEngine")
assert
$xml_el = SubElement(config, "component")
#Element.is_a($xml_el) # we do not really need this fact

$xml_el.set("class", $feat.Name + "Class")

# implies has no matching Feature class member
Feature.implies($feat, $xml_el)

There are two facts, Feature.Selected and Feature.implies. As you see
I
took the class name as knowledge base identifier. The first one is
reflected by a member of my Feature class, whereas the second one has
no
Feature member equivalent.

Feature.implies only intends to trigger other fc rules which use
Feature.implies within their foreach part. But the same applies to
Feature.Selected as the next small example shows.

feature_selects_parent_featuregroup
foreach
$feat in Feature.Instances
Feature.Selected($feat, True)

Feature.Selected($feat.Parent, False)
assert
Feature.Selected($feat.Parent, True)

Normally, I would have to write the following two statements for any
knowledge base representing a class and a fact representing a class
member.

Feature.Selected($feat, Value) #Value = [True|False]
$feat.Selected = Value

But I would like to avoid it. Instead, by using Feature.Selected
within
a 'foreach' clause, the member value could be checked automatically.
For
instance, Feature.Selected($feat, True) could check the object member
for True. Similarly, Feature.Selected($feat, True) within an 'assert'
clause could set the member implicitly.

Additionally, in case of Feature.implies we would not need to set any
non-existant member of the feature object. Asserting this new fact to
the Feature kb would suffice.

Up until know I thought about writing an object knowledge base class.
One would need to explicitly instantiate and add such a oo kb to the
engine before calling any prove or assert methods on this kb. The pyke
compiler would appropriately call oo_kb.prove("Feature", "Selected",
$feat, True) for a 'foreach' clause and the assert method for an
'assert'.

There are some more issues to consider. The first one are compound
class
names (e.g. MyDomain.Feature) as knowledge base names. The kb name
should be exactly the same as the imported class name which dependes
how
the class is imported. Thus, there would be an assumption that a class
is always imported the same way when using the object knowledge base.
But this is a reasonable assumption from my point of view as Python
would treat a class imported by using 'from my_module import Feature'
as
a different class instance as one imported by 'import Feature'.

Secondly, statemtents like FeatureGroup($feat_gr, $children) should be
ok. As I would like to do a check like 'check $child in $children'.
This
complicates a little bit what the object knowledge base would need to
do. It would need to look up whether the second argument actually is a
pattern and bind that pattern to the instance member value.

The last one is about new objects and how to reflect the knowledge
about
their existence within the object kb. I commented out the line
'Element.is_a($xml_el)' in the first rule. I created a new Xml element
in the 'assert' part. Currently, I'm not using it but other 'foreach'
rule parts could depend on newly created xml element objects. My
suggestion is to keep here to both statements

$xml_el = SubElement(config, "component")
Element.is_a($xml_el)

to make it explicit that a new object has been created and that it
gets
added to the object "Element" kb.

Just let me know what you think.

Thanks
Egon

dangyogi

unread,
Aug 19, 2008, 2:32:38 PM8/19/08
to PyKE
Well, much to think about!

As I understand this, you are proposing the addition of an "object-
oriented" knowledge base to pyke. And it seems that the proposal is
for a facility that could be incorporated into pyke for others to
benefit from, rather than just a special knowledge base for your own
needs. If I have misunderstood, please let me know!

So with that in mind.

On Aug 18, 9:30 am, egon.wuch...@siemens.com wrote:
> [...]
>
> each_script_engine_requires_xml_component_entry
> foreach
> $feat in Feature.Instances
>
> # $feat.Selected = True does not work,
> # specifying a kb fact makes other relevant
> # assert parts trigger this foreach part
> Feature.Selected($feat, True)
>
> check $feat.Tags.contains("ScriptEngine")
> assert
> $xml_el = SubElement(config, "component")
> #Element.is_a($xml_el) # we do not really need this fact
>
> $xml_el.set("class", $feat.Name + "Class")
>
> # implies has no matching Feature class member
> Feature.implies($feat, $xml_el)
>
> There are two facts, Feature.Selected and Feature.implies. As you see I
> took the class name as knowledge base identifier. The first one is
> reflected by a member of my Feature class, whereas the second one has no
> Feature member equivalent.
>
> Feature.implies only intends to trigger other fc rules which use
> Feature.implies within their foreach part. But the same applies to
> Feature.Selected as the next small example shows.

I see the need to trigger fc_rules as almost certainly requiring a
knowledge base in order to avoid having to remember to make two
updates (one to the object and another to a fact base).

You mention the need to trigger fc_rules when an attribute of an
object changes (in this case, the 'Selected' attribute).

You also mention the need to trigger fc_rules in other cases related
to the object that don't correspond to any actual change within the
object ('Feature.implies'). For this case, I would think that you
could just use a normal fact base. This would, however, mean that the
normal fact base would have a different kb_name. Perhaps
"Implies.Feature($feat, $xml_el)"?

But you fail to mention that your fc_rule will not be triggered by the
creation of new Feature objects, i.e., by the line: "$feat in
Feature.Instances".

And I would not expect to need the "$feat in Feature.Instances" with a
normal fact base. Deleting this line leaves us with:
"Feature.Selected($feat, True)" which ought to succeed multiple times
binding "$feat" to a different object instance each time (each of the
instances whose 'Selected' attribute is 'True').

Taking this interpretation would then require that the new knowledge
base rerun the fc_rules referring to it in two situations: when a new
object is created with Selected = True, and when an old object's
Selected attribute is changed from False to True. Thus, the new
knowledge base would have to track all of the instances of the Feature
class (which is already implied by "Feature.Instances"), in addition
to tracking changes to the object's attributes.

Then the question of how to track these object creations and attribute
changes.

I can think of two answers:

1. Modify the classes being observed to report these actions to the
new knowledge_base. This might mean, for example, that you must
derive these classes from a certain pyke class.

2. When using these new knowledge bases, expect the programmer to go
to the knowledge base to create objects and change their attributes.
The knowledge base could accomplish these changes as well as rerunning
the appropriate fc_rules. You already suggest that the object's
attribute should be changed by asserting "Feature.Selected($feat,
True)" for example. Perhaps there could always be a built-in
"__init__" (or "__new__", or ???), such that "Feature.__init__($feat,
init_arg...)" creates an object (and binds it to "$feat"). Then there
could also be two additional back-door methods on the knowledge_base
to allow python code to do the same things.

My initial lean would be towards the second approach. This places
more of a burden on the python code to use these new back-door
functions, but allows one knowledge base to be used with several
classes that are seen by the programmer as being interchangeable,
though they do not share a common base class (as this is not required
by python). Thus, there would be no requirement that the name of the
knowledge base match any particular class name. That would just be up
to the programmer using the new knowledge base. (And thus, dots ('.')
would still not be allowed in kb_names).

Two final questions:

1. Does this new kind of knowledge base cross reference the object
attribute values to speed lookups (like fact bases do)? I guess my
lean here would be "yes".

2. Does this new kind of knowledge base support the use of method
names as the attribute name? I.e., "Feature.foo($answer, $feat,
arg, ...)" where the result of calling $feat.foo(arg, ...) is
$answer. This raises some questions:

1. How does the new knowledge base know when the answer to a method
call changes for any set of arguments? Does it just re-run the method
each time any attribute value changes (what about changes to other
objects that this method also depends on?)? Is the object required to
notify the knowledge base in this situation?

2. How does the new knowledge base know the possible arg values to
use? I.e., can the rules programmer use pattern variables for one or
more of these arguments to ask "for what argument values does this
method return X"? Does it only use arg values that it's seen in the
``foreach`` clause of fc_rules? Do you have to give it a list of the
values?

Supporting method calls seems to open a can of worms, so my initial
lean here would be "no". But maybe somebody can think of something
clever...

> [...]
>
> Secondly, statemtents like FeatureGroup($feat_gr, $children) should be
> ok. As I would like to do a check like 'check $child in $children'.
> This
> complicates a little bit what the object knowledge base would need to
> do. It would need to look up whether the second argument actually is a
> pattern and bind that pattern to the instance member value.

I don't really understand what you are intending here. If attribute X
has a tuple (or a list) as a value, then "Feature.X($feat, $value)"
should bind "$value" to this tuple. Just like
"Feature.Selected($feat, $value)" would bind "$value" to either 'True'
or 'False'.

Perhaps a small example would clarify this.

>
> The last one is about new objects and how to reflect the knowledge
> about
> their existence within the object kb. I commented out the line
> 'Element.is_a($xml_el)' in the first rule. I created a new Xml element
> in the 'assert' part. Currently, I'm not using it but other 'foreach'
> rule parts could depend on newly created xml element objects. My
> suggestion is to keep here to both statements
>
> $xml_el = SubElement(config, "component")
> Element.is_a($xml_el)
>
> to make it explicit that a new object has been created and that it
> gets
> added to the object "Element" kb.

I think that the new knowledge base tracking the instances would
pretty much do this (as explained above). But we might also always
want an "instance" (or "_instance_", or ???) entity, such that
"Feature.instance($feat)" would enumerate the instances (i.e., succeed
multiple times, binding "$feat" to a different instance each time).

Then when a new instance is created, this would rerun any fc_rules
containing it with just the one new instance for "$feat" so that all
of the prior instances don't need to be re-processed.

If I have misunderstood something, don't hesitate to explain it again!

Thanks for your input,

-bruce

egon.w...@siemens.com

unread,
Aug 20, 2008, 1:33:51 PM8/20/08
to PyKE
Hi Bruce,

thanks for all the comments and suggestions. I have inlined my
thoughts
to the discussion.

> As I understand this, you are proposing the addition of an
> "object- oriented" knowledge base to pyke. And it seems that
> the proposal is for a facility that could be incorporated
> into pyke for others to benefit from, rather than just a
> special knowledge base for your own needs. If I have
> misunderstood, please let me know!
>

Yes. You're absolutely right.

> > There are two facts, Feature.Selected and Feature.implies.
> As you see
> > I took the class name as knowledge base identifier. The
> first one is
> > reflected by a member of my Feature class, whereas the
> second one has
> > no Feature member equivalent.
> >
> > Feature.implies only intends to trigger other fc rules which use
> > Feature.implies within their foreach part. But the same applies to
> > Feature.Selected as the next small example shows.
>
> I see the need to trigger fc_rules as almost certainly
> requiring a knowledge base in order to avoid having to
> remember to make two updates (one to the object and another
> to a fact base).

Actually, you need to use a knowlegde base since otherwise the trigger
mechanism
of fc rules (a rule asserting a fact to a kb has to trigger the
foreach part of another rule
using using the knowledge base and relationship of that new fact)
won't work.

Since I need to use a knowledge base, I would like to get the object
change
automaitically.

> You mention the need to trigger fc_rules when an attribute of
> an object changes (in this case, the 'Selected' attribute).
>
> You also mention the need to trigger fc_rules in other cases
> related to the object that don't correspond to any actual
> change within the object ('Feature.implies'). For this case,
> I would think that you could just use a normal fact base.
> This would, however, mean that the normal fact base would
> have a different kb_name. Perhaps "Implies.Feature($feat, $xml_el)"?

I agree. It makes sense to separate these kind of relationships to a
object-oriented kb and a normal one.

> But you fail to mention that your fc_rule will not be
> triggered by the creation of new Feature objects, i.e., by
> the line: "$feat in Feature.Instances".

Actually, I do not create new features by my rules. And the
'Instances' is a
(static) member of my Feature class in order to collect all Feature
instances.
But more on creating new objects below.

> And I would not expect to need the "$feat in
> Feature.Instances" with a normal fact base. Deleting this
> line leaves us with:
> "Feature.Selected($feat, True)" which ought to succeed
> multiple times binding "$feat" to a different object instance
> each time (each of the instances whose 'Selected' attribute
> is 'True').
>
> Taking this interpretation would then require that the new
> knowledge base rerun the fc_rules referring to it in two
> situations: when a new object is created with Selected =
> True, and when an old object's Selected attribute is changed
> from False to True. Thus, the new knowledge base would have
> to track all of the instances of the Feature class (which is
> already implied by "Feature.Instances"), in addition to
> tracking changes to the object's attributes.

Tracking new instances and instance changes should ideally cover
both, (a) instance creation and change by Pyke rules (using the oo
knowledge base) and (b) creation and change by the Python code
run/called by the rules.
Yes. I favour the second approach as well. Also I like the
Feature.__init__ suggestion about object creation. But somehow
one of the arguments would have to be the class instance. Or did I
miss something here?

Using back-door methods would support the instance creation and
change case (b) from above. Ideally, what I'm even aiming at is
something like a combination of decorators and my classes in
order to reduce the burden on the python code to call these methods
when changing any attribute members or within the __init__ methods
of a class. But I'm still new to Python and I'm just thinking how
this
could work. (By the way, in C++ you should not pass an object around
until the ctor has completed. Is this the case with Python as well?)

Another issue would be whether calling the back-door methods directly
or indirectly from an assert clause affects the forach clause of rules
using
the new facts. The Pyke compiler is able to build up a dependency
graph
between fc rules in order to trigger dependent fc rules with the
right
pattern bindings. But this would'nt work with the back-door methods
since it is a runtime issue. Nevertheless, I think it would be
feasible to
implement this.

And you're right, the knowledge base name does not have to reflect
the
class name as long as the objects support the same attributes.

> Two final questions:
>
> 1. Does this new kind of knowledge base cross reference the
> object attribute values to speed lookups (like fact bases
> do)? I guess my lean here would be "yes".
>
> 2. Does this new kind of knowledge base support the use of
> method names as the attribute name? I.e.,
> "Feature.foo($answer, $feat, arg, ...)" where the result of
> calling $feat.foo(arg, ...) is $answer. This raises some questions:
>
> 1. How does the new knowledge base know when the answer to
> a method call changes for any set of arguments? Does it just
> re-run the method each time any attribute value changes (what
> about changes to other objects that this method also depends
> on?)? Is the object required to notify the knowledge base in
> this situation?
>
> 2. How does the new knowledge base know the possible arg
> values to use? I.e., can the rules programmer use pattern
> variables for one or more of these arguments to ask "for what
> argument values does this method return X"? Does it only use
> arg values that it's seen in the ``foreach`` clause of
> fc_rules? Do you have to give it a list of the values?
>
> Supporting method calls seems to open a can of worms, so my
> initial lean here would be "no". But maybe somebody can
> think of something clever...

I agree, supporting method calls ends nowhere except for much
headache ;-). But there might be a solution. What really matters
are attribute member changes to an object done by methods. And
by using the back-door methods when attribute members are set
(maybe even implicitly be some means like decorators, see above)
we could possibly circumvent the method call issue.

> > [...]
> >
> > Secondly, statemtents like FeatureGroup($feat_gr,
> $children) should be
> > ok. As I would like to do a check like 'check $child in $children'.
> > This
> > complicates a little bit what the object knowledge base
> would need to
> > do. It would need to look up whether the second argument
> actually is a
> > pattern and bind that pattern to the instance member value.
>
> I don't really understand what you are intending here. If
> attribute X has a tuple (or a list) as a value, then
> "Feature.X($feat, $value)"
> should bind "$value" to this tuple. Just like
> "Feature.Selected($feat, $value)" would bind "$value" to either 'True'
> or 'False'.
>
> Perhaps a small example would clarify this.

Yes. The binding is exactly what I meant, it should occur implicitly.
I was only referring to this as an implementation issue (without
being explicit about it and mixing it with a requirement discussion.
sometimes it is hard to separate these two things rigorously ;-)


> I think that the new knowledge base tracking the instances
> would pretty much do this (as explained above). But we might
> also always want an "instance" (or "_instance_", or ???)
> entity, such that "Feature.instance($feat)" would enumerate
> the instances (i.e., succeed multiple times, binding "$feat"
> to a different instance each time).
>
> Then when a new instance is created, this would rerun any
> fc_rules containing it with just the one new instance for
> "$feat" so that all of the prior instances don't need to be
> re-processed.

Here we would have an implicit fact dependency between
Feature.__init__ and Feature.instance which can be resolved at
compile time. Just a remark of mine.

Looking forward to any further discussion.
Egon

Bruce Frederiksen

unread,
Aug 21, 2008, 2:02:18 PM8/21/08
to py...@googlegroups.com
comments inlined...

Good discussion!

-bruce

egon.w...@siemens.com wrote:
[...]
Tracking new instances and instance changes should ideally cover
both, (a) instance creation and change by Pyke rules (using the oo
knowledge base) and (b) creation and change by the Python code
run/called by the rules.
  
I agree.  The question is how to do this...  A __setattr__ method captures the attribute changes.  But one of the problems with capturing object creation is that you'd want the new object base to see the created object after it has been fully initialized.  It seems that the least intrusive way to do this (that I can think of) is by using Python's metaclass facility (which I've never used before!).  This would mean that all classes to be tracked by an object base be derived from a special pyke class.  That's all that the programmer of these classes would have to do.  The special pyke class would have a __setattr__ method to trap the attribute changes and use the metaclass capability to trap object creation after __init__ is finished.  I have attached a demo on the end of this email.  It is also checked into svn (rev 134) as pyke/metaclass.py (just to make it available to play with -- it isn't used anywhere within pyke).
Yes, I missed that!

Using back-door methods would support the instance creation and
change case (b) from above. Ideally, what I'm even aiming at is
something like a combination of decorators and my classes in
order to reduce the burden on the python code to call these methods
when changing any attribute members or within the __init__ methods
 of a class.  But I'm still new to Python and I'm just thinking how
this
could work. (By the way, in C++ you should not pass an object around
until the ctor has completed. Is this the case with Python as well?)
  
Python does not have this problem, but we would still want the affected fc_rules to see the fully initialized objects (the fc_rules might ask about several attribute values).

Another issue would be whether calling the back-door methods directly
or indirectly from an assert clause affects the forach clause of rules
using
the new facts. The Pyke compiler is able to build up a dependency
graph
between fc rules in order to trigger dependent fc rules with the
right
pattern bindings. But this would'nt work with the back-door methods
since it is a runtime issue. Nevertheless, I think it would be
feasible to
implement this.
  
The pyke compiler does not build the dependency graph.  It only stores a list of the "facts" (actually "knowledge entities") referenced in each fc_rule's foreach clause.  Then, when the rule base is activated, each fc_rule calls add_fc_rule_ref on all of the listed entities.  So the new object bases would automatically get these add_fc_rule_ref calls too when they are referenced by an fc_rule.  This would not require any code changes, just define the method on the new object_base_entity objects.
[...]
  
I think that the new knowledge base tracking the instances
would pretty much do this (as explained above).  But we might
also always want an "instance" (or "_instance_", or ???)
entity, such that "Feature.instance($feat)" would enumerate
the instances (i.e., succeed multiple times, binding "$feat"
to a different instance each time).

Then when a new instance is created, this would rerun any
fc_rules containing it with just the one new instance for
"$feat" so that all of the prior instances don't need to be
re-processed.
    
Here we would have an implicit fact dependency between
Feature.__init__ and Feature.instance which can be resolved at
compile time. Just a remark of mine.
  
Again, this wouldn't be necessary.  The object base will be given the list of fc_rules that are interested in each "entity" name (for example, "instance" and "Select").  The object base can then trigger these rules whenever these (virtual) "facts" change.  So the object base "__init__" would know to trigger the fc_rules attached to "instance" (for example).  (Actually, I'm thinking now that the name "create" is less confusing than "__init__" -- or maybe "__create__"?).

----------------- cut here for metaclass.py demo ---------------------
# metaclass.py

from pyke.unique import unique

class metaclass(type): # this _must_ be derived from 'type'!
    _ignore_setattr = False
    def __init__(self, name, bases, dict):
        # This gets called when new derived classes are created.
        #
        # We don't need to define an __init__ method here, but I was just
        # curious about how this thing works...
        print "metaclass: name", name, ", bases", bases, \
              ", dict keys", tuple(sorted(dict.keys()))
        super(metaclass, self).__init__(name, bases, dict)
    def __call__(self, *args, **kws):
        # This gets called when new instances are created (using the class as
        # a function).
        obj = super(metaclass, self).__call__(*args, **kws)
        del obj._ignore_setattr
        print "add instance", obj, "to", self.knowledge_base
        return obj

class tracked_object(object):
    r'''
        All classes to be tracked by an object base would be derived from this
        one:

        >>> class foo(tracked_object):
        ...     def __init__(self, arg):
        ...         super(foo, self).__init__()
        ...         print "foo.__init__:", arg
        ...         self.x = arg    # should be ignored
        metaclass: name foo , bases (<class '__main__.tracked_object'>,) ,
        dict keys ('__init__', '__module__')


        And we can keep deriving classes:

        >>> class bar(foo):
        ...     def __init__(self, arg1, arg2):
        ...         super(bar, self).__init__(arg1)
        ...         print "bar.__init__:", arg1, arg2
        ...         self.y = arg2    # should be ignored
        metaclass: name bar , bases (<class '__main__.foo'>,) ,
        dict keys ('__init__', '__module__')


        We can't do the next step directly in the class definition because the
        knowledge_engine.engine hasn't been created yet and so the object
        bases don't exist at that point in time.

        So this simulates adding the knowledge_base to the class later, after
        the knowledge_engine.engine and object bases have been created.

        >>> foo.knowledge_base = 'foo base'
        >>> bar.knowledge_base = 'bar base'


        And now we create some instances (shouldn't see any attribute change
        notifications here!):

        >>> f = foo(44)
        foo.__init__: 44
        add instance <__main__.foo object at 0x...> to foo base
        >>> b = bar(55, 66)
        foo.__init__: 55
        bar.__init__: 55 66
        add instance <__main__.bar object at 0x...> to bar base


        And modify some attributes:

        >>> f.x = 'y'
        notify foo base of attribute change:
        (<__main__.foo object at 0x...>, x, y)
        >>> b.y = 'z'
        notify bar base of attribute change:
        (<__main__.bar object at 0x...>, y, z)
        >>> b.y = 'z' # should be ignored
        >>> b.z = "wasn't set"
        notify bar base of attribute change:
        (<__main__.bar object at 0x...>, z, wasn't set)

    '''
    __metaclass__ = metaclass
    _not_bound = unique('_not_bound') # a value that should != any other value!
    def __init__(self):
        self._ignore_setattr = True
    def __setattr__(self, attr, value):
        # This gets called when any attribute is changed.  We would need to
        # figure out how to ignore attribute setting by the __init__
        # function...
        #
        # Also the check to see if the attribute has actually changed by doing
        # a '!=' check could theoretically lead to problems.  For example this
        # would fail to change the attribute to another value that wasn't
        # identical to the first, but '==' to it: for example, 4 and 4.0.
        if getattr(self, attr, self._not_bound) != value:
            super(tracked_object, self).__setattr__(attr, value)
            if not hasattr(self, '_ignore_setattr'):
                print "notify", self.knowledge_base, \
                      "of attribute change: (%s, %s, %s)" % (self, attr, value)

def test():
    import sys
    import doctest
    sys.exit(doctest.testmod(optionflags = doctest.ELLIPSIS
                                         | doctest.NORMALIZE_WHITESPACE)
                            [0])

if __name__ == "__main__":
    test()

egon.w...@siemens.com

unread,
Aug 23, 2008, 6:53:21 AM8/23/08
to PyKE

> I agree. The question is how to do this... A __setattr__
> method captures the attribute changes. But one of the
> problems with capturing object creation is that you'd want
> the new object base to see the created object after it has
> been fully initialized. It seems that the least intrusive
> way to do this (that I can think of) is by using Python's
> metaclass facility (which I've never used before!). This
> would mean that all classes to be tracked by an object base
> be derived from a special pyke class. That's all that the
> programmer of these classes would have to do. The special
> pyke class would have a __setattr__ method to trap the
> attribute changes and use the metaclass capability to trap
> object creation after __init__ is finished. I have attached
> a demo on the end of this email. It is also checked into svn
> (rev 134) as pyke/metaclass.py (just to make it available to
> play with -- it isn't used anywhere within pyke).

I heard about the metaclass facility in Python and I like your
metaclass example. From my first search on the web I could figure out
that you can add class methods as well by using metaclasses. Is it
somehow possible to add object methods like __setattr__ to classes? In
order to avoid having to subclass from a certain pyke class. In C++
multi-inheritance is estimated as bad possibly implying several
problems.

By the way, is there any good documentation/tutorial on the web about
metaclasses in Python?

As you mentioned we have to track somehow attribute changes within the
ctor. In these cases we would not like to notify the knowledge base
and entity. We have to take care of attribute changes by the fc assert
clauses done by the object kb. The object kb has to assign the new
attribute values and implicitly the __setattr__ call tries to update
the object base again. Could be that we need an _ignore_kb attribute
for each object class.

> The pyke compiler does not build the dependency graph. It
> only stores a list of the "facts" (actually "knowledge
> entities") referenced in each fc_rule's foreach clause.
> Then, when the rule base is activated, each fc_rule calls
> add_fc_rule_ref on all of the listed entities. So the new
> object bases would automatically get these add_fc_rule_ref
> calls too when they are referenced by an fc_rule. This would
> not require any code changes, just define the method on the
> new object_base_entity objects.

> Again, this wouldn't be necessary. The object base will be
> given the list of fc_rules that are interested in each
> "entity" name (for example, "instance" and "Select"). The
> object base can then trigger these rules whenever these
> (virtual) "facts" change. So the object base "__init__"
> would know to trigger the fc_rules attached to "instance"
> (for example). (Actually, I'm thinking now that the name
> "create" is less confusing than "__init__" -- or maybe "__create__"?).

Ok. I got it now. But when are dependent foreach clauses triggered?
Immediately after updating the knowldedge entities? Or at the end of
each assert clause?

Thanks
Egon

Bruce Frederiksen

unread,
Aug 23, 2008, 9:19:13 AM8/23/08
to py...@googlegroups.com
egon.w...@siemens.com wrote:
  
[...]
I heard about the metaclass facility in Python and I like your
metaclass example. From my first search on the web I could figure out
that you can add class methods as well by using metaclasses. Is it
somehow possible to add object methods like __setattr__ to classes?
You just define a method called "__setattr__" in the class.  This will be inherited by derived classes.  Or are you trying to capture setting attributes on the class itself, rather than instances of the class?

In
order to avoid having to subclass from a certain pyke class. In C++
multi-inheritance is estimated as bad possibly implying several
problems.
  
Yes, C++ multi-inheritance is a total mess.  Though I nearly never use multi-inheritance in python, it should work much better.

In C++ the instance variables are translated into byte offsets within the object.  But these byte offsets will be different in derived classes that use multi-inheritance.  This implementation is one of the main reasons that C++ multi-inheritance is a can of worms.

Python stores all of an object's instance variables in a dictionary, so all of the instance variables from all of the base classes get thrown into the same dictionary.  Then self.foobar does a lookup for 'foobar' in this dictionary, rather than using a byte offset.  So multiple base classes sharing the same instance variable is not a problem at all.

The only question is which method gets used when multiple base classes define the same method.  The algorithm for this changed fairly recently in python (don't remember the exact release, 2.3 or 2.4?).   Since I don't find a need to use multi-inheritance, I'm not familiar with the details here.

Here's a little test that you might find interesting.  As usual, python does what's expected:
# multi_test.py

class top(object):
    def foo(self, indent = 0):
        print ' ' * indent + "top.foo"
    def bar(self):
        print "top.bar"

class left(top):
    r'''
        >>> l = left()
        >>> l.foo()     # here left.foo calls top.foo
        left.foo
            top.foo
        >>> l.bar()
        top.bar
    '''
    def foo(self, indent = 0):
        print ' ' * indent + "left.foo"
        super(left, self).foo(indent + 4)

class right(top):
    r'''
        >>> r = right()
        >>> r.foo()
        right.foo
            top.foo
    '''
    def foo(self, indent = 0):
        print ' ' * indent + "right.foo"
        super(right, self).foo(indent + 4)
    def bar(self):
        print "right.bar"

class bottom(left, right):
    r'''
        >>> b = bottom()
        >>> b.foo()     # here left.foo calls right.foo
        bottom.foo
            left.foo
                right.foo
                    top.foo
        >>> b.bar()     # gets right.bar, not left->top.bar
        right.bar
    '''
    def foo(self, indent = 0):
        print ' ' * indent + "bottom.foo"
        super(bottom, self).foo(indent + 4)


def test():
    import sys
    import doctest
    sys.exit(doctest.testmod()[0])


if __name__ == "__main__":
    test()
The more I try to think of other options, the more I think that the metaclass approach is the correct one.  Can you try adding tracked_object as another base class to your classes and see if it breaks anything?  It may not track object creation unless it's the first base class (not sure about this)?

The only other approach that I can think of to capture object creation would be to replace the __init__ method with another method that calls the original (replaced) __init__ method and then registers the initialized object with the object base.  (This could be done with a function decorator, or even by monkey patching the affected classes after their definitions).  But this approach doesn't work when that class has derived classes that have their own __init__ method.  Then the object base would not see the initialization done in the derived __init__.

The approach for __setattr__ could have a similar problem if a derived class wants to define it's own __setattr__.  But defining a __setattr__ is very rare (while defining an __init__ is very common).

By the way, is there any good documentation/tutorial on the web about
metaclasses in Python?
  
I'm not sure.  What I found was very sketchy and I had to try several things to get it to work.  The documentation describes the metaclass as simply a "callable" (i.e., function), but the key seems to be defining the metaclass as a class and deriving the metaclass from type.  But I didn't go back to look for better documentation...  One quote that I saw basically said to only use metaclasses as a last resort.

As you mentioned we have to track somehow attribute changes within the
ctor. In these cases we would not like to notify the knowledge base
and entity.
My metaclass example does this.

 We have to take care of attribute changes by the fc assert
clauses done by the object kb. The object kb has to assign the new
attribute values and implicitly the __setattr__ call tries to update
the object base again. Could be that we need an _ignore_kb attribute
for each object class.
I would think that the object kb assert would simply set the attribute on the object and let the __setattr__ take care of the rest.  Thus the object kb assert doesn't store anything in the object kb, but lets the callback from __setattr__ do this.
[...]
Ok. I got it now. But when are dependent foreach clauses triggered?
Immediately after updating the knowldedge entities? Or at the end of
each assert clause?
  
Immediately after updating the knowledge entities.  The fact_list (fact base entity) keeps track of the fc_rules passed to it through add_fc_rule_ref and calls these fc_rules (calling new_fact on them) whenever a new fact is asserted.  So the callback is in the fact_list.  The object version(s) (object_create, object_instance, object_attribute?) would have to do the same thing.

OK, so here is another question!

When the object base is reset (by engine.reset), what does it do?
  • Forget about all instances and attributes.
  • Not forget any instances and attributes (i.e., do nothing).
    • Does this mean there needs to also be a "forget" on object bases to cause it to forget an instance (and that instance's attributes)?
  • Have "universal" and "case_specific" instances (and their attributes).
-bruce

egon.w...@siemens.com

unread,
Aug 29, 2008, 9:59:09 AM8/29/08
to PyKE

> OK, so here is another question!
> When the object base isreset(byengine.reset), what does it do?Forget about all instances and attributes.Not forget any instances and attributes (i.e., do nothing).Does this mean...
>
I have to think about this, but my initial thought here is to keep to
universal and case specific facts as well.

By the way, I worked on a second metaclass approach to be used with a
pyke object knowledge. I listed the pros and cons of both options and
the code below in case of interest. (s. metaclass.py in the Pyke
repository as well). A colleague of mine has the "Pyhton Cookbook"
which covers metaclass programming to some extent. I found some useful
information about metaclass.__new__ and metaclass.__init__ which I
used for this approach.

----------------------------------------------------------------------------------------------------------
# metaclass.py
from pyke.unique import unique

'''
this metaclass is intended to be used by deriving from tracked_object
as base class

pros:
- probably works with IronPython or Jython
- easier to understand

cons:
- __setattr__ defined by classes poses problems

'''
class metaclass_option1(type): # this _must_ be derived from 'type'!
_ignore_setattr = False
def __init__(self, name, bases, dict):
# This gets called when new derived classes are created.
#
# We don't need to define an __init__ method here, but I was
just
# curious about how this thing works...
print "metaclass: name", name, ", bases", bases, \
", dict keys", tuple(sorted(dict.keys()))
super(metaclass_option1, self).__init__(name, bases, dict)

def __call__(self, *args, **kws):
# This gets called when new instances are created (using the
class as
# a function).
obj = super(metaclass_option1, self).__call__(*args, **kws)
del obj._ignore_setattr
print "add instance", obj, "to", self.knowledge_base
return obj


'''
this metaclass requires the __metaclass__ = metaclass_option2
attribute of classes to be used with the object knowledge base of pyke

pros:
- solves the problem of classes defining their own __setattr__ method
- does not require any multiple inheritance

cons:
- hard to understand
- possibly does not work with IronPython or Jython

'''
class metaclass_option2(type): # this _must_ be derived from 'type'!

def __new__(mcl, name, bases, clsdict):

print "metaclass_option2.__new__: class dict before __new__:
name", name, ", bases", bases, \
", dict keys", tuple(clsdict.keys()), ", dict values",
tuple(clsdict.values())

def __setattr__(self, attr, value):
# This gets called when any attribute is changed. We
would need to
# figure out how to ignore attribute setting by the
__init__
# function...
#
# Also the check to see if the attribute has actually
changed by doing
# a '!=' check could theoretically lead to problems. For
example this
# would fail to change the attribute to another value that
wasn't
# identical to the first, but '==' to it: for example, 4
and 4.0.
if self.__instance__.get(self, False) :
if getattr(self, attr) != value:
print "metaclass.__new__: notify knowledge base",
\
"of attribute change: (%s, %s, %s)" % (self,
attr, value)

if self.__cls__setattr__ != None:
self.__cls__setattr__(attr, value)
else:
super(self.__class__, self).__setattr__(attr,
value)

else:
# does not work to call super.__setattr__
#super(self.__class__, self).__setattr__(attr, value)
#
self.__dict__[attr] = value

def __getattr__(self, name):
return self.__dict__[name]

cls__setattr__ = None
if clsdict.get('__setattr__', None) != None:
cls__setattr__ = clsdict['__setattr__']

clsdict['__setattr__'] = __setattr__
clsdict['__getattr__'] = __getattr__
clsdict['__cls__setattr__'] = cls__setattr__
clsdict['__instance__'] = {}

print "metaclass_option2.__new__: class dict after __new__:
name", name, ", bases", bases, \
", dict keys", tuple(sorted(clsdict.keys())), ", dict
values", tuple(clsdict.values())

return super(metaclass_option2, mcl).__new__(mcl, name, bases,
clsdict)


'''
def __init__(cls, name, bases, clsdict):
# This gets called when new derived classes are created.
#
# We don't need to define an __init__ method here, but I was
just
# curious about how this thing works...
super(metaclass_option2, cls).__init__(name, bases, clsdict)

print "class dict after __init__: name", name, ", bases",
bases, \
", dict keys", tuple(sorted(clsdict.keys()))

# does not work to create __instance class member here
#clsdict['__instance__'] = {}
'''

def __call__(cls, *args, **kws):
# This gets called when new instances are created (using the
class as
# a function).
obj = super(metaclass_option2, cls).__call__(*args, **kws)

obj.__instance__[obj] = True

print "add instance of class", cls.__name__, "to knowledge
base"
__metaclass__ = metaclass_option1
_not_bound = unique('_not_bound') # a value that should != any
other value!
def __init__(self):
self._ignore_setattr = True
self.knowledgebase = None

def __setattr__(self, attr, value):
# This gets called when any attribute is changed. We would
need to
# figure out how to ignore attribute setting by the __init__
# function...
#
# Also the check to see if the attribute has actually changed
by doing
# a '!=' check could theoretically lead to problems. For
example this
# would fail to change the attribute to another value that
wasn't
# identical to the first, but '==' to it: for example, 4 and
4.0.
print "tracked_object.__setattr__ called on object %s with
property %s and value %s" % (self, attr, value)
if getattr(self, attr, self._not_bound) != value:
super(tracked_object, self).__setattr__(attr, value)
if not hasattr(self, '_ignore_setattr'):
print "tracked_object.__setattr__: notify",
self.knowledge_base, \
"of attribute change: (%s, %s, %s)" % (self,
attr, value)


''' tracked_object and foo_tracked use metaclass_option1 '''
class foo_tracked(tracked_object):
def __init__(self, arg):
super(foo_tracked, self).__init__()
self.prop = arg


''' the following classes use metaclass_option2 '''
class foo_base(object):
def __setattr__(self, attr, value):
print "foo_base.__setattr__ called on object %s with property
%s and value %s" % (self, attr, value)


class foo_attribute_base(foo_base):
__metaclass__ = metaclass_option2

def __init__(self, arg):
super(foo_attribute_base, self).__init__()
self.prop = arg


class foo_attribute(object):
__metaclass__ = metaclass_option2

def __init__(self, arg):
super(foo_attribute, self).__init__()
self.prop = arg

def __setattr__(self, attr, value):
print "foo_attribute.__setattr__ called on object %s with
property %s and value %s" % (self, attr, value)


class foo(object):
__metaclass__ = metaclass_option2

def __init__(self, arg):
super(foo, self).__init__()
self.prop = arg

#self.knowledge_base = "foo"

def foo_method(self):
print "foo_method called"

def test_foo_option2():
f1 = foo(1) # should add instance to knowledge base
f1.prop = 2 # should notify knowledge base of property change

f2 = foo("egon") # should add instance to knowledge base
f2.prop = "ralf" # should notify knowledge base of property
change

f3 = foo_attribute(3)
f3.prop = 4

f4 = foo_attribute("karin")
f4.prop = "sabine"

f5 = foo_attribute_base(5)
f5.prop = 6

f6 = foo_attribute_base("sebastian")
f6.prop = "philipp"


def test_foo_option1():
import sys
import doctest
sys.exit(doctest.testmod(optionflags = doctest.ELLIPSIS
doctest.NORMALIZE_WHITESPACE)
[0])

if __name__ == "__main__":
#test_foo_option1()
test_foo_option2()

egon.w...@siemens.com

unread,
Sep 30, 2008, 12:50:47 PM9/30/08
to PyKE
I would like to continue this thread since other issues came to my
mind when working with the Jboss Drools forward chaining engine.

We had one of the following statements:
>Tracking new instances and instance changes should ideally cover both, (a) instance creation and change by Pyke rules (using the oo knowledge base) and (b) creation and change by the Python code run/called by the rules.

We have to take care that the rule engine evaluation does not get
stuck in a circular loop. The simplest case would be that we have
Feature.Selected($object, $value) in the 'foreach' conditional part of
the loop and a method call in the action part resetting the 'Selected'
attribute again.

Jboss Drools tries to tackle this by offering keywords like 'no-loop'
per rule (or 'lock-on-active' on a rule activation group but I do not
want to go into these details here). By knowing now more how Pyke
rule firing of fc rules works, I would say that it would be possible
to track these circular dependencies by Pyke. If the 'foreach'
conditional part of a rule gets fired again with the variable being
bound to the same set of values the rule should not fire again.

There is another issue being more of an optimization. If the action
part of the rule contains several method calls we should accumulate
all object property changes and only fire the affected rules at the
end of the action execution. I know that this would differ from how
Pyke works now but the main reason is that we could avoid unnecessary
rule activations/firing. The 'selected' property of a feature object
might be set several times during the execution of the rule action.
But only the last value matters. And other rules should be fired with
this last and only relevant value as a variable binding candidate.

Thanks
Egon

Bruce Frederiksen

unread,
Oct 2, 2008, 9:02:18 PM10/2/08
to py...@googlegroups.com
egon.w...@siemens.com wrote:
> I would like to continue this thread since other issues came to my
> mind when working with the Jboss Drools forward chaining engine.
>
> We had one of the following statements:
>
>> Tracking new instances and instance changes should ideally cover both, (a) instance creation and change by Pyke rules (using the oo knowledge base) and (b) creation and change by the Python code run/called by the rules.
>>
>
> We have to take care that the rule engine evaluation does not get
> stuck in a circular loop. The simplest case would be that we have
> Feature.Selected($object, $value) in the 'foreach' conditional part of
> the loop and a method call in the action part resetting the 'Selected'
> attribute again.
>
> Jboss Drools tries to tackle this by offering keywords like 'no-loop'
> per rule (or 'lock-on-active' on a rule activation group but I do not
> want to go into these details here). By knowing now more how Pyke
> rule firing of fc rules works, I would say that it would be possible
> to track these circular dependencies by Pyke. If the 'foreach'
> conditional part of a rule gets fired again with the variable being
> bound to the same set of values the rule should not fire again.
>
I don't think that would be a problem because Pyke ignores duplicate
facts. So if an fc_rule asserts facts already known, nothing happens.
With immutable facts, there can be no problem. I guess the object
extensions introduce mutable facts; so two rules could each change a
fact back and forth between two values and get caught in a loop. I
would think that this would represent an error in the rules though.

> There is another issue being more of an optimization. If the action
> part of the rule contains several method calls we should accumulate
> all object property changes and only fire the affected rules at the
> end of the action execution. I know that this would differ from how
> Pyke works now but the main reason is that we could avoid unnecessary
> rule activations/firing. The 'selected' property of a feature object
> might be set several times during the execution of the rule action.
> But only the last value matters. And other rules should be fired with
> this last and only relevant value as a variable binding candidate.
Do you think that a property would be set to several different values in
practice? This raises the question of what do mutable facts mean? And
if rules are supposed to be activated when a fact becomes true, should
whatever the rule asserted also be retracted if the fact that it depends
on is later retracted? And if not, then I would expect the the rule
would be fired for each value that the property takes on, not just every
Nth value depending on what the value happened to be at the end of some
other rule's assert clause. So I guess I would like to see some real
world examples here of problems that you are running into so that I can
better understand what's going on.

As far as the optimizations go, I'd also like to see a real situation
where optimization is needed. In thinking about this, it seems that
what "optimal" would be might differ for different rule sets. For
example, when rule C asserts fact X that re-fires rule A, which asserts
fact Y, which causes rule B to need to be re-fired, should rule B be run
right away? At the end of rule A's assert clause? Or after all of the
other rules affected by rule C's assert clause have been run? Or should
a master list of rules to re-fire be kept for rule C and each new rule
be inserted into the list in the order that it appears in the .krb file
and at the end of rule C's assert clause always execute the first rule
in the list until the list is empty? And each of these different kinds
of "optimizations" seem like they would work better for some rule sets,
but make things worse for others.

So if you have some real examples that you think need optimization help,
we can take a look at them to get a better idea of where the problems
are and how to solve them.

I've been doing some thinking about fact retraction too. I'll post this
in a separate post...

-bruce

pachet

unread,
Nov 4, 2008, 5:10:57 AM11/4/08
to PyKE
Hello

I am happy to see that the "objects" and "rules" theme is
reappearing.
For information, I worked on rules + objects in a Object-Oriented
setting, some time ago.
This once popular subject then somehow vanished.
There may be some interesting links to do with what was done with Rete
based approach in particular, and the "modified" problem,
as well as the concept of "Rule Base Inheritance":
http://www.csl.sony.fr/publications/item/?reference=pachet%3A95a
http://www.csl.sony.fr/publications/item/?reference=pachet%3A92a

Best
Francois



On Oct 3, 2:02 am, Bruce Frederiksen <dangy...@gmail.com> wrote:
Reply all
Reply to author
Forward
0 new messages