1

I want to extract the - the name of the "current" methode - the class name of the current object/instance - the class name of the next base class of that object/instance

The problem in the example below is that the methode-name is not really that what I want. But I need to make that code reusable for a lot of classes. I don't want to multiply that code for each class I need it for.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys, inspect

def notimpl_str(obj):
    classname = obj.__class__.__name__

    methode = sys._getframe().f_code.co_name

    hi = inspect.getmro(obj.__class__)
    nistr = 'In {}.{}()\nLook in {}.{}()' \
        .format(classname,
                methode,
                hi[1].__name__,
                methode)

    return nistr


class Foo:
    def test(self):
        print(notimpl_str(self))

class Xoo(Foo):
    def __init__(self):
        self.test()

Xoo()

Background of my question: I want to implement a generic not-implemented-error-message if someone would call Xoo().test(). The message could like be Please implement Xoo.test(). See the documentation of Foo.test() for more details.

buhtz
  • 10,774
  • 18
  • 76
  • 149

3 Answers3

3

This is far away from perfect but a little bit more near to "generic". ;)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys, inspect

def create_niestr(obj,
                  abstract_class,
                  methode):
    classname = obj.__class__.__name__
    hi = inspect.getmro(obj.__class__)
    this_method = getattr(obj, methode)
    base_method = getattr(abstract_class, methode)

    if classname is not abstract_class.__name__ and this_method.__func__ is base_method:
        nistr = 'Please implement {}.{}(). See the documentation of {}.{}() for more details.' \
                .format(classname, methode, hi[1].__name__, methode)
    else:
        nistr = 'Normal execution.'

    return nistr


class Foo:
    def test(self):
        nistr = create_niestr(self,
                              Foo,
                              sys._getframe().f_code.co_name)
        print(nistr)


class Xoo(Foo):
    pass

class Zoo(Foo):
    def test(self):
        super(self.__class__, self).test()

Foo().test()
Xoo().test()
Zoo().test()
buhtz
  • 10,774
  • 18
  • 76
  • 149
1

I think this is what you are looking for. I have added an additional parameter 'Inheritance hierarchy'.

import sys
import inspect

class Foo(object):
    def __init__(self):
        flag = 0
        print("Subclasses:", end=" ")
        for subclass in self.__class__.__subclasses__(): #prints the subclasses
            print(subclass.__name__, end=" ")
            flag = 1
        if flag == 0:
            print(None)
        else:
            print()
        print("Class name: ", self.__class__.__name__) #prints the class name
        print("Method name: ", sys._getframe().f_code.co_name) #prints the method name
        a = inspect.getmro(self.__class__)
        flag = 0
        print("Inheritance Hierarchy:", end=" ")
        for i in range(len(a)): #prints the inheritance hierarchy
            if not i == 0:
                print(a[(len(a)-1)-i].__name__, end=" ")
                flag = 1
        if flag == 0:
            print(None)
        else:
            print("\n")

class Xoo(Foo):
    def __init__(self):
        flag = 0
        print("Subclasses:", end=" ")
        for subclass in self.__class__.__subclasses__(): #prints the subclasses
            print(subclass.__name__, end=" ")
            flag = 1
        if flag == 0:
            print(None)
        else:
            print()
        print("Class name: ", self.__class__.__name__) #prints the class name
        print("Method name: ", sys._getframe().f_code.co_name) #prints the method name
        a = inspect.getmro(self.__class__)
        flag = 0
        print("Inheritance Hierarchy:", end=" ")
        for i in range(len(a)): #prints the inheritance hierarchy
            if not i == 0:
                print(a[(len(a)-1)-i].__name__, end=" ")
                flag = 1
        if flag == 0:
            print(None)
        else:
            print("\n")

class Zoo(Xoo):
    def __init__(self):
        flag = 0
        print("Subclasses:", end=" ")
        for subclass in self.__class__.__subclasses__(): #prints the subclasses
            print(subclass.__name__, end=" ")
            flag = 1
        if flag == 0:
            print(None)
        else:
            print()
        print("Class name: ", self.__class__.__name__) #prints the class name
        print("Method name: ", sys._getframe().f_code.co_name) #prints the method name
        a = inspect.getmro(self.__class__)
        flag = 0
        print("Inheritance Hierarchy:", end=" ")
        for i in range(len(a)): #prints the inheritance hierarchy
            if not i == 0:
                print(a[(len(a)-1)-i].__name__, end=" ")
                flag = 1
        if flag == 0:
            print(None)
        else:
            pass

Foo()
Xoo()
Zoo()

Running the above code, you get the following output:

Subclasses:  Xoo
Class name:  Foo
Method name:  __init__
Inheritance Hierarchy:  Foo 

Subclasses:  Zoo
Class name:  Xoo
Method name:  __init__
Inheritance Hierarchy:  Foo Xoo 

Subclasses:  None
Class name:  Zoo
Method name:  __init__
Inheritance Hierarchy:  Foo Xoo Zoo
Swastik Padhi
  • 1,849
  • 15
  • 27
  • Maybe this can help. I will check later. But there are some understanding problems. The first instance `Foo()`hast no subclass but return `Xoo` as a subclass. But the instance never derived to this subclass. It is just `Foo()`. Maybe I should ask more exact for the "class of the sub-instance". See the difference? – buhtz Jun 26 '15 at 20:13
  • 1
    well, it seems your are mixing things up. `Foo` indeed has a subclass named `Xoo` as described by your question and as implemented by me. Also, you have not made it clear, do you want a method to print all derived classes of the current class or the base class of the current class? In either case, my code would be apt as you can see it prints both **subclasses** and the **inheritance hierarchy** of a given class. However, if this is not what you want then you might consider editing the question and adding more detail to it. – Swastik Padhi Jun 26 '15 at 23:06
  • Re-edit the subject. The first Foo() instance has nothing to do with Xoo or Zoo and know nothing about it. btw: Does your code run with Python3? – buhtz Jun 27 '15 at 08:19
  • 1
    @buhtz I coded it on python 2.7 Porting to python 3 is rather simple, just the print statements have to be changed. Anyways, I have updated my answer. Now it works with python 3. About your edited question, I could not understand this part- **"the class name of the next base class of that object/instance"**. Please explain. – Swastik Padhi Jun 27 '15 at 19:50
  • Of course I know that a class is in Python just an object. But this doesn't help here. Look on it in the C++-way. A class is just a blueprint nothing more! So see: `Foo()`is a instance of `class Foo`. For `Foo()` doesn't it matter that maybe anohter instance (maybe `Xoo()`) derived from `class Foo`. Next step: `Xoo()` is a instance of `class Xoo(Foo)`. A call to `Xoo().test()` will result in a `Foo().test()` because `Xoo().test()` is not implemented. NOW: `Foo().test()` need to know that it is a instance of `class Xoo(Foo)` and present the user a nice readable not-implemented-message. – buhtz Jun 28 '15 at 12:36
  • 1
    @buhtz There you are. Now you have made your question clear. Let me think about it. – Swastik Padhi Jun 28 '15 at 19:51
  • 1
    @buhtz check out the new answer that i have posted. Reworked part of your code, which happens to be part of my original code. ;) – Swastik Padhi Jun 29 '15 at 01:37
1

Check this out!

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys, inspect

class Foo:
    def test(self):
        classname = self.__class__.__name__
        methode = sys._getframe().f_code.co_name
        hi = inspect.getmro(self.__class__)
        this_method = getattr(self, 'test')
        base_method = getattr(Foo, 'test') #here 'Foo' is the base class and so this value has to be hard-coded for every base class inside which you paste this code
        if classname is not "Foo" and this_method.__func__ is base_method:
            nistr = 'Please implement {}.{}(). See the documentation of {}.{}() for more details.'\
            .format(classname, methode, hi[1].__name__, methode)
            print(nistr)
        else:
            print("Normal execution.") #put the actual code for the 'test' method here

class Xoo(Foo):
    pass

class Zoo(Foo):
    def test(self):
        super(self.__class__, self).test()

Foo().test() #test has been defined so normal execution
Xoo().test() #test has not been defined so a message is shown
Zoo().test() #test has been defined, even if it is just for calling the base class's 'test' method, it is still a valid definition so normal execution

Output-

Normal execution.
Please implement Xoo.test(). See the documentation of Foo.test() for more details.
Normal execution.

About implementing a generic method as you have shown in the question, well, I would say that will create a lot of confusion and may not be even possible as the value of self cannot contain the calling object as well as the called object. Also, if your call another method for checking this, then sys._getframe().f_code.co_name will no longer return the original method's name, which is what you want. So the best way would be to add this piece of code to every base class.

Swastik Padhi
  • 1,849
  • 15
  • 27
  • Very nice. And I understand the problems about the generic methode. Isn't there something like a placeholder in Python: like #define in C++ I could use here? I wonder why Python doesn't handle the fact that "abstract" classes may exist. – buhtz Jun 29 '15 at 16:46
  • @buhtz python indeed has support for `abstract classes`. Check this out- https://docs.python.org/3/library/abc.html Also you have support for `global variables` (http://stackoverflow.com/questions/423379/using-global-variables-in-a-function-other-than-the-one-that-created-them) which might help you in easily accessing the various parameters needed inside the generic function. About defining `constants`, here's the **trick!** - http://code.activestate.com/recipes/65207-constants-in-python/?in=user-97991 – Swastik Padhi Jun 30 '15 at 00:09