Python的函数和参数
2021-09-06 本文已影响0人
东方胖
image.png
实参和形参
parameter 是函数定义的参数形式
argument 是函数调用时传入的参数实体。
python的实参传参模式有哪些
对于函数调用的传参模式,一般有两种:
- 位置传参
func(a, b) # a, b都是位置传参,这是初级调用最为常见的传参模式
func(*(a, b)) #这也是位置传参,等于上面的例子 ,实践中比较少见,一般为了强调a,b有某种聚集关系可以这样传递
- 关键字传参
常见的例子如
"Welcome {name}!".format(name='Xiaoming')
此外,
func(key1=value1, key2=value2,...)
func(**{"key1": value1, "key2":value2})
也是关键字传参
python的四种形参定义方式
python的函数参数定义一般来说有五种: 位置和关键字参数混合,仅位置参数,仅关键字参数,可变位置参数,可变关键字参数。其中仅位置参数的方式仅仅是一个概念,python语法中暂时没有这样的设计。
通常我们见到的函数是位置和关键字混合的方式。
- 位置或关键字 (position-or-keyword):
例子:
def func(foo, bar=None):
...
func(foo=f, bar="abc") # 关键字调用
func(f, bar="abc") #关键字和位置混合
func(f, "abc") #位置调用
- 位置 (position-only): Python没有显式的语法来规定 positon-only 的定义,通常我们定义一个函数
def my_func(arg1, arg2):
...
# 调用
my_func(arg1, arg2="abc")
my_func(arg1, arg2)
既可以用关键字又可以用位置调用
- keyword-only
func(arg, *, kw_only1, kw_only2):
...
或
def func(*, name, age):
print(name, age)
# 调用
func(name='Tom', age=100) # 输出 Tom 100
func(**{'name': 'Tom', 'age'=100}) # 输出 Tom 100
这种方式的定义只能使用关键字传参的模式
- 可变位置 (var-position): 指定一个可以由任意数量构成的位置参数构成的序列
def func(*args):
print(args)
# 调用
func(1) # 输出 (1,)
func(1,2,3) #输出(1,2,3)
func(*[1,2,3]) #输出 (1,2,3)
func([1 , 2, 3]) #输出([1,2,3],) 注意二者的区别。
f(*some_list) 与 f(arg1, arg2, ...) (其中some_list = [arg1, arg2, ...])是等价的
- 可变关键字(var-keyword):指定一个可以由任意数量的键值对组成的字典参数。可以在形参前面用 双星号 ** 来定义
def func(**kwargs):
print(kwargs)
# 调用
func(name='XiaoMing') # 输出 {'name': 'XiaoMing'}
func(**{'name': 'XiaoMing'}) # 输出 {'name': 'XiaoMing'}
func(name='Zhangsan', age=20) # 输出 {'name': 'Zhangsan', 'age': 20}
func(**{'name': 'Zhangsan', 'age': 20}) # 输出 {'name': 'Zhangsan', 'age': 20}
网络模块request的request方法的设计
多数的可选参数被设计成可变关键字参数
def request(method, url, **kwargs):
"""Constructs and sends a :class:`Request <Request>`.
:param method: method for the new :class:`Request` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
:param data: (optional) Dictionary or list of tuples ``[(key, value)]`` (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')``
or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string
defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers
to add for the file.
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
:param timeout: (optional) How many seconds to wait for the server to send data
before giving up, as a float, or a :ref:`(connect timeout, read
timeout) <timeouts>` tuple.
:type timeout: float or tuple
:param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``.
:type allow_redirects: bool
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
:param verify: (optional) Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use. Defaults to ``True``.
:param stream: (optional) if ``False``, the response content will be immediately downloaded.
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
:return: :class:`Response <Response>` object
:rtype: requests.Response
Good Design
关于函数输出:尽量使用返回值或元组的方式输出结果
有多种方法能够为函数定义输出:
- 返回结果元组
- 传入可变对象,然后在函数内部改变它的值
- 定义全局变量
- 在类的实例中捆绑值
最好的实践是第一种,偶尔可能会使用第二种方法,但是可能会带来不可预料的问题。第三种和第四种方法是不推荐的,定义全局变量然后调用函数改变它,再为其他例程所用,这是线程不安全的;最后一种方法很晦涩,python官方文档中提供了它的实现方式如下:
class callByRef:
def __init__(self, **args):
for (key, value) in args.items():
setattr(self, key, value)
def func4(args):
args.a = 'new-value' # args is a mutable callByRef
args.b = args.b + 1 # change object in-place
args = callByRef(a='old-value', b=99)
func4(args)
print(args.a, args.b)
非常晦涩
关于默认参数:尽量不要用可变对象作为函数的默认参数
如果使用可变对象作为函数的默认参数,会导致默认参数在所有的函数调用中被共享。
例子1:
def addItem(item, data=['hello']):
data.append(item)
return data
addItem方法的data设计了一个默认参数,使用不当会造成默认参数被共享。
python里面,函数的默认参数被存在__default__属性中,这是一个元组类型
例子2:
def myfunc(a, arg1='hello', arg2='world'):
arg1 = arg1 + arg2 # 左边的arg1实际上是一个新的对象,不会存入 \_\_default__属性中
print(myfunc.__default__)
# 输出 ('hello', 'world')
在例子1中,默认参数是一个列表,它是mutable的数据类型,当它写进 __defauts__属性中时,函数addItem的操作并不会改变它的id,相当于 __defauts__只是保存了data的引用,对于它的内存数据并不关心,每次调用addItem,都可以修改 addItem.__defauts__中的数据,它是一个共享数据。
如果默认参数是一个imutable类型,情况将会不一样,你无法改变默认参数第一次存入的值。
例子1中,连续调用addItem('world') 的结果会是
['hello', 'world']
['hello', 'world', 'world']
...
而不是期望的
['hello', 'world']
['hello', 'world']
编写函数的四个基本原则
-
函数的body要尽量短小
这里包含的精义是,不要过多的嵌套,如果必须如此,可能需要重新考虑设计;
短小的含义没有具体精确的数字,但是一般来说,观感上一个函数不应该要来回拉动
滚动条来进行codereviw,一般需要这样做的话,CR你代码的老大通常都会叼你。 -
对参数的数量进行控制
太多参数会导致测试用例膨胀,参数含义难以理解 - 考虑向下兼容
-
一个函数只做一件事
这些准则不仅仅适用于Python语言 -
设计函数时慎用可变参数
可变参数太灵活了。
一般适用于可变参数的场景有
- 装饰器
def mydeco(func):
def new(*args, **kwargs):
return func(*args, **kwargs)
- 函数的参数不确定
- 实现函数的多态,或在继承情况下需要调用父类的某些方法的时候
class A(object):
def somefun(self, p1, p2):
pass
class B(A):
def myfun(self, p3, *args, **kwargs):
super.(B, self).somefunc(*args, **kwargs)