美文网首页
Python的函数和参数

Python的函数和参数

作者: 东方胖 | 来源:发表于2021-09-06 15:47 被阅读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语言
    • 设计函数时慎用可变参数
      可变参数太灵活了。
      一般适用于可变参数的场景有
    1. 装饰器
    def mydeco(func):
          def new(*args, **kwargs):
                return func(*args, **kwargs)
    
    1. 函数的参数不确定
    2. 实现函数的多态,或在继承情况下需要调用父类的某些方法的时候
    class A(object):
          def somefun(self, p1, p2):
                pass
     class B(A):
          def myfun(self, p3, *args, **kwargs):
               super.(B, self).somefunc(*args, **kwargs)
    

    相关文章

      网友评论

          本文标题:Python的函数和参数

          本文链接:https://www.haomeiwen.com/subject/pbnewltx.html