Python:向函数内的可变对象添加属性
Python: Adding attributes to mutables inside a function
我试着了解在这种情况下的最佳做法是什么。假设我们有一个字典(或列表或其他一些可变的),它在函数内部被改变(但在它之外定义)
d = {'a': 0}
def my_fun(x):
for i, el in enumerate(x):
d[el] = i + 1
调用 my_fun(['b', 'c'])
然后 print(d)
将打印 {'a':0, 'b':1, 'c':2}
这很好。然而,同样可以通过向函数添加 return 语句来完成(尽管不是必需的)。甚至, return 语句加上表示字典的函数中的第二个参数:
d = {'a': 0}
def my_fun(x, d):
for i, el in enumerate(x):
d[el] = i + 1
return d
return 语句和第二个 arg 在这里都是多余的,我只是发现它们有助于清晰度。它更容易阅读。调用 d = my_fun(['b', 'c'], d)
然后 print(d)
将再次打印 {'a':0, 'b':1, 'c':2}
最后,您可以选择传递可变对象的副本:d = my_fun(['b', 'c'], d.copy())
这可能更像 pythonic,但我不知道制作副本是否是内存管理方面的好习惯。
什么是最佳实践?您如何处理这些类型并将属性添加到函数内的可变变量?
像在函数中那样修改可变变量不是好的做法。你是对的,你建议的两种方法都修改了原始对象。对于像这样的简单情况,最好 return 一个新对象。
在这个简单的示例中,您可以像您所说的那样复制字典。 IMO 你应该在函数中这样做。你需要从一开始就清楚dict的结构是什么,因为你可能需要深拷贝。
def my_fun(x, d):
d = d.copy()
for i, el in enumerate(x):
d[el] = i + 1
return d
或者你可以创建一个新的字典并用旧的字典更新它。我更喜欢这个,但你必须小心重复键。
def my_func(x, d):
result = {el: i for i, el in enumerate(x)}
result.update(d)
return result
对于更复杂的东西,你可能有一个class来封装整个东西。
class Foo:
def __init__(self, d):
self.d = d
def update(self, x):
for i, el in enumerate(x):
self.d[el] = i
你的两个例子不等价。它们之所以以这种方式出现,是因为您碰巧将函数参数命名为与全局参数相同。等效函数是:
d = {'a': 0}
def my_fun(x, foo):
for i, el in enumerate(foo):
foo[el] = i + 1
return foo
但是这个函数不是"pythonic"。可以修改传入的对象,但是当您这样做时,也不要 return 它。如果您在函数中复制对象,则 return 它。你所做的只是制造了歧义——我一眼就可以从 return 声明中假设原始字典没有被修改。至于清晰度,这就是文档字符串的用途。现在输入 help(my_fun)
的每个人都知道发生了什么。
def my_fun(x, foo):
"""Update foo with an enumeration of x"""
for i, el in enumerate(foo):
foo[el] = i + 1
你的第一个例子是最危险的。它对全局对象进行静默更新。我们尽量避免这种事情。当它完成时,文档字符串和函数本身的名称可以清楚地表明正在发生一些麻烦事。
至于抄送,你可以让来电者自己决定。更新传递给你的函数的字典比复制它更通用一些,因为调用者可以决定是否应该复制正在更新的数据。当然,由于您不再 return 更新后的对象,调用者需要做更多的工作。
d_copy = d.copy()
my_fun(['b', 'c'], d_copy)
这还不错。代码很清楚。它不像我们 运行 没有换行符之类的东西。
我试着了解在这种情况下的最佳做法是什么。假设我们有一个字典(或列表或其他一些可变的),它在函数内部被改变(但在它之外定义)
d = {'a': 0}
def my_fun(x):
for i, el in enumerate(x):
d[el] = i + 1
调用 my_fun(['b', 'c'])
然后 print(d)
将打印 {'a':0, 'b':1, 'c':2}
这很好。然而,同样可以通过向函数添加 return 语句来完成(尽管不是必需的)。甚至, return 语句加上表示字典的函数中的第二个参数:
d = {'a': 0}
def my_fun(x, d):
for i, el in enumerate(x):
d[el] = i + 1
return d
return 语句和第二个 arg 在这里都是多余的,我只是发现它们有助于清晰度。它更容易阅读。调用 d = my_fun(['b', 'c'], d)
然后 print(d)
将再次打印 {'a':0, 'b':1, 'c':2}
最后,您可以选择传递可变对象的副本:d = my_fun(['b', 'c'], d.copy())
这可能更像 pythonic,但我不知道制作副本是否是内存管理方面的好习惯。
什么是最佳实践?您如何处理这些类型并将属性添加到函数内的可变变量?
像在函数中那样修改可变变量不是好的做法。你是对的,你建议的两种方法都修改了原始对象。对于像这样的简单情况,最好 return 一个新对象。
在这个简单的示例中,您可以像您所说的那样复制字典。 IMO 你应该在函数中这样做。你需要从一开始就清楚dict的结构是什么,因为你可能需要深拷贝。
def my_fun(x, d):
d = d.copy()
for i, el in enumerate(x):
d[el] = i + 1
return d
或者你可以创建一个新的字典并用旧的字典更新它。我更喜欢这个,但你必须小心重复键。
def my_func(x, d):
result = {el: i for i, el in enumerate(x)}
result.update(d)
return result
对于更复杂的东西,你可能有一个class来封装整个东西。
class Foo:
def __init__(self, d):
self.d = d
def update(self, x):
for i, el in enumerate(x):
self.d[el] = i
你的两个例子不等价。它们之所以以这种方式出现,是因为您碰巧将函数参数命名为与全局参数相同。等效函数是:
d = {'a': 0}
def my_fun(x, foo):
for i, el in enumerate(foo):
foo[el] = i + 1
return foo
但是这个函数不是"pythonic"。可以修改传入的对象,但是当您这样做时,也不要 return 它。如果您在函数中复制对象,则 return 它。你所做的只是制造了歧义——我一眼就可以从 return 声明中假设原始字典没有被修改。至于清晰度,这就是文档字符串的用途。现在输入 help(my_fun)
的每个人都知道发生了什么。
def my_fun(x, foo):
"""Update foo with an enumeration of x"""
for i, el in enumerate(foo):
foo[el] = i + 1
你的第一个例子是最危险的。它对全局对象进行静默更新。我们尽量避免这种事情。当它完成时,文档字符串和函数本身的名称可以清楚地表明正在发生一些麻烦事。
至于抄送,你可以让来电者自己决定。更新传递给你的函数的字典比复制它更通用一些,因为调用者可以决定是否应该复制正在更新的数据。当然,由于您不再 return 更新后的对象,调用者需要做更多的工作。
d_copy = d.copy()
my_fun(['b', 'c'], d_copy)
这还不错。代码很清楚。它不像我们 运行 没有换行符之类的东西。