Path: csiph.com!v102.xanadu-bbs.net!xanadu-bbs.net!eternal-september.org!feeder.eternal-september.org!newsfeed.fsmpi.rwth-aachen.de!newsfeed.straub-nv.de!newsfeed0.kamp.net!newsfeed.kamp.net!feeder1.cambriumusenet.nl!feed.tweaknews.nl!194.109.133.86.MISMATCH!newsfeed.xs4all.nl!newsfeed3.news.xs4all.nl!xs4all!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.000 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'python.': 0.02; 'classes,': 0.05; 'plenty': 0.07; '*args,': 0.09; 'apis': 0.09; 'arguments': 0.09; 'function,': 0.09; 'instances.': 0.09; 'methods,': 0.09; 'wrapped': 0.09; 'wrapper': 0.09; 'api': 0.11; 'python': 0.11; 'def': 0.12; 'changes': 0.15; '42,': 0.16; 'attr': 0.16; 'attributes.': 0.16; 'behave': 0.16; 'dict': 0.16; 'dictionaries': 0.16; 'googling': 0.16; 'i\xe2\x80\x99d': 0.16; 'i\xe2\x80\x99m': 0.16; 'lambda': 0.16; 'modifier': 0.16; 'need:': 0.16; 'objective-c,': 0.16; 'obviously,': 0.16; 'overridden': 0.16; 'recurse': 0.16; 'subclasses': 0.16; 'subject:dictionaries': 0.16; 'travis': 0.16; 'unexpected': 0.16; 'ignore': 0.16; 'language': 0.16; 'wrote:': 0.18; 'wed,': 0.18; 'module': 0.19; 'typing': 0.19; 'written': 0.21; 'feb': 0.22; '>>>': 0.22; 'import': 0.22; 'replace': 0.24; 'question': 0.24; 'academic': 0.26; 'class.': 0.26; 'second': 0.26; 'code:': 0.26; 'header:In- Reply-To:1': 0.27; 'am,': 0.29; 'patch': 0.29; 'restrict': 0.30; 'returned': 0.30; 'message-id:@mail.gmail.com': 0.30; 'doc': 0.31; 'evil': 0.31; 'noted': 0.31; 'object.': 0.31; 'reduced': 0.31; 'sets.': 0.31; 'though.': 0.31; 'class': 0.32; 'extend': 0.32; 'skip:c 30': 0.32; 'option': 0.32; 'another': 0.32; 'received:209.85.212': 0.32; '(e.g.': 0.33; 'guess': 0.33; 'not.': 0.33; 'copying': 0.34; 'sense': 0.34; 'skip:_ 10': 0.34; 'could': 0.34; 'problem': 0.35; 'received:209.85': 0.35; 'problem.': 0.35; 'tool': 0.35; 'something': 0.35; 'case,': 0.35; 'test': 0.35; 'but': 0.35; 'received:google.com': 0.35; 'there': 0.35; 'really': 0.36; 'done,': 0.36; 'false': 0.36; 'module.': 0.36; 'done': 0.36; 'shows': 0.36; 'possible': 0.36; 'similar': 0.36; 'should': 0.36; 'too': 0.37; 'received:209': 0.37; 'checks': 0.38; 'tired': 0.38; 'to:addr:python-list': 0.38; 'pm,': 0.38; 'rather': 0.38; 'expect': 0.39; 'does': 0.39; 'aside': 0.39; 'expensive': 0.39; 'to:addr:python.org': 0.39; 'how': 0.40; 'ian': 0.60; 'problems.': 0.60; 'solve': 0.60; 'free': 0.61; 'first': 0.61; 'skip:n 10': 0.64; '8bit%:10': 0.64; 'places': 0.64; 'strategy': 0.64; 'interest': 0.64; 'relatively': 0.65; 'skip:\xe2 10': 0.65; 'due': 0.66; '8bit%:21': 0.69; '8bit%:43': 0.74; '2015': 0.84; 'break.': 0.84; 'can\xe2\x80\x99t': 0.84; 'examples.': 0.84; 'safer,': 0.84; 'wrapper,': 0.84; 'careful': 0.91 X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:in-reply-to:references:from:date :message-id:subject:to:content-type:content-transfer-encoding; bh=Sz9k35qsFGtp7GwvtjJfbhODUI+s1lRBanyV0EqRWDM=; b=B0jI75Rn6uSYtU2pBeBUt3h8Wb/6fF9Wr/28aFV8G5h5PkFb7TMyOQJ1cs0kfvS2Hy fteyXs/JhGl+RVehbJjpu5Gsj0bO5tcJAIMNGdeX83ekAIQffBx7/EjfBCDQb7grI5Tu 4hMLxYIETnhdSZvtgM+bFDVD/O6w/199Nk+PVym0YzzUabS4MQ8gw75cuUIhZnYBcbhO DIHVt00PhafUpsHTLGINF5ByXRkYx8FMheVP7xhfek1ZfHybYWCiYsdkc4p+fw9EWrVs 9IutlOgLmHvU9OST38nfP8p00nSlZwGt6e+PVbl2lryDBJ/CFgfu8z2zujVEFRMrluku EFtA== X-Gm-Message-State: ALoCoQmwdBOtzihcDYX82qy7BkByBOqRzdUXHtRWOUyxLosClaIxLcum6KD4QRL5QFedC88xbpdG X-Received: by 10.180.205.210 with SMTP id li18mr3860671wic.39.1423082772059; Wed, 04 Feb 2015 12:46:12 -0800 (PST) MIME-Version: 1.0 In-Reply-To: <70CF795C-8494-48CF-A5F8-3D3F8FE3AE95@gmail.com> References: <436419A8-6CD1-43DA-AC5E-A2E974AD585E@gmail.com> <70CF795C-8494-48CF-A5F8-3D3F8FE3AE95@gmail.com> From: Chris Kaynor Date: Wed, 4 Feb 2015 12:45:51 -0800 Subject: Re: pymongo and attribute dictionaries To: Python Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 120 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1423082780 news.xs4all.nl 2893 [2001:888:2000:d::a6]:40296 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:85229 On Wed, Feb 4, 2015 at 12:16 PM, Travis Griggs wro= te: > >> On Feb 4, 2015, at 9:22 AM, Ian Kelly wrote: >> >> On Wed, Feb 4, 2015 at 9:50 AM, Travis Griggs w= rote: >>> I really like pymongo. And I really like Python. But one thing my finge= rs really get tired of typing is >>> >>> someDoc[=E2=80=98_=E2=80=99id=E2=80=99] >>> >>> This just does not roll of the fingers well. Too many =E2=80=9Creach fo= r modifier keys=E2=80=9D in a row. I would rather use >>> >>> someDoc._id >>> >>> Googling shows that I=E2=80=99m not the first to want to do this in the= general sense (e.g. http://stackoverflow.com/questions/4984647/accessing-d= ict-keys-like-an-attribute-in-python). >>> >>> Arguments aside of whether this should or shouldn=E2=80=99t be done, I = want to know how I might solve this with Python. Consider it an academic pu= rsuit. >>> >>> The problem I have is not how to do the AttributeDictionary subclass, t= here are plenty of those examples. The problem is that the pymongo APIs alr= eady return dictionaries. In a language (Smalltalk, Objective-C, Ruby) that= supports class extensions, that would be my first tool of choice to solve = this problem. I=E2=80=99d just extend Dictionary to behave the way I want a= nd be done with it. I can=E2=80=99t do that in Python though. I guess I cou= ld make my own module that subclasses the relevant pymongo classes, and do = super() calling implementations of all of the relevant methods, coercing th= e return type. That is a maintenance headache though. >>> >>> What are my options, if any? >> >> You could construct the AttributeDictionary by copying the dict >> returned from pymongo. The question then is whether the copying would >> be too expensive or not. >> >> Alternately, you could just wrap the dictionaries returned by pymongo >> in an object. Something like this should be all you need: >> >> class AttrDictWrapper(object): >> def __init__(self, the_dict): >> self.__dict__ =3D the_dict >> >>>>> d =3D AttrDictWrapper({'spam': 42, 'ham': False}) >>>>> d.spam >> 42 >>>>> d.ham >> False >> > > Yes, that is clever. So if you wanted to minimize the amount of typing yo= u had to do at all of your pymongo API call sites, what strategy would you = use to keep that relatively terse? > > Is the following the right approach to take? > > class Doc(object): > def __init__(self, target): > self.__dict__ =3D target > > and then something like > > for doc in client.db.radios.find({=E2=80=99_id': {=E2=80=99$regex=E2=80= =99: =E2=80=98^[ABC]'}}): > pprint(doc) > > changes to > > for doc in ((Doc(d) for d in client.db.radios.find({=E2=80=99_id': {=E2= =80=99$regex=E2=80=99: =E2=80=98^[ABC]'}})): > pprint(doc) > > Are there other approaches? Feel free to impress me with evil abuses in t= he interest of academic enrichment... One possible approach, that may or may not work based on the use case, would be to monkey patch the module. The main drawback is that if it is used in places that do not expect the wrapper, it could well break. Untested code: import types def monkey(module): for attr in dir(module): if attr.startswith('_'): # Ignore private attributes. continue object =3D getattr(module, attr) if not isinstance(object, types.Function): # Ignore non-functions. continue setattr(module, attr, lambda *args, **kwargs: AttrDictWrapper(object(*args, **kwargs))) Another option would be to create a wrapper module. This option has the side-effect that it would be difficult to recurse into classes, as you would have to copy the class. This would be very similar (again, untested): import types # Note that this may get overridden in the function, which could cause problems. def makeWrapper(module): for attr in dir(module): if attr.startswith('_'): # Ignore private attributes. continue object =3D getattr(module, attr) if not isinstance(object, types.Function): # Ignore non-functions. object =3D lambda *args, **kwargs: AttrDictWrapper(object(*args, **kwargs)) globals()[attr] =3D object Both of these are written to (attempt) to replace all instances. Obviously, this could be reduced to a white-list or additional checks could be made to restrict the wrapped sets. I would be VERY weary of actually using the first due to the noted drawback. The second is safer, but you would want to be careful to test it for unexpected side-effects.