I’ll post a fuller example. My tiny example above is sort of to show how little I understand here.
Before getting into the fuller example, some questions still outstanding to me:
- if the class
__dict__
is read only, how is it that I can do a cls.__name__ = 'new name here'
at all?
- that clearly sets something, because looking up
.__name__
later finds the new value, but not just on cls
- also on the wrapper class
In the real class I’m not making a __name__
class attribute directly in the class definition, I’m going, essentially:
name0 = cls.__name__
..............
class BinClass(cls, AbstractBinary):
_baseclass = cls
..............
BinClass.__name__ = name0
cls.__name__ = f'{name0}__original}
where BinClass
is the new wrapper class I’m making.
So. At the bottom of the decorator is this code:
cls.name0 = name0
cls.__name__ = f'{name0}__original'
assert BinClass._dataclass is dcls
assert BinClass._dataclass.__name__.startswith(cls.name0)
BinClass._check()
BinClass.__name__ = name0
assert BinClass.__name__ != 'BinClass'
X("@binclass: returning %s %r", BinClass, BinClass.__name__)
X(" BinClass %d:%s", id(BinClass), BinClass.__name__)
X(" cls %d:%s", id(cls), cls.__name__)
X(" dcls %d:%s", id(dcls), dcls.__name__)
assert BinClass._baseclass is cls
assert BinClass._dataclass is dcls
global LastBinClass
LastBinClass = BinClass
return BinClass
There’s some extraneous stuff there, but best to show everything. Notes:
cls
is the wrapped class
dcls
is a dataclass I’ve made from the cls.__annotations
BinClass
is the wrapper class; it subclasses cls
in order to let cls
provide some methods
X()
is a debugging function, essentially a print
LastBinClass
is a global I set up entirely so that I can inspect it from another module after calling the decorator - just for debugging
The output from the X()
calls is like this:
@binclass: returning <class 'cs.binary.binclass.<locals>.BinClass'> 'FullBoxBody2'
BinClass 140252895848048:FullBoxBody2
cls 140252895825072:FullBoxBody2__original
dcls 140252895826704:FullBoxBody2__dataclass
Here the .__name__
attributes seem correct.
Now the fun begins.
I’m calling the @binclass
decorator from inside a decorator in another module, shown below. The additional decorator is so that I can supplant a registration of the wrapped class with the new wrapper class. Thus:
@boxbodyclass
class FullBoxBody2(BoxBody):
''' A common extension of a basic `BoxBody`, with a version and flags field.
ISO14496 section 4.2.
'''
version: UInt8
flags0: UInt8
flags1: UInt8
flags2: UInt8
The second decorator is this:
def boxbodyclass(cls):
''' A decorator for `@binclass` style `BoxBody` subclasses
which reregisters the new binclass in the
`BoxBody.SUBCLASSES_BY_BOXTYPE` mapping.
'''
if not issubclass(cls, BoxBody):
raise TypeError(f'@boxbodyclass: {cls=} is not a subclass of BoxBody')
cls0 = cls
cls = binclass(cls0)
X("@boxbodyclass:")
import cs.binary
LastBinClass = cs.binary.LastBinClass
X(" LastBinClass %d:%s", id(LastBinClass), LastBinClass.__name__)
X(" cls0 %d:%s", id(cls0), cls0.__name__)
X(" cls %d:%s", id(cls), cls.__name__)
BoxBody._register_subclass_boxtypes(cls, cls0)
breakpoint()
return cls
The output here looks like this:
@boxbodyclass:
LastBinClass 140252895848048:FullBoxBody2__original
cls0 140252895825072:FullBoxBody2__original
cls 140252895848048:FullBoxBody2__original
Suddenly the .__name__
attributes all have the same value.
I find this very confusing.