简体   繁体   中英

Issues passing self to class decorator in python

I am new to decorators but ideally I wan to use them to simply define a bunch of class functions within class OptionClass, each representing some particular option with a name and description and if it's required. I don't want to modify the operation of the class function at all if that makes sense, I only want to use the decorator to define name, description, and if it's required.

Problem 1: I construct an OptionClass() and I want to call it's option_1. When I do this I receive a TypeError as the call decorator is not receiving an instance of OptionClass. Why is this? When I call option_1 passing the instance of OptionClass() it works. How do I call option_1 without needing to always pass the instance as self. The error when received is:

Traceback (most recent call last):
  File "D:/OneDrive_P/OneDrive/projects/python/examples/dec_ex.py", line 110, in <module>
    print(a.option_1("test")) # TypeError: option1() missing 1 required positional argument: 'test_text'
   File "D:/OneDrive_P/OneDrive/projects/python/examples/dec_ex.py", line 80, in __call__
    return self.function_ptr(*args, **kwargs)
TypeError: option_1() missing 1 required positional argument: 'test_text'

Problem 2: How would I run or call methods on the decorator to set_name, set_description, set_required?

Problem 3: Although this is a sample I intend to code an option class using async functions and decorate them. Do I need to make the decorator call be async def __call__() or is it fine since it's just returning the function?

class option_decorator(object):
    def __init__(self, function_pt):
        self.function_ptr = function_pt
        self.__required = True
        self.__name = ""
        self.__description = ""

    def set_name(self, text):
        self.__name = text

    def set_description(self, text):
        self.__description = text

    def set_required(self,flag:bool):
        self.__required = flag

    def __bool__(self):
        """returns if required"""
        return self.__required

    def __call__(self, *args, **kwargs):
        return self.function_ptr(*args, **kwargs)

    def __str__(self):
        """prints a description and name of the option """
        return "{} - {}".format(self.__name, self.__description)


class OptionClass(object):
    """defines a bunch of options"""
    @option_decorator
    def option_1(self,test_text):
        return("option {}".format(test_text))

    @option_decorator
    def option_2(self):
        print("option 2")

    def get_all_required(self):
        """would return a list of option functions within the class that have their decorator required flag set to true"""
        pass

    def get_all_available(self):
        """would return all options regardless of required flag set"""
        pass

    def print_all_functions(self):
        """would call str(option_1) and print {} - {} for example"""
        pass

a = OptionClass()
print(a.option_1("test")) # TypeError: option1() missing 1 required positional argument: 'test_text'
print(a.option_1(a,"test")) #Prints: option test

Problem 1

You implemented the method wrapper as a custom callable instead of as a normal function object. This means that you must implement the __get__() descriptor that transforms a function into a method yourself. (If you had used a function this would already be present.)

from types import MethodType


class Dec:
 def __init__(self, f):
     self.f = f

 def __call__(self, *a, **kw):
     return self.f(*a, **kw)

 def __get__(self, obj, objtype=None):
     return self if obj is None else MethodType(self, obj)


class Foo:
    @Dec
    def opt1(self, text):
        return 'foo' + text

>>> Foo().opt1('two')
'footwo'

See the Descriptor HowTo Guide

Problem 2

The callable option_decorator instance replaces the function in the OptionClass dict. That means that mutating the callable instance affects all instances of OptionClass that use that callable object. Make sure that's what you want to do, because if you want to customize the methods per-instance, you'll have to build this differently.

You could access it in class definition like

class OptionClass(object):
    """defines a bunch of options"""
    @option_decorator
    def option_1(self,test_text):
        return("option {}".format(test_text))

    option_1.set_name('foo')

Problem 3

The __call__ method in your example isn't returning a function. It's returning the result of the function_ptr invocation. But that will be a coroutine object if you define your options using async def , which you would have to do anyway if you're using the async/await syntax in the function body. This is similar to the way that yield transforms a function into a function that returns a generator object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM