方法是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
方法能提高应用的模块性,和代码的重复利用率。
Python提供了许多内建方法,比如print()。
你也可以自己创建方法,这被叫做用户自定义方法。
下面我们来详解
def test(a, b=1, c=2, *args, **kwargs): print(a) print(b) print(c) print(args) print(kwargs)
当使用以下方式来调用test方法时
test(1,2,3,4,5,6)
输出
1
2
3
(4, 5, 6)
{‘y’: 7}
在这里, 六个参数都变成了位置参数, 按照你输入的顺序,依次赋值给定义的参数,区别在于
a,b,c三个参数为显式定义, 可以直接打印参数名
args为可变参数, a,b,c后面没有显式定义的参数名的参数, 以元组的形式赋值给args, 本质上就是个位置参数
当使用以下方式调用test方法时
test(c=1,a=2,b=3,d=4,e=5)
输出
2
3
1
()
{‘d’: 4, ‘e’: 5}
在这里, a,b,c,d,e都变成了关键字参数
输出时,没有按照输入的顺序赋值,而是按照key赋值
其中显式定义的a,b,c都拿都到了自己的值,
kwargs为可变参数, a,b,c后面显式定义的参数, 以字典的形式赋值给kwargs,本质上就是个关键字参数
在上述代码中, a与bc的却别在于, bc被赋予了默认值, 又称默认参数,所以调用方法时可以不输入bc,但必须输入a
a可以是位置参数(下图1,2),也可以是关键字参数(3,4,5)
# 正确 test(1) # 1 test(1, b=1, c=1) # 2 test(a=1) # 3 test(a=1, b=1) # 4 test(b=1,c=1,a=2) # 5 # 报错 test(b=2) # 6 test(b=2, c=1) # 7
从调用的角度来看, *args可以理解为位置参数, **kwargs可以理解为关键字参数
在没有不定参数*args与 **kwargs的情况下, 位置参数和关键字参数更多是由调用时决定的, 只要保证默认参数在后即可
调用时,隐式调用就是位置参数,显示调用就是关键字参数, 二者可以互相转换
ps: 这里使用隐式和显示可能不太准确, 但我想不到更好的词, 能理解即可
请注意: 上面提到了默认参数, 默认参数的值必须为不可变对象,下面会说明
在有不定参数*args与 **kwargs的时候, 位置参数和关键字参数就不能随意转换
先说结论, 位置参数一定要在关键字参数前面
因此定义方法时, 建议的参数顺序
def test(a, b, c=100, *args, d=1, e=2, f, **kwargs): print(a) print(b) print(c) print(args) print(d) print(e) print(f) print(kwargs) test(1, 2, 3, 4, 5, d=10, e = 7, l=11, f=8, h=10)
执行结果如下
1
2
3
(4, 5)
10
7
8
{‘l’: 11, ‘h’: 10}
*args之前(含) 为位置参数, 依次赋值, 不能乱序
*args之后为关键字参数, 无所谓顺序
上图中参数c在定义时被赋予了默认值100, 但是打印出的结果为3, 被位置参数所取代, 所以在有*args时,位置参数不建议设置默认值, 几乎不会生效(注:不是一定)
当不需要参数*args, 但又想区分位置参数与关键字参数,可以使用*区分
def test(a, b, c=100, *, d=1, e=2, f, **kwargs): print(a) print(b) print(c) print(d) print(e) print(f) print(kwargs)
结果如下
1
2
3
10
7
8
{‘l’: 11, ‘h’: 10}
*在这里只是一个分隔符, 不暂用参数位置,不可被调用和继承
ps: 这里的参数顺序是我个人经验总结的经验最佳顺序, 但是在python版本中, 默认参数在*args之后似乎会报错, 可尝试调整.但参数顺序的原则是, 在不报错的情况下, 输入的每一个参数在方法中都能够被取到
在定义方法时, 以key=value的形式定义的参数,就是默认参数, 这个value称为默认值
在调用方法时,如果不输入这个key,或者位置参数覆盖不到这个key, 方法中取到的就是默认值
但是,需要注意的是, 默认参数的值必须是不可变对象
来看下面的例子:
def test(a=[]): a.append(1) print(a) test() test() test() test()
运行结果:
[1]
[1, 1]
[1, 1, 1]
[1, 1, 1, 1]
这是由于python在对变量赋值时,赋予的并不是值本身, 而是该值的引用,形参a每次被调用时, 访问的都是同一个内存地址
一个不恰当的比喻,如果将方法比作一张照片,装饰器就是照片外面的相框, 一束光想要穿过照片, 就要先穿过相框前面,然后穿过照片,最后穿过相框的背面,在相框与照片之前的间隙中,可以插入我们想要的处理.
举个例子: 我要执行一个方法,并计算方法执行的时间
import time import datetime def test(): start_time = datetime.datetime.now() ''' you code here ''' end_time = datetime.datetime.now() print('方法执行时间:'+str(end_time-start_time)) test() # 输出 # 方法执行时间:0:00:00.000006
方法本身没有问题, 但写成方法的目的就是为了精简代码,提高重用率, 每一个方法中都去定义start_time与end_time也过于繁琐了
直接上代码
import time import datetime def count_time(func): def inner(*args, **kwargs): start_time = datetime.datetime.now() res = func(*args, **kwargs) end_time = datetime.datetime.now() print('方法执行时间:'+str(end_time-start_time)) return res return inner @ count_time def test(s=1): time.sleep(5) return s re = test(3) print(re) # 执行结果 # 方法执行时间:0:00:05.000924 # 3
import time import datetime def count_time(text): def wraper(func): def inner(*args, **kwargs): print(text) start_time = datetime.datetime.now() res = func(*args, **kwargs) end_time = datetime.datetime.now() print('方法执行时间:'+str(end_time-start_time)) return res return inner return wraper @ count_time('hello') def test(s=1): time.sleep(5) return s re = test(3) print(re) # 运行结果 # hello # 方法执行时间:0:00:05.000002 # 3
import time import datetime class Test: def __init__(self, text): self.text = text def count_time(text): def wraper(func): def inner(self, *args, **kwargs): print(text) print(self.text) start_time = datetime.datetime.now() res = func(self, *args, **kwargs) end_time = datetime.datetime.now() print('方法执行时间:'+str(end_time-start_time)) return res return inner return wraper @count_time('hello') def a(self): time.sleep(5) a = Test(text='welcome') a.a() # 运行结果 # hello # welcome # 方法执行时间:0:00:05.000297
这种写法的好处,是可以在装饰器中调用类的参数
需要注意的是,在类装饰器里, 装饰器一定要写在被装饰的方法之前
如有错误,还请指正