码迷,mamicode.com
首页 > 其他好文 > 详细

默认值的作用域

时间:2019-02-15 15:10:46      阅读:203      评论:0      收藏:0      [点我收藏+]

标签:not   name   key   str   作用   test   修改   int   [1]   

1 默认值的作用域

  • python中一切皆对象,python会把函数的默认值放在属性中,这个属性就伴随着这个函数对象的整个生命周期
  • 也就是说,函数定义完之后,其默认值也就只生成一次,只要函数存在,其缺省值就不变。
  • 函数的缺省值和函数是否被调用没有关系,只和函数的定义有关系,函数的缺省值是在定义函数的时候一并定义的,同一个函数对象在内存中(堆中)只有一份,缺省值也伴随着只有一份,其实就是函数对象的一个属性。
  • 文艺化的表述就是:函数调不调用,缺省值都在那里
  • 函数的调用和函数的定义是两码事
  • 可以通过foo.__defaults__ 查看函数的默认属性值
  • 函数的默认值的作用域是local作用域

1.1 演示1 (默认值的作用域仅仅是local)

定义两个个函数

def foo(xyz=1):
    print(xyz)

foo()
foo()
print(xyz)
=====================
def foo(xyz=[]):
    xyz.append(1)
    print(xyz)

foo()
foo()
print(xyz)

执行结果

In [20]: def foo(xyz=1):
    ...:     print(xyz)
    ...:
In [21]: foo()
1
In [22]: foo()
1
In [23]: print(xyz)
#----------------------------------------------------------------------
NameError                            Traceback (most recent call last)
<ipython-input-23-b6499aa2e891> in <module>
----> 1 print(xyz)

NameError: name ‘xyz‘ is not defined

In [24]:

================================

In [24]: def foo(xyz=[]):
    ...:     xyz.append(1)
    ...:     print(xyz)
    ...:
    ...: foo()
    ...: foo()
    ...: print(xyz)
[1]
[1, 1]
#----------------------------------------------------------------------
NameError                            Traceback (most recent call last)
<ipython-input-24-4726e79ae693> in <module>
      5 foo()
      6 foo()
----> 7 print(xyz)

NameError: name ‘xyz‘ is not defined

1.2 演示2 (引用类型的默认值的示例)

def foo(xyz=[], u=‘abc‘, z=123):
   xyz.append(1)
   return xyz

print(foo(), id(foo))
print(foo.__defaults__) 
print(foo(), id(foo))    
print(foo.__defaults__)
In [43]: def foo(xyz=[], u=‘abc‘, z=123):
    ...:    xyz.append(1)
    ...:    return xyz
    ...:

In [44]: print(foo(), id(foo))
    ...: print(foo.__defaults__)
[1] 2386557664520
([1], ‘abc‘, 123)

In [45]: print(foo(), id(foo))
    ...: print(foo.__defaults__)
[1, 1] 2386557664520
([1, 1], ‘abc‘, 123)  # 引用类型xyz的内容发生了变化,但全局函数foo对象并没有变化,所以其属性__defaults__也不会变化,其中的xyz的地址也没有发生变化,只不过是在列表中追加了一个元素而已,列表对象还是原来的那个列表对象。

结论

  1. 函数地址并没有变化,就是说函数这个对象并没有变,调用它,它的属性 defaults 中使用元组保存默认值
  2. xyz的默认值是引用类型,应用类型的元素变动,并不是元组的变化。这和拷贝的原理是类似的。

1.3 演示3 (非引用类型的默认值的示例)

def foo(w, u=‘abc‘, z=123):
    u = ‘xyz‘
    z = 789
    print(w,u,z)

print(foo.__defaults__)
foo(‘test‘)
print(foo.__defaults__)
In [47]: def foo(w, u=‘abc‘, z=123):
    ...:     u = ‘xyz‘
    ...:     z = 789
    ...:     print(w,u,z)
    ...:

In [48]: print(foo.__defaults__)
(‘abc‘, 123)

In [49]: foo(‘test‘)
test xyz 789

In [50]: print(foo.__defaults__)
(‘abc‘, 123)   # 非引用类型的默认值并没有变化

结论

  1. 属性 defaults 中使用元组保存所有位置参数默认值,它不会因为在函数体内使用了它而发生改变。

1.4 演示4 (关键字默认参数默认值的示例)

def foo(w, u=‘abc‘, *, z=123, zz=[4,5,6]):
    u = ‘xyz‘
    z = 789
    zz.append(8)
    print(w,u,z,zz)

print(foo.__defaults__)
print(foo.__kwdefaults__)
foo(‘test‘)
print(foo.__kwdefaults__)
In [64]: def foo(w, u=‘abc‘, *, z=123, zz=[4,5,6]):
    ...:     u = ‘xyz‘
    ...:     z = 789
    ...:     zz.append(8)
    ...:     print(w,u,z,zz)
    ...:

In [65]: print(foo.__defaults__)
(‘abc‘,)

In [66]: print(foo.__kwdefaults__)
{‘z‘: 123, ‘zz‘: [4, 5, 6]}

In [67]: foo(‘test‘)
test xyz 789 [4, 5, 6, 8]

In [68]: print(foo.__kwdefaults__)
{‘z‘: 123, ‘zz‘: [4, 5, 6, 8]}   # 引类型的关键字的默认值的发生了变化

结论

  1. 属性 defaults 中使用元组保存所有位置参数默认值,它不会因为在函数提内使用了它而发生改变。
  2. 属性 kwdefaults 中使用字典保存所有keyword-only参数默认值.(由于是字典,所以是可以修改的)
  3. * args 和 **kwargs 都是没有缺省值的,因为他们都可以可接收 0 个参数,就没有必要设置缺省值了。

keyword-only 是在 python3.0之后才有的,所以 kwdefaults 也是python3.0之后才有的。

2 默认值的修改

  • 使用可变类型作为默认值,就可能修改这个默认值
  • 有时候这个特性是好的,有的时候这种特性是不好的,有副作用

如何做到按需改变呢?看下面的2种方法

2.1 函数体内,不改变默认值(此方法会有内存拷贝)

xyz都是传入参数或者默认参数的副本,如果就想修改原参数,无能为力

def foo(xyz=[], u=‘abc‘, z=123):
    xyz = xyz[:] # 影子拷贝
    xyz.append(1)
    print(xyz)

foo()
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)

--------------------------------------------
In [85]: def foo(xyz=[], u=‘abc‘, z=123):
    ...:     xyz = xyz[:] # 影子拷贝
    ...:     xyz.append(1)
    ...:     print(xyz)
    ...:
    ...: foo()
    ...: print(foo.__defaults__)
    ...: foo()
    ...: print(foo.__defaults__)
    ...: foo([10])
    ...: print(foo.__defaults__)
    ...: foo([10,5])
    ...: print(foo.__defaults__)
[1]
([], ‘abc‘, 123)
[1]
([], ‘abc‘, 123)
[10, 1]
([], ‘abc‘, 123)
[10, 5, 1]
([], ‘abc‘, 123)

--------------------------------------------
In [84]: a = []
    ...: b =a[:]
    ...: id(a),id(b)
Out[84]: (1885549915592, 1885549914248)   # < -- 切片是浅拷贝的过程,生成了一个新的列表

2.2 使用不可变类型默认值(建议使用的方法)

  • 如果使用缺省值None就创建一个列表
  • 如果传入一个列表,就修改这个列表
def foo(xyz=None, u="abc", z=123):
    if xyz is None:
        xyz = []
    xyz.append(1)
    print(xyz)

foo()
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)

--------------------------------------------

In [86]: def foo(xyz=None, u="abc", z=123):
    ...:     if xyz is None:
    ...:         xyz = []
    ...:     xyz.append(1)
    ...:     print(xyz)
    ...:
    ...: foo()
    ...: print(foo.__defaults__)
    ...: foo()
    ...: print(foo.__defaults__)
    ...: foo([10])
    ...: print(foo.__defaults__)
    ...: foo([10,5])
    ...: print(foo.__defaults__)
[1]
(None, ‘abc‘, 123)
[1]
(None, ‘abc‘, 123)
[10, 1]
(None, ‘abc‘, 123)
[10, 5, 1]
(None, ‘abc‘, 123)

2.3 两种方法的比较

  • 第一种方法
    使用影子拷贝创建一个新的对象,永远不能改变传入的参数
  • 第二种方法
    通过值的判断就可以灵活的选择创建或者修改传入对象,这种方式灵活,应用广泛,很多函数的定义,都可以看到使用None这个不可变的值作为默认参数,可以说这是一种惯用法

3 + 和 += 对函数默认值的影响

3.1 使用 += 改变了__defaults__属性, += 是就地修改相当于 extend

def x(a=[]):
    a += [5]
print(x.__defaults__)
x()
print(x.__defaults__)
In [78]: def x(a=[]):
    ...:     a += [5]
    ...: print(x.__defaults__)
    ...: x()
    ...: print(x.__defaults__)
([],)
([5],)

3.2 使用 + 并没有改变__defaults__ , 是右边通过 + 生成一个新的对象再赋值给左边的变量,并没有堆默认值操作。

def y(a=[]):
    a = a + [5]
print(y.__defaults__)
y()
print(y.__defaults__)
In [79]: def y(a=[]):
    ...:     a = a + [5]
    ...: print(y.__defaults__)
    ...: y()
    ...: print(y.__defaults__)
([],)
([],)

3.3 两种方法结果不同的原因

  • 这两种方法在处理列表时采用的方式不一样
  • 第一种方法,本质上使用的是列表的extend方法
  • 第二种方法,本质上就是列表的+,返回一个新列表
  • 但是对于字符串这种非引用类型的话,+ 和 += 都是生成一个新的对象。
In [87]: a = ‘abc‘
    ...: a += "d"

In [88]: a
Out[88]: ‘abcd‘

In [89]: a = "abc"
    ...: a = a + "d"

In [90]: a
Out[90]: ‘abcd‘

本文链接:https://www.cnblogs.com/shichangming/p/10383563.html

默认值的作用域

标签:not   name   key   str   作用   test   修改   int   [1]   

原文地址:https://www.cnblogs.com/shichangming/p/10383563.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!