<class '__main__.DuidLL'>duid_ll = DUID.from_dict({ 'duid_type': 'DUID-LL', 'layer2_addr': 'de:ad:be:ef:00:00' })
type(duid_ll)
'DUID-LL'duid_ll.duid_type
b'\xde\xad\xbe\xef\x00\x00'duid_ll.layer2_addr
Traceback (most recent call last):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' })
<class 'method'>type(DuidLLT._parse_l2addr)
Truecallable(DuidLLT._parse_l2addr)
<class 'classmethod'>type(DuidLLT._attrs['layer2_addr'])
Falsecallable(DuidLLT._attrs['layer2_addr'])
I am trying to figure out a way to gracefully deal with uncallable >classmethod objects.
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,
}
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():
Traceback (most recent call last):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' })
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.
<class 'method'>type(DuidLLT._parse_l2addr)
Truecallable(DuidLLT._parse_l2addr)
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.
<class 'classmethod'>type(DuidLLT._attrs['layer2_addr'])
Falsecallable(DuidLLT._attrs['layer2_addr'])
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."
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)
A couple of questions (finally!):
* Is my analysis of why this is happening correct?
* 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.
...
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.
<class 'method'>type(DuidLLT._parse_l2addr)
Truecallable(DuidLLT._parse_l2addr)
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.
<class 'classmethod'>type(DuidLLT._attrs['layer2_addr'])
Falsecallable(DuidLLT._attrs['layer2_addr'])
On 11Nov2022 15:29, Ian Pilcher <arequipeno@gmail.com> wrote:
* 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)`.
On 11Nov2022 15:29, Ian Pilcher <arequipeno@gmail.com> wrote:
* 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)`.
So I've done this.
class _HasUnboundClassMethod(object):
@classmethod
def _classmethod(cls):
pass # pragma: no cover
_methods = [ _classmethod ]
_ClassMethodType = type(_HasUnboundClassMethod._methods[0])
Which allows me to do this:
def __init__(self, d):
for attr, factory in self._attrs.items():
if callable(factory):
value = factory(d[attr])
else:
assert type(factory) is self._ClassMethodType
value = factory.__func__(type(self), d[attr])
setattr(self, attr, value)
It's a bit cleaner, although I'm not thrilled about having a throwaway
class, just to define a literal that ought to be provided by the
runtime.
Use the inspect module as Cameron suggested.
# replace fctory with a function calling factory.__func__
factory = lambda arg: factory.__func__(classmethod, arg)
You shouldn't need a throwaway class, just use the name "classmethod" directly - it's the type!
if not callable(factory):
if type(factory) is classmethod:
# replace fctory with a function calling factory.__func__
factory = lambda arg: factory.__func__(classmethod, arg)
else:
raise TypeError("unhandled factory type %s:%r" % (type(factory), factory)
value = factory(d[attr])
On 13Nov2022 07:57, Cameron Simpson <cs@cskk.id.au> wrote:
# replace fctory with a function calling factory.__func__
factory = lambda arg: factory.__func__(classmethod, arg)
It just occurred to me that you might need to grab the value of >factory.__func__ first:
factory0 = factory
factory = lambda arg: factory0.__func__(classmethod, arg)
Otherwise the closure might introduce a recursion.
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 300 |
Nodes: | 16 (2 / 14) |
Uptime: | 63:41:13 |
Calls: | 6,712 |
Files: | 12,244 |
Messages: | 5,355,976 |