From: Gerald B. <ger...@gm...> - 2009-07-20 18:30:34
|
Brian, Benny I need some advice on part of this project The gen/lib/base.py module contains a number of things besides methods. The methods will become purely abstract. However, base.py also sets up some global dictionaries: CLASS_TO_KEY_MAP KEY_TO_CLASS_MAP and a couple of global variables: _UNDO_SIZE _SIGBASE Should these stay in base.py? I'm thinking yes and then explicitly include them in the hew read.pt module. base.py also does a lot in it's __init__ method. For example, it set's up default (empty) dictionaries, lists and sets and initializes a bunch of instance variables. I'm thinking that these should all go to the new read.py module. It also sets up a bunch of signals. I'm thinking about that these should all go to the new write.py ( which will replace dbdir.py) Your thoughts? On Fri, Jul 17, 2009 at 9:36 AM, Richard Taylor<rjt...@th...> wrote: > There is no implicit checking that an interface is implemented. It is > possible to 'explicitly' check that a class implements all of the > methods specified in an interface but such a check is of limited value > in Python. For instance, if you are using the __getattr__ delegation > pattern there is no way to check that a class implements a particular > set of methods unless you actually try to call them > > So the interfaces are meant as a declaration that an *object* (note: not > a class) will provide an interface, at runtime. It is even possible for > a given object to change the interfaces that it will support. > > In our case it would be possible to change the interfaces that the > database object declared. So the IWriteDB interface could be removed if > the database was flagged as readonly. Calling code can ask for an object > to be converted to support a particular interface and if a method is > available to do the conversion to will be called automatically. > > This approach is extreemly flexible but it can be difficult for > newcomers to the code base to understand. It might not be a good thing > for gramps to end up as difficult to work on as Zope :-) > > Regards > > Richard > > Benny Malengier wrote: >> 2009/7/17 Gerald Britton <ger...@gm...>: >>> At first glance, I don't like it much. It seems like trying to force >>> Java idioms on Python. I really prefer a purely Pythonic approach. >>> Plus, I try to avoid function calls whenever I can because they are >>> still expensive in Python compared to other languages. The example >>> where getattr does a lookup by calling names() is just the kind of >>> thing I would try to avoid. >> >> But it is a nice structure, and probably it avoids the class >> inheritence of an abstract class which would save some time >> It looks like the gtk classes I mentioned that also use some sort of >> implements structure. >> >> But you are right, the __getattr__ adds some extra calls. Note that >> the slowdown is only present in one of the two cases: >> >> 1. the method is implemented ==> __getattr__ is never called, the >> method is found, and executed >> 2. the method is not implemented ==> __getattr__ is called, name >> looked up in a dictionary, if present, db method is called. This as >> opposed to calling the method that then does a call of db method >> immediately. >> >> I think I would avoid the __getattr__ calling too, but the implements >> idea remains valid in that case nevertheless as it removes the >> abstract class inheritance, while still checking an interface is >> actually implemented (I hope, did not look at the details of zope). >> >> Benny >>> On Fri, Jul 17, 2009 at 7:36 AM, Richard >>> Taylor<rjt...@th...> wrote: >>>> Gerald >>>> >>>> This might be a step too far, but you could consider using >>>> zope.interfaces (http://pypi.python.org/pypi/zope.interface/3.5.2). >>>> (Don't worry about the "zope" name, it came from zope originally but is >>>> now used by many projects outside of zope). >>>> >>>> zope.interface provide a framework for defining interfaces orthogonally >>>> from the classes that implement them ("provide them" in zope.interface >>>> speak). >>>> >>>> >>>> Interfaces are defined just like normal classes but they inherit from >>>> Interface: >>>> >>>> from zope.interface import Interface >>>> >>>> class IReadOnlyDB(Interface): >>>> >>>> def read(self, wibble): >>>> """Docstring""" >>>> pass #this can not provide an implementation >>>> >>>> A class can they state that it implements the Interface (and hence >>>> objects of that class "provide" the interface). >>>> >>>> from zope.interface import implements >>>> >>>> class ReadOnlyDB: >>>> implements(IReadOnlyDB,<plus other interfaces>) >>>> >>>> def read(self, wibble): >>>> implementation >>>> >>>> >>>> So a proxy class (using delegation) might look something like: >>>> >>>> >>>> class ReadOnlyProxy: >>>> implements(IReadOnlyDB) >>>> >>>> def __init__(self, c): >>>> self.c = c >>>> >>>> def __getattr__(self,name): >>>> if name in IReadOnlyDB.names(): >>>> return getattr(self.c, name) >>>> >>>> db = ReadOnlyDB() >>>> proxy = ReadOnlyProxy(db) >>>> >>>> Both db and proxy provide the same IReadOnlyDB interface. >>>> >>>> Note the line "IReadOnlyDB.names()" which demonstrates that you can >>>> introspect the interface. >>>> >>>> This model is very powerful and enables lots of control of which objects >>>> provide which interface without needing to use Abstract Base Classes and >>>> ending up with complex multiple inheritance structures. Objects can be >>>> 'verified' to check if they implement a particular interface, interfaces >>>> can be inherited etc. >>>> >>>> Just a thought. >>>> >>>> Richard >>>> >>>> >>>> >>>> Gerald Britton wrote: >>>>> On second thought I'm not sure that this is the way to go, since then >>>>> we lose the abstract base class idea. If __getattr__ gobbles up all >>>>> undefined methods, there's nowhere to put the docstring for the >>>>> interface. In spite of the seeming waste of space, spelling it all >>>>> out might just be the way to go. >>>>> >>>>> On Wed, Jul 15, 2009 at 12:50 PM, Gerald >>>>> Britton<ger...@gm...> wrote: >>>>>> The trouble is that the NotImplementedError is raised in __getattr__ >>>>>> and you don't see the method you were trying to call. Of course the >>>>>> traceback shows you where you came from so you can figure it out. >>>>>> >>>>>> The other thing I'm worried about is performance, though it seems that >>>>>> a custom __getattr__ method does not add significant overhead (< 1%) >>>>>> >>>>>> I think I like this idea and will try to exploit it unless others object. >>>>>> >>>>>> On Wed, Jul 15, 2009 at 11:48 AM, Richard >>>>>> Taylor<rjt...@th...> wrote: >>>>>>> Hi >>>>>>> >>>>>>> Firstly, I have been out of Gramps development for some time so forgive >>>>>>> me if my response is a little naive. >>>>>>> >>>>>>> I have been following this thread with interest. >>>>>>> >>>>>>> Taking a quick look at the current implementation of ProxyDbBase I am >>>>>>> struck by the number of methods that are of the form: >>>>>>> >>>>>>> def method(self, arg): >>>>>>> self.db.method(arg) >>>>>>> >>>>>>> This looks like a classic 'delegate' pattern. It would be better to deal >>>>>>> with this using a __getattr__ delegation approach e.g.: >>>>>>> >>>>>>> >>>>>>> class C: >>>>>>> def method(self,a): >>>>>>> print "C ",a >>>>>>> >>>>>>> class D: >>>>>>> def __init__(self,c): >>>>>>> self.c = c >>>>>>> >>>>>>> def method2(self,a): >>>>>>> print "D ",a >>>>>>> >>>>>>> def __getattr__(self,name): >>>>>>> return getattr(self.c, name) >>>>>>> >>>>>>> The methods that should not be delegated could then be put into a list >>>>>>> that is checked in __getattr__: >>>>>>> >>>>>>> class D: >>>>>>> __do_not_delegate__ = ('methodA','methodB') >>>>>>> >>>>>>> def __getattr__(self,name): >>>>>>> if name in __do_not_delegate__: >>>>>>> raise NotImplemented >>>>>>> >>>>>>> return getattr(self.c, name) >>>>>>> >>>>>>> This at least would cut down on the boiler plate in the classes. >>>>>>> >>>>>>> Just a thought. >>>>>>> >>>>>>> Richard >>>>>>> >>>>>>> >>>>>>> Gerald Britton wrote: >>>>>>>> I've been pondering the right way to go about restructuring the >>>>>>>> database objects while minimizing disruption. Here's a possible plan >>>>>>>> for discussion: >>>>>>>> >>>>>>>> End result: >>>>>>>> >>>>>>>> GrampsDbBase object, containing only abstract methods all returning >>>>>>>> NotImplementedError >>>>>>>> GrampsDbRead object, derives from GrampsDbBase and implementing only >>>>>>>> the methods needed to read the database >>>>>>>> GrampsDbWrite object, derives from GrampsDbRead and implementing the >>>>>>>> methods need to write the database (thus comprising a full set of >>>>>>>> methods, those inherited from GrampsDbRead and those implemented in >>>>>>>> GrampsDbWrite) >>>>>>>> >>>>>>>> Here are two options for the proxy databases: >>>>>>>> >>>>>>>> 1. ProxyDbRead object, subclassed from GrampsDbRead and overriding its >>>>>>>> methods to add conditional logic (see below) >>>>>>>> >>>>>>>> OR >>>>>>>> >>>>>>>> 2. ProxyDbBase object, subclassed from GrampsDbBase, possibly adding >>>>>>>> some abstract methods (not sure there are or should be any to add, >>>>>>>> though) >>>>>>>> ProxyDbRead, subclassed from ProxyDbBase, implementing basic methods >>>>>>>> to read a proxy database with some conditional logic >>>>>>>> >>>>>>>> I'm partial to option 1 since it builds on earlier work but that >>>>>>>> depends on how much of GrampsDbRead is directly useful in ProxyDbRead. >>>>>>>> >>>>>>>> Whichever option is chosen (or some other scheme, if neither of these >>>>>>>> is appropriate), the actual proxy database objects (currently four: >>>>>>>> PrivateProxyDb, LivingProxyDb, FilterProxyDb, and ReferencedProxyDb) >>>>>>>> would derive from ProxyDbRead. >>>>>>>> >>>>>>>> My thoughts for ProxyDbRead include implementing a predicate scheme >>>>>>>> for use as a kind of callback to indicate if an object should be >>>>>>>> included or not. I did this already in the iter_... functions >>>>>>>> recently committed, which by default now return: >>>>>>>> >>>>>>>> ifilter(include_something, self.db.iter_something_handles()) >>>>>>>> >>>>>>>> where "include_something" is a predicate that is passed the handle >>>>>>>> being considered. If not overridden in the subclass, it defaults to >>>>>>>> None which tells ifilter to return any handle from the iterator if it >>>>>>>> evaluates to boolean True. A good example is for the PrivateProxyDb, >>>>>>>> where I can define: >>>>>>>> >>>>>>>> def include_person(self, handle): >>>>>>>> person = self.db.get_person_from_handle() >>>>>>>> if person: >>>>>>>> return not person.__isprivate() >>>>>>>> return False >>>>>>>> >>>>>>>> This forces the iterator to only return person objects NOT marked >>>>>>>> private. The same idea can be used in other default methods as well, >>>>>>>> such as: >>>>>>>> >>>>>>>> get_something_from_handle: Only return an object if the predicate >>>>>>>> evaluates true >>>>>>>> get_something_from_id: Same as above >>>>>>>> has_something_handle: Only return True if the predicate is >>>>>>>> True for this object, otherwise return False >>>>>>>> >>>>>>>> etc. >>>>>>>> >>>>>>>> This way, the child proxy database classes have much less to >>>>>>>> implement. In some cases, the predicate functions will be sufficient. >>>>>>>> >>>>>>>> Please consider these ideas and provide feedback. I want to do the >>>>>>>> right thing, here. >>>>>>>> >>>>>>>> >>>>>> >>>>>> -- >>>>>> Gerald Britton >>>>>> >>>>> >>>>> >>>> >>> >>> >>> -- >>> Gerald Britton >>> >>> ------------------------------------------------------------------------------ >>> Enter the BlackBerry Developer Challenge >>> This is your chance to win up to $100,000 in prizes! For a limited time, >>> vendors submitting new applications to BlackBerry App World(TM) will have >>> the opportunity to enter the BlackBerry Developer Challenge. See full prize >>> details at: http://p.sf.net/sfu/Challenge >>> _______________________________________________ >>> Gramps-devel mailing list >>> Gra...@li... >>> https://lists.sourceforge.net/lists/listinfo/gramps-devel >>> >> >> >> > > -- Gerald Britton |