1

Is it possible to create a "constructor".. or rather "Initializer" to each function, instead of having to manually write it at the top of each function in class?

So, each time a function in a class is called, the other assigned function (unknown to caller) is always called first (called pre_check in below example).

An example using super(), but I then have to manually copy it inside each function.

class Helper():
    def pre_check(self):
        print("Helper fcn")


class Parent(Helper):
    def __init__(self):
        print("Initializer")

    def foo(self):
        super().pre_check()  # <---- new code
        # ... existing code here ...

    def bar(self):
        super().pre_check()  # <---- new code
        # ... existing code here ...

    def many_more_functions(self):
        super().pre_check()  # <---- new code
        # ... existing code here ...

m = Parent()

m.foo()
m.bar()

Note how __init__ in Parent is not supposed to run pre_check.

niCk cAMel
  • 869
  • 1
  • 10
  • 26
  • 3
    Perhaps you're looking for _decorators_, but you will still have to decorate the methods. Maybe there are class decorators –  Jun 13 '19 at 11:10
  • Your code seems to work fine. What you are looking for please clarify? – Amazing Things Around You Jun 13 '19 at 11:10
  • Possible duplicate of [How to decorate a class?](https://stackoverflow.com/questions/681953/how-to-decorate-a-class) –  Jun 13 '19 at 11:11
  • 3
    @AmazingThingsAroundYou I think OP would just prefer to not have to copy paste `super().pre_check()` part to each method. –  Jun 13 '19 at 11:12
  • 1
    unless `Parent` is a `Helper`, it should probably not inherit from `Helper`. – Reblochon Masque Jun 13 '19 at 11:17
  • @ReblochonMasque This is just an example. Could be a mixin. – Ocaso Protal Jun 13 '19 at 11:36
  • Are there any rules to what is and is not checked? How about ``_protected``, ``__private`` and ``__special__`` methods? – MisterMiyagi Jun 13 '19 at 11:56
  • @MisterMiyagi Since I don't even know how to implement required functionality without manually writing it in each function, I can't say much about restrictions. If a proposed solution requires certain rule, I think I'll just rather live with that – niCk cAMel Jun 13 '19 at 11:59
  • Yes @OcasoProtal, it could be... I am struggling with a use case. – Reblochon Masque Jun 13 '19 at 12:01
  • Great responses and answers all. I'm struggling to pick an answer since I can work any of them (more or less)... Why this does not have a more "simple" way of implementing is beyond me. I'll work more closely with the answers and see if any is more accurate than the other. Thanks again – niCk cAMel Jun 13 '19 at 12:25

3 Answers3

2

You can use a decorator for the class that will in turn decorate all public methods defined in the class:

def addhelper(helpmethod):
    def deco(cls):
        def decomethod(method):
            def inner(self, *args, **kwargs):
                helpmethod(self)
                return method(self, *args, **kwargs)
            # copy signature, doc and names from the original method
            inner.__signature__ = inspect.signature(method)
            inner.__doc__ = method.__doc__
            inner.__name__ = method.__name__
            inner.__qualname__ = method.__qualname__
            return inner
        # search all methods declared in cls with a name not starting with _
        for name, meth in inspect.getmembers(
            cls,lambda x: inspect.isfunction(x)
            and not x.__name__.startswith('_')
            and x.__qualname__.startswith(cls.__name__)):
            # replace each method with its decoration
            setattr(cls, name, decomethod(meth))
        return cls
    return deco

class Helper():
    def pre_check(self):
        print("Helper fcn")

@addhelper(Helper.pre_check)
class Parent(Helper):
    def __init__(self):
        print("Initializer")

    def foo(self):
#        super().pre_check()  # <----
        print('in foo')

    def bar(self):
#        super().pre_check()  # <----
        print('in bar')

    def many_more_functions(self):
#        super().pre_check()  # <----
        print('in many_more_functions')

We can now use it:

>>> p = Parent()
Initializer
>>> p.foo()
Helper fcn
in foo
>>> p.bar()
Helper fcn
in bar
>>> p.many_more_functions()
Helper fcn
in many_more_functions
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
1

You can use metaclass and define a decorator for each method in the instance of that metaclass

Code :

def decorate(f):
    def do_something(self, a):

        if (f(self, a) > 18) :
            return ("Eligible to vote")
        else :
            return ("Not eligible to vote")

    return do_something


class Meta(type):
    def __new__(cls, name, bases, namespace, **kwds):
        namespace = {k: v if k.startswith('__') else decorate(v) for k, v in namespace.items()}
        return type.__new__(cls, name, bases, namespace)


class MetaInstance(metaclass=Meta):

    def foo1(self, val):
        return val + 15

    def foo2(self, val):
        return val + 9

obj1 = MetaInstance()
print(obj1.foo1(5))
print(obj1.foo2(2))
Amartya Gaur
  • 665
  • 6
  • 21
  • Thanks, I've edited the question. The way I see your proposed answer decorates / replaces the entire function in `foo1` and `foo2`, nothing added inside them will execute !?. I like the filtering of methods starting with "__". – niCk cAMel Jun 13 '19 at 12:11
  • the decorator does not replace the function in foo1 and foo2, foo1 and foo2 accept the respective values and then returns the computed ones to the decorator which then looks at if the returned value (from the respective function) is greater than 18 or not and prints eligible or not eligible to vote – Amartya Gaur Jun 13 '19 at 12:17
1

Use __init_subclass__ to change subclasses as they are created. You can wrap the methods of subclasses:

class Helper():
    def __init_subclass__(cls):
        for field, value in cls.__dict__.items():
            # add additional checks as desired, e.g. exclude __special_methods__
            if inspect.isfunction(value) and not getattr(value, 'checked', False):
                setattr(cls, field, cls._check(value))  # wrap method

    @classmethod
    def _check(cls, fcn):
        """Create a wrapper to inspect the arguments passed to methods"""
        @functools.wraps(fcn)
        def checked_fcn(*args, **kwargs):
            print(fcn, "got", args, kwargs)
            return fcn(*args, **kwargs)
        return checked_fcn


class Parent(Helper):
    def __init__(self):
        print("Initializer")

    def foo(self):
        print("Foo")

Note that this will wrap all methods, including special methods such as __init__:

>>> Parent().foo()
<function Parent.__init__ at 0x1029b2378> got (<__main__.Parent object at 0x102c09080>,) {}
Initializer
<function Parent.foo at 0x1029b2158> got (<__main__.Parent object at 0x102c09080>,) {}
Foo

You can extend the check in __init_subclass__ with arbitrary rules to filter out functions. For example, field[:2] == field[-2:] == "__" excludes special methods.

MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119