从父类获取子类的初始化参数

huangapple go评论53阅读模式
英文:

Get init arguments of child class from parent class

问题

ann 类中的 getInitInpArgs 方法从 anninit 方法中获取输入参数并将其存储在字典中。现在我想定义一个类似于 getInitInpArgs 方法的函数,但只在 ann(父类)上定义,而不在子类上定义,该函数将创建一个包含 myAnn(子类)的 init 方法的输入参数的字典。

import inspect

class ann():
    def __init__(self, arg1):
        super(ann, self).__init__()
        self.getInitInpArgs()

    def getInitInpArgs(self):
        args, _, _, values = inspect.getargvalues(inspect.currentframe().f_back)
        self.inputArgs = {arg: values[arg] for arg in args if arg != 'self'}

class myAnn(ann):
    def __init__(self, inputSize, outputSize):
        super(myAnn, self).__init__(4)

z1 = myAnn(40, 1)
print(z1.inputArgs)

因此,在这里,z1.inputArgs 可能等于 { 'arg1': 4, 'inputSize': 40, 'outputSize': 1 } 或等于 { 'inputSize': 40, 'outputSize': 1 },但现在 z1.inputArgs 等于 { 'arg1': 4 }

英文:

getInitInpArgs on ann gets the input arguments in init of ann to a dictionary. Now I want to define a function like getInitInpArgs on ann (parent class) and not on the child classes, which makes a dictionary of input arguments of init of myAnn(child class).

import inspect

class ann():
    def __init__(self, arg1):
        super(ann, self).__init__()
        self.getInitInpArgs()

    def getInitInpArgs(self):
        args, _, _, values = inspect.getargvalues(inspect.currentframe().f_back)
        self.inputArgs = {arg: values[arg] for arg in args if arg != 'self'}

class myAnn(ann):
    def __init__(self, inputSize, outputSize):
        super(myAnn, self).__init__(4)

z1=myAnn(40,1)
print(z1.inputArgs)

So here we either would have z1.inputArgs equal to {'arg1': 4, 'inputSize':40, 'outputSize':1} or equal to {'inputSize':40, 'outputSize':1} but now z1.inputArgs is equal to {'arg1': 4}.

答案1

得分: 3

关于你试图做的方式:

不可以。

不确定你在尝试做什么,但这是错误的方式。

在Python编码中,实例化类和子类,并对传递的参数进行注释是基本功能。

检查调用方框架范围内的变量虽然可能并且在Python中有记录,但它是一种高级特性,虽然可以在需要时使用,但不应该要求做基本操作 - 这是一种非凡的技能,因此应该保留用于非凡的事物。可以实现这一点的好处之一是编写半神奇的库框架,允许更基本和简单地使用ORM、测试框架、分析等。

但这不是轻率使用的资源,也不是用于检查传递给子类的参数 - 这只是更好理解参数和变量处理方式的问题,并相应地编写代码。

这里的问题是:“不符合预期”的代码部分,你希望getInitArgs方法自动标注所有传递给__init__的参数。但你通过使用语言中的一个超级高级特性来实现这一点,即检查函数中传递的参数的Frame对象 - 然后你总是选择当前函数调用getInitArgs的前一帧 - 即inspect.currentframe().f_back硬编码为选择调用getInitArgs的函数的帧 - 但这个函数是父类中的方法 - 此时子类的__init__已经被调用两次了(可以通过执行inspect.currentframe().f_back.f_back来访问),正如你所看到的,这在大型项目中是不可行的:你需要一个复杂的逻辑来检查初始__init__有多远,以从“正确”的__init__获取参数,并且会有很多边缘情况。

应该做的是:

相反,对于你编写一个机制来注释所有传递的参数,即使你在一个对子类的参数一无所知的超类中,有一个“中间”的Python特性,那就是“关键字参数”。

要实现你想要的功能:

如果根本不想这样做,另一种方法是使每个子类中的每个__init__被装饰上一些代码,该代码将对初始参数进行注释,然后再调用适当的__init__函数。这可以通过__init_subclass__特殊方法完成 - 我认为这比你的方法更具可维护性。

英文:

about "how" you are trying to do it:

no.

Not sure what you are trying to do there, but this is the wrong way.

Instantiating classes and subclasses, and annotating passed arguments is a basic function of coding in Python.

Inspecting the variables in the scope of caller frames, while possible and documented in Python is an advanced feature, that while perfectly ok to be used when needed, should not be required to do the basic - it is an extraordinary feat, and as so, it should be reserved for extraordinary things. One of the benefits of it being possible is that writing semi-magical libraries frameworks which allow the basic use of ORMs, test frameworks, profiling, and such to be even more basic and easy.

But it is not a resource to be used lightly, and not for checking arguments passed to a child class - this is just a matter of understanding better the way parameters and variables are dealt with, and write your code accordingly.

Here is what is wrong

So, what is "not expected" in your code, is that you'd expect the getInitArgs method to anotate automatically all the parameters passed to __init__. But you do that by using a super-advanced feature in the language to inspect the arguments passed in a function in a Frame object - and then you always pick the frame prior to the current one - i.e. - inspect.currentframe().f_back is hardcoded to pick the frame of the function which called getInitArgs - but this function is the method in the parent class - the __init__ of the child class is two levels removed at this point (it could be accessed by doing inspect.currentframe().f_back.f_back) As you can see, that is not feasible in large projects: you'd need a complicated logic there just to check how far removed the initial __init__ is to get the arguments from the "correct" __init__, and it would have a lot of edge cases.

what should be done:

Instead, for you write a mechanism to annotate all passed arguments, even when you are in a superclass that knows nothing about the argument names in the child classes, there is an "intermediate" Python feature which are the "keyword arguments" -
For a coincidence, I wrote a lengthy answer explaining their working, and the "one obvious way" to retrieve different initialization arguments in a large class hierarchy last week - so I will refer you there:

https://stackoverflow.com/questions/76259238/python-hybrid-inheritance/76328151#76328151

To do exactly what you are trying to - but without frame introspection -

That answer talks about the common way of dealing with several child-classes, each having its own specific parameters - but on the more common pattern, there is not a "catch all" - each __init__ method would know about, and take care, of the parameters it is concerned with. (But check Python's dataclasses, about generating the __init__ methods automatically for that: https://docs.python.org/3/library/dataclasses.html )

getting the exact functionality you are expecting:

If that is not desired at all, another way to do it is to have each __init__ in each child-class to be decorated with some code that would annotate the initial parameters, and just then call the __init__ function proper. That can be done with the __init_subclass__ special method - I think it is more maintainable than your approach there:

from functools import wraps

# class and method names normalized to a more usual 
# Python naming scheme 

class Base:

    @staticmethod
    def get_initial_args_wrapper(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            # This code will run automatically before each __init__ method
            # in each subclass: 
            
            # if you want to annotate arguments passed in order,
            # as just "args", then you will indeed have to resort
            # to the "inspect" module - 
            # but, `inspect.signature`, followed by `signature.bind` calls 
            # instead of dealing with frames.
    
            # for named args, this will just work to annotate all of them:
            input_args = getattr(self, "input_args", {})
            
            input_args.update(kwargs)
            self.input_args = input_args
    
            # after the arguments are recorded as instance attributes, 
            # proceed to the original __init__ call:
            return func(self, *args, **kwargs)
        return wrapper
        
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        if "__init__" in cls.__dict__:
            setattr(cls, "__init__", cls.get_initial_args_wrapper(cls.__init__))

And running it with your example (with "normalized" Python names):

  class Ann(Base):
    def __init__(self, arg1):
        super().__init__()
    
class MyAnn(Ann):
    def __init__(self, input_size, output_size):
        super().__init__(arg1=4)
z1=MyAnn(input_size=40, output_size=1)
print(z1.input_args)
# outputs:

{'input_size': 40, 'output_size': 1, 'arg1': 4}


huangapple
  • 本文由 发表于 2023年5月30日 05:20:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76360370.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定