Path: csiph.com!fu-berlin.de!uni-berlin.de!not-for-mail From: Oscar Benjamin Newsgroups: comp.lang.python Subject: Re: using __getitem()__ correctly Date: Thu, 31 Dec 2015 12:12:43 +0000 Lines: 92 Message-ID: References: <56846ddf$0$1601$c3e8da3$5496439d@news.astraweb.com> Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable X-Trace: news.uni-berlin.de qrbpHH9rEyPWtMtCzTjvZA+Oqo28DbNs40w/6EZd3xYw== Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.001 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'failing': 0.05; 'attributes': 0.07; 'cc:addr:python-list': 0.09; "'name':": 0.09; 'dict': 0.09; 'falls': 0.09; 'kwargs': 0.09; 'lookup': 0.09; 'object;': 0.09; 'subclass': 0.09; 'subject:()': 0.09; 'subject:using': 0.09; 'itself.': 0.11; 'syntax': 0.13; 'def': 0.13; '...)': 0.16; 'attributes.': 0.16; 'cc:name:python list': 0.16; 'container.': 0.16; 'distinct': 0.16; 'distinction': 0.16; 'exhausted': 0.16; 'fallback': 0.16; 'received:io': 0.16; 'received:psf.io': 0.16; 'wrote:': 0.16; 'looked': 0.16; 'obviously': 0.16; 'attribute': 0.18; 'mechanism': 0.18; '>>>': 0.20; 'load': 0.20; '2015': 0.20; 'cc:2**0': 0.20; 'cc:addr:python.org': 0.20; 'keys': 0.22; 'referring': 0.22; 'trying': 0.22; 'implemented': 0.24; 'header:In-Reply-To:1': 0.24; "doesn't": 0.26; '(which': 0.26; 'message-id:@mail.gmail.com': 0.27; 'found.': 0.27; 'idea': 0.28; 'dictionary': 0.29; 'objects': 0.29; "i'm": 0.30; 'classes': 0.30; 'checks': 0.30; 'checked': 0.31; "can't": 0.32; 'skip:_ 10': 0.32; 'december': 0.32; 'generally': 0.32; 'class': 0.33; 'problem': 0.33; 'instances': 0.33; 'received:google.com': 0.35; 'could': 0.35; 'i.e.': 0.35; 'important.': 0.35; 'instance': 0.35; 'but': 0.36; 'there': 0.36; 'received:209.85': 0.36; 'possible': 0.36; '(i.e.': 0.36; 'skip:{ 10': 0.36; 'subject:: ': 0.37; 'method': 0.37; 'turn': 0.37; '(with': 0.38; 'associated': 0.38; 'difference': 0.38; 'no,': 0.38; 'virtual': 0.38; 'received:209': 0.38; 'names': 0.38; 'self': 0.38; 'means': 0.39; 'whatever': 0.39; 'does': 0.39; 'skip:x 10': 0.40; 'where': 0.40; 'your': 0.60; 'personally': 0.61; 'back': 0.62; 'different': 0.63; 'complete': 0.63; 'between': 0.65; 'subscript': 0.66; 'wanting': 0.66; 'talking': 0.67; 'skip:\xe2 10': 0.70; '8bit%:43': 0.72; 'covers': 0.72; 'special': 0.73; 'smith': 0.76; "'foo'}": 0.84; 'dict.': 0.84; 'oscar': 0.84; 'syntax;': 0.84 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :cc:content-type:content-transfer-encoding; bh=MItqtCTBzmK8ysk9csNBIMLkwQ02/4ph371x60O/dh8=; b=T0mBvsn5G3SkrbVjSA83amsR/bSfqx6MO/Ssr2XteA/rEyIISXA6Pj87T1xQcnEK9T DqVuxLn8Bi4djDYTS57YJ7zU2ADV4goF7VY4z3wV1IK/8JsyI2qSOzkTh5SUH36uTzKb oa4en9VrISkR7U7AEwNLOwjB3kGjQJ+Ksi0ZbiDNHxFas20ssQX3S8dzOyMJhQgWUcp6 oHfQyf8nK14mTpos9DVwFFJNIMs8E8DSMqO8HCZdXO/mAea8pIVKQ98Ool3zo+KvvU5d lL1z8tcSpvj64l765U2kvI3EiC7MabZqNopdfg+Dqyru5DfSz0bf2Y760DSHMOGIbMjY sriw== X-Received: by 10.112.181.196 with SMTP id dy4mr8386492lbc.42.1451563982918; Thu, 31 Dec 2015 04:13:02 -0800 (PST) In-Reply-To: X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.20+ Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Xref: csiph.com comp.lang.python:101052 On 31 December 2015 at 11:30, Charles T. Smith wrote: >>> Obviously there is a syntax difference between x.attr and x['key'] >> >> Not merely syntax; the attributes of an object are not generally >> available as items of the container. > > > What are the set of ways that an attribute is accessible? Including > implementation implications? > > >> >>> Either the instance __dict__, the class __dict__, or a superclass >>> __dict__. >> >> No, I'm not referring to the =E2=80=98__dict__=E2=80=99 attribute of an = object; I'm >> referring to the object itself. >> >> To talk about the attributes of an object =E2=80=98foo=E2=80=99 is disti= nct from talking >> about the items in a dictionary =E2=80=98foo=E2=80=99. That distinction = is real, and >> important. > > > But wanting to deal with the attributes of an object without considering > the way it's implemented - although possible - requires a complete virtua= l > model that covers all implications. It's easier to simply understand how= the > objects work under the covers. When you write x.attr the name 'attr' is looked up on the object x. This calls x.__getattribute__('attr'). In turn this checks the dict associated with the object x i.e. x.__dict__['attr']. This in turn calls x.__dict__.__getitem__('attr'). The lookup of x.__dict__ is special and doesn't use the normal __getattribute__ mechanism (otherwise this would be an infinite recursion). Generally special attributes (with double underscores) are looked up in a different way. If x.__dict__ does not have the attribute then the dict associated with the class/type of x is checked i.e. x.__class__.__dict__['attr']. The lookup of x.__class__ is also special. Failing this the other classes in x.__class__.__mro__ are checked i.e. x.__class__.__mro__[1].__dict__['attr']. Once these are exhausted x.__getattribute__('attr') falls back on calling x.__getattr__('attr'). IIUC you're trying to create an attribute dict where the same attributes can be looked up via x.attr or x['attr'] (i.e. x.__getattribute__('attr') or x.__getitem__('attr')). One way to do this is to subclass dict and then set each instances __dict__ to be itself. This way __getattribute__ will search the instance (which is a dict subclass) as its own attribute dict. You can then use __getattr__ as the fallback for attributes that are not found. A simple implementation of this could look like: class attrdict(dict): def __init__(self, name=3DNone): self.__dict__ =3D self kwargs =3D {'name':name} if name is not None else {} super(attrdict, self).__init__(**kwargs) def __getattr__(self, attrname): ob =3D self[attrname] =3D type(self)(name=3Dattrname) return ob >>> d =3D attrdict('foo') >>> d {'name': 'foo'} >>> d.bar {'name': 'bar'} >>> d {'bar': {'name': 'bar'}, 'name': 'foo'} The problem with this as with any dict subclass approach is that a dict has a load of methods (.items() .keys() .pop() ...) that will now become conflated with your dict keys when you use the attribute access: >>> d.items This means that you can't use any of the dict method names in whatever you're doing. This is the reason that a dict uses a different mechanism __getitem__ so that it can store arbitrary keys at the same time as having named methods which must be attributes. Personally I think that the best approach is to ditch the idea of conflating attributes and keys and just use the subscript x['attr'] syntax. -- Oscar