0

After reading Various errors in code that tries to call classmethods, Why are python static/class method not callable?, Are staticmethod and classmethod in python not callable? and others it is clear that Python class methods are not callable. The "why" does not seem to be answered that well and just saying that it is not needed is plainly not true as manifested by numerous questions on this topic.

Anyway, I have a XML with many different tags used in the same position each requiring fundamentally similar, but different nonetheless, handling. My idea was to have a base class and subclasses for the individual cases, each able to parse its own part (element) and operate with these. Like this (simplified):

import xml.etree.ElementTree as ET


class base():        
    parsers = dict()
    @classmethod
    def parseXml(cls, x:str):
        parser = base.parsers.get(x)
        parser(cls, x)  # different class, but same base (and all called cls elements are only in base class)
                # or parser(el)


class derivedOne(base):
    def __init__(self):
        self.one = None
    @classmethod
    def parseXml(cls, x:str):

        d1 = derivedOne()
        d1.one = 'one+'+x
    
    base.parsers['xmlTagOne']=parseXml 


class derivedTwo(base):
    def __init__(self):
        self.two = None
    @classmethod
    def parseXml(cls, x:str):
        d2 = derivedTwo()
        d2.two = 'two+'+x

    base.parsers['xmlTagTwo']=parseXml

    if __name__ == "__main__":
        base.parseXml('xmlTagOne')
        base.parseXml('xmlTagTwo')

The parser is found, but not callable.

Of course all the constructors could be called from one place with some if/else logic, but that is not very elegant and most importantly it does not allow (as I planned) to add additional handler/parser classes without changing the base class...

Is there any way to keep this "add parsing of new tag in single class"?

Lukas
  • 2,232
  • 3
  • 21
  • 34
  • 3
    I'm confused by the questions you have linked. If I define a class `C` with a classmethod `themethod`, then `callable(C.themethod)` returns `True` and calling `C.themethod()` clearly works. – mkrieger1 Aug 24 '20 at 19:58
  • Can you show a [mre] of how you try to use this code and what goes wrong? – mkrieger1 Aug 24 '20 at 20:00
  • 2
    Why are you writing classes and classmethods to begin with? Can you not write normal functions? – mkrieger1 Aug 24 '20 at 20:05
  • `derivedOne.parseXML` is callable because that is really evaluating to `derivedOne.__dict__['parseXML'].__get__(derivedOne, type)`. The `classmethod` object is *not* callable, but the object returned by the `classmethod`'s `__get__` method is. – chepner Aug 24 '20 at 20:05
  • Don't use the `classmethod` as you have learned, they are not callable. So outside the class just do `base.parsers['xmlTagOne']=derivedOne.parseXm` – juanpa.arrivillaga Aug 24 '20 at 20:05
  • @mkrieger1: The problem is that I do not know the class that should be called. - it is not known whether it should be C, D, or something else where it is called – Lukas Aug 24 '20 at 20:12
  • @juanpa.arrivillaga: but then I have to determine somehow if derivedOne or derivedTwo should be used outside these classes and that would defy the whole purpose – Lukas Aug 24 '20 at 20:17
  • @mkrieger1: I am writing classes not just to parse the xml, but to have these classes available for further processing and manipulations. Reasoning between classes and methods versus functions and some structure of nested lists seems quite off topic of the question. – Lukas Aug 24 '20 at 20:20
  • Wait, don't you mean `base.parsers['xmlTagOne']()` rather than `base.parseXML('xmlTagOne')`? (In which case, you probably want to store the *class*, not its class method, in the `dict` and use `base.parsers['xmlTagOne'].parseXML()`.) – chepner Aug 24 '20 at 20:21
  • @chepner: xx = base.parsers[x].__get__(cls,base); return xx(x) works fine (just class vaariables are used of base class and not of the derived one, but that is not a real problem – Lukas Aug 24 '20 at 20:42
  • @chepner: Storing the class would be perfect if only possible to store the class to somewhere while defining it, not after it (but after consideration even after is quite ok) – Lukas Aug 24 '20 at 20:49
  • @Lukas huh? No, I mean literally un-indent those last lines in your class defintion and right below do that, so for the first class `base.parsers['xmlTagOne']=derivedOne.parseXm` and for the second `base.parsers['xmlTagTwo']=derivedTwo.parseXm`. You wouldn't have to know any more than writing what you wrote inside the class definition – juanpa.arrivillaga Aug 24 '20 at 21:10
  • @juanpa.arrivillaga: yes that works too :) – Lukas Aug 24 '20 at 21:13
  • @mkrieger1 That's because doing `C.themethod` implicitly invokes the descriptor protocol (`__get__`), producing a bound method (the same type as when looking up an ordinary instance method from an instance). Using more direct access techniques, like `vars` and `getattr`, bypass that, and give the actual `classmethod` object. – Karl Knechtel Jul 24 '23 at 21:37

1 Answers1

2

You can use __init_subclass__ to store a reference to a new subclass in base.parsers.

class Base:        
    parsers = dict()
    @classmethod
    def parseXml(cls, x:str):
        parser = base.parsers.get(x)
        parser(cls, x)

    def __init_subclass__(cls, /, tag, **kwargs):
        super().__init_subclass__(**kwargs)  # removed ...(cls, ...
        Base.parsers[tag] = cls


class DerivedOne(Base, tag="xmlTagOne"):
    def __init__(self):
        self.one = None

    @classmethod
    def parseXml(cls, x:str):
        ...


class DerivedTwo(Base, tag="xmlTagTwo"):
    def __init__(self):
        self.two = None
    @classmethod
    def parseXml(cls, x:str):
        ...


if __name__ == "__main__":
    base.parsers['xmlTagOne'].parseXml(some_string)
    base.parsers['xmlTagTwo'].parseXml(some_other_string)

Base.__init_subclass__ is called after immediately after the subclass is created, with any keyword arguments in the base class list passed through to __init_subclass__. Each subclass is saved to Base.parsers with the specified tag.


Note: I'm ignoring the issue of whether you should use classes and class methods at all here.

Lukas
  • 2,232
  • 3
  • 21
  • 34
chepner
  • 497,756
  • 71
  • 530
  • 681