英文:
Get init arguments of child class from parent class
问题
ann
类中的 getInitInpArgs
方法从 ann
的 init
方法中获取输入参数并将其存储在字典中。现在我想定义一个类似于 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}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论