在Python中何时创建默认对象?

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

When is a default object created in Python?

问题

我有一个类似以下结构的Python(3)代码:

  • main_script.py
  • util_script.py
  • AccessClass.py

main 脚本调用 util 中的一个函数,其签名如下:

def migrate_entity(project, name, access=AccessClass.AccessClass()):

主脚本中的调用如下:

migrate_entity(project_from_file, name_from_args, access=access_object)

当调用完成时,所有对象都有值。然而,一旦执行 main 脚本,函数参数中的 AccessClass 默认就会被初始化,即使它从未被使用过。例如,这个 main 脚本的 __init__ 会在函数签名中创建默认的类:

if __name__ == "__main__":
  argparser = argparse.ArgumentParser(description='Migrate support data')
  argparser.add_argument('--name', dest='p_name', type=str, help='The entity name to migrate')
  load_dotenv()
  fileConfig('logging.ini')

  # Just for the sake of it
  quit()
  # The rest of the code...
  # ...and then
  migrate_entity(project_from_file, name_from_args, access=access_object)

即使添加了 quit()AccessClass 也会被创建。如果我使用 ./main_script.py -h 运行脚本,函数签名中的 AccessClass 也会被创建。尽管函数实际上只接收一个访问对象的调用,但我可以看到调用是发生在 AccessClass.__init__ 中。

如果我用 None 替换默认值,然后在函数内部检查参数,然后再创建它,一切都按预期运行,也就是说,只有在需要时才会创建 AccessClass

请问有人能解释为什么会发生这种情况以及默认值的工作原理是什么吗?

在Python中,参数默认值是否总是提前创建的?

英文:

I have a Python (3) structure like following:

  • main_script.py
  • util_script.py
  • AccessClass.py

The main script is calling a function in util with following signature:

def migrate_entity(project, name, access=AccessClass.AccessClass()):

The call itself in the main script is:

migrate_entity(project_from_file, name_from_args, access=access_object)

All objects do have values when the call is done.
However, As soon as the main script is executed the AccessClass in the function parameters defaults is initialized, even though it is never used. For example this main script __init__ will create the default class in the function signature:

if __name__ == "__main__":
  argparser = argparse.ArgumentParser(description='Migrate support data')
  argparser.add_argument('--name', dest='p_name', type=str, help='The entity name to migrate')
  load_dotenv()
  fileConfig('logging.ini')

  # Just for the sake of it
  quit()
  # The rest of the code...
  # ...and then
  migrate_entity(project_from_file, name_from_args, access=access_object)

Even with the quit() added the AccessClass is created. And if I run the script with ./main_script.py -h the AccessClass in the function signature is created. And even though the only call to the function really is with an access object I can see that the call is made to the AccessClass.__init__.

If I replace the default with None and instead check the parameter inside the function and then create it, everything is working as expected, i.e. the AccessClass is not created if not needed.

Can someone please enlighten me why this is happening and how defaults are expected to work?

Are parameter defaults always created in advance in Python?

答案1

得分: 1

基本上,可变对象在你声明函数时就被初始化,而不是在调用函数时初始化。这就是为什么广泛不推荐将可变类型用作默认值的原因。你可以像你提到的那样使用 None,然后在函数体内进行检查,如 if something is None,然后进行适当的初始化。

def foo_bad(x = []): pass # 这是不好的

foo_bad() # 在声明时初始化的列表被使用
foo_bad([1,2]) # 使用提供的列表
foo_bad() # 再次使用在声明时初始化的列表 

def foo_good(x = None):
    if x is None:
        x=[]
    ... # 进一步的逻辑
英文:

Basically the mutable objects are initialized the moment you declare the function, not when you invoke it. That's why it's widely discouraged to use mutable types as defaults. You can use None as you mentioned and inside the body do the check if something is None and then initialize it properly.

def foo_bad(x = []): pass # This is bad

foo_bad() # the list initialized during declaration used
foo_bad([1,2]) # provided list used
foo_bad() # again the list initialized during declaration used 

def foo_good(x = None):
    if x is None:
        x=[]
    ... # further logic

答案2

得分: 1

AccessClass是因为您将其设置为默认参数而被创建,因此它位于文件本身的范围内,并将在文件首次导入时初始化。这也是为什么不建议将列表或字典用作默认参数的原因。

如果未提供任何内容,这是定义默认值的一种更安全的方式:

def migrate_entity(project, name, access=None):
    if access is None:
        access = AccessClass.AccessClass()

您还可以使用类型提示来演示access应该是什么类型:

def migrate_entity(project, name, access: Optional[AccessClass.AccessClass] = None):
英文:

AccessClass is being created because you've set it as a default parameter, so it it's in the scope of the file itself and will be initialised when the file is first imported. This is also why it's not recommended to use lists or dicts as default parameters.

This is a much safer way of defining a default value if nothing is provided:

def migrate_entity(project, name, access=None):
    if access is None:
        access = AccessClass.AccessClass()

You could also use type hinting to demonstrate what type access should be:

def migrate_entity(project, name, access: Optional[AccessClass.AccessClass] = None): ...

huangapple
  • 本文由 发表于 2023年1月9日 18:45:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/75056128.html
匿名

发表评论

匿名网友

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

确定