Rocksolid Light

Welcome to novaBBS (click a section below)

mail  files  register  newsreader  groups  login

Message-ID:  

For God's sake, stop researching for a while and begin to think!


devel / comp.lang.python / Re: Dealing with non-callable classmethod objects

SubjectAuthor
o Re: Dealing with non-callable classmethod objectsCameron Simpson

1
Re: Dealing with non-callable classmethod objects

<mailman.932.1668206882.20444.python-list@python.org>

  copy mid

https://www.novabbs.com/devel/article-flat.php?id=20298&group=comp.lang.python#20298

  copy link   Newsgroups: comp.lang.python
Path: i2pn2.org!i2pn.org!aioe.org!news.uzoreto.com!fu-berlin.de!uni-berlin.de!not-for-mail
From: cs...@cskk.id.au (Cameron Simpson)
Newsgroups: comp.lang.python
Subject: Re: Dealing with non-callable classmethod objects
Date: Sat, 12 Nov 2022 09:47:57 +1100
Lines: 113
Message-ID: <mailman.932.1668206882.20444.python-list@python.org>
References: <b4954329-dded-fded-2dbb-035357ac55c1@gmail.com>
<Y27RHc8oJsin8pge@cskk.homeip.net>
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii; format=flowed
X-Trace: news.uni-berlin.de NBy4C94yFYzauErL5YpFcgTLFV5Ij9fMOa3rfCu/5CEw==
Return-Path: <cameron@cskk.id.au>
X-Original-To: python-list@python.org
Delivered-To: python-list@mail.python.org
Authentication-Results: mail.python.org; dkim=none reason="no signature";
dkim-adsp=none (unprotected policy); dkim-atps=neutral
X-Spam-Status: OK 0.000
X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'skip:@ 10': 0.03; 'def':
0.04; '(most': 0.05; 'containing': 0.05; 'last):': 0.05;
'correct?': 0.07; 'suggestion': 0.07; 'cc:addr:python-list': 0.09;
'else:': 0.09; 'obviously': 0.09; 'skip:_ 20': 0.09; 'skip:` 10':
0.09; 'skip:` 20': 0.09; 'yes.': 0.09; 'cheers,': 0.11; 'cc:no
real name:2**0': 0.14; '>>>>': 0.16; '@classmethod': 0.16;
'cameron': 0.16; 'directly,': 0.16; 'from:addr:cs': 0.16;
'from:addr:cskk.id.au': 0.16; 'from:name:cameron simpson': 0.16;
'message-id:@cskk.homeip.net': 0.16; 'nuances': 0.16; 'objects.':
0.16; 'received:13.237': 0.16; 'received:13.237.201': 0.16;
'received:13.237.201.189': 0.16; 'received:cskk.id.au': 0.16;
'received:id.au': 0.16; 'received:mail.cskk.id.au': 0.16;
'simpson': 0.16; 'skip:> 10': 0.16; 'static': 0.16; 'treats':
0.16; 'version."': 0.16; 'work:': 0.16; 'wrote:': 0.16; "aren't":
0.19; 'figure': 0.19; 'cc:addr:python.org': 0.20; "i've": 0.22;
'skip:_ 10': 0.22; 'version': 0.23; 'cc:2**0': 0.25; 'seems':
0.26; 'object': 0.26; 'bit': 0.27; 'function': 0.27; 'fact': 0.28;
'header:User-Agent:1': 0.30; 'seem': 0.31; 'module': 0.31;
"doesn't": 0.32; '"",': 0.32; '(as': 0.32; 'context': 0.32;
'happening': 0.32; 'modified': 0.32; 'objects': 0.32; 'but': 0.32;
"i'm": 0.33; 'there': 0.33; 'able': 0.34; 'header:In-Reply-To:1':
0.34; 'trying': 0.35; 'received:au': 0.35; 'functions': 0.36;
'couple': 0.37; "skip:' 10": 0.37; 'special': 0.37; 'class': 0.37;
'file': 0.38; 'way': 0.38; 'could': 0.38; 'put': 0.38; 'methods':
0.39; 'appears': 0.40; 'define': 0.40; 'situation': 0.40;
'reference': 0.60; 'method': 0.61; 'skip:b 30': 0.61; 'me.': 0.62;
'feel': 0.63; 'received:13': 0.64; 'your': 0.64; 'look': 0.65;
'well': 0.65; 'skip:t 20': 0.66; 'improve': 0.66; '100%': 0.66;
'received:userid': 0.66; 'skip:t 30': 0.67; 'change.': 0.69;
'analysis': 0.69; 'below': 0.69; 'deal': 0.73; 'skip:f 20': 0.75;
'discuss': 0.78; 'known': 0.84; 'factory': 0.84; 'mapping.': 0.84;
'method,': 0.84; 'thus,': 0.84; 'type.': 0.84; 'articles': 0.86
Mail-Followup-To: Ian Pilcher <arequipeno@gmail.com>,
python-list@python.org
Content-Disposition: inline
In-Reply-To: <b4954329-dded-fded-2dbb-035357ac55c1@gmail.com>
User-Agent: Mutt/2.2.7 (2022-08-07)
X-BeenThere: python-list@python.org
X-Mailman-Version: 2.1.39
Precedence: list
List-Id: General discussion list for the Python programming language
<python-list.python.org>
List-Unsubscribe: <https://mail.python.org/mailman/options/python-list>,
<mailto:python-list-request@python.org?subject=unsubscribe>
List-Archive: <https://mail.python.org/pipermail/python-list/>
List-Post: <mailto:python-list@python.org>
List-Help: <mailto:python-list-request@python.org?subject=help>
List-Subscribe: <https://mail.python.org/mailman/listinfo/python-list>,
<mailto:python-list-request@python.org?subject=subscribe>
X-Mailman-Original-Message-ID: <Y27RHc8oJsin8pge@cskk.homeip.net>
X-Mailman-Original-References: <b4954329-dded-fded-2dbb-035357ac55c1@gmail.com>
 by: Cameron Simpson - Fri, 11 Nov 2022 22:47 UTC

On 11Nov2022 15:29, Ian Pilcher <arequipeno@gmail.com> wrote:
>I am trying to figure out a way to gracefully deal with uncallable
>classmethod objects.

I'm just going to trim your example below a bit for reference purposes:

>class DUID(object):
> def __init__(self, d):
> for attr, factory in self._attrs.items():
> setattr(self, attr, factory(d[attr]))
> @classmethod
> def from_dict(cls, d):
> subcls = cls._subclasses[d['duid_type']]
> return subcls(d)
>
>class DuidLL(DUID):
> @staticmethod
> def _parse_l2addr(addr):
> return bytes.fromhex(addr.replace(':', ''))
> _attrs = { 'layer2_addr': _parse_l2addr }
>
>class DuidLLT(DuidLL):
> @classmethod
> def _parse_l2addr(cls, addr):
> return super()._parse_l2addr(addr)
> _attrs = {
> 'layer2_addr': _parse_l2addr,
> }

So what you've got is that `for attr, factory in self._attrs.items():`
loop, where the factory comes from the subclass `_attrs` mapping. For
`DuidLL` you get the static method `_parse_l2addr` object and for
`DuidLLT` you get the class method object.

[...]
>This works with static methods (as well as normal functions and object
>types that have an appropriate constructor): [...]
[...]
>
>It doesn't work with a class method, such as DuidLLT._parse_l2addr():
>
>>>>duid_llt = DUID.from_dict({ 'duid_type': 'DUID-LLT', 'layer2_addr': 'de:ad:be:ef:00:00', 'time': '2015-09-04T07:53:04-05:00' })
>Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> File "/home/pilcher/subservient/wtf/wtf.py", line 19, in from_dict
> return subcls(d)
> File "/home/pilcher/subservient/wtf/wtf.py", line 14, in __init__
> setattr(self, attr, factory(d[attr]))
>TypeError: 'classmethod' object is not callable
>
>In searching, I've found a few articles that discuss the fact that
>classmethod objects aren't callable, but the situation actually seems to
>be more complicated.
>
>>>> type(DuidLLT._parse_l2addr)
><class 'method'>
>>>> callable(DuidLLT._parse_l2addr)
>True
>
>The method itself is callable, which makes sense. The factory function
>doesn't access it directly, however, it gets it out of the _attrs
>dictionary.
>
>>>> type(DuidLLT._attrs['layer2_addr'])
><class 'classmethod'>
>>>> callable(DuidLLT._attrs['layer2_addr'])
>False
>
>I'm not 100% sure, but I believe that this is happening because the
>class (DuidLLT) doesn't exist at the time that its _attrs dictionary is
>defined. Thus, there is no class to which the method can be bound at
>that time and the dictionary ends up containing the "unbound version."

Yes. When you define the dictionary `_parse_l2addr` is an unbound class
method object. That doesn't change.

>Fortunately, I do know the class in the context from which I actually
>need to call the method, so I am able to call it with its __func__
>attribute. A modified version of DUID.__init__() appears to work:
>
> def __init__(self, d):
> for attr, factory in self._attrs.items():
> if callable(factory): # <============= ???!
> value = factory(d[attr])
> else:
> value = factory.__func__(type(self), d[attr])
> setattr(self, attr, value)

Neat!

>A couple of questions (finally!):
>* Is my analysis of why this is happening correct?

It seems so to me. Although I only learned some of these nuances
recently.

>* Can I improve the 'if callable(factory):' test above? This treats
> all non-callable objects as classmethods, which is obviously not
> correct. Ideally, I would check specifically for a classmethod, but
> there doesn't seem to be any literal against which I could check the
> factory's type.

Yeah, it does feel a bit touchy feely.

You could see if the `inspect` module tells you more precise things
about the `factory`.

The other suggestion I have is to put the method name in `_attrs`; if
that's a `str` you could special case it as a well known type for the
factory and look it up with `getattr(cls,factory)`.

Cheers,
Cameron Simpson <cs@cskk.id.au>

1
server_pubkey.txt

rocksolid light 0.9.81
clearnet tor