
在Python中有一个可变参数的语法,就是在编写代码的,并不能明确有多少个参数,那么就可以使用可变参数。写法如下
def get(data, *args, **kwargs):
pass*args称之为Non-keyword Variable Arguments,无关键字参数
**kwargs称之为keyword Variable Arguments,有关键字参数
当函数中以列表或者元组的形式传参时,就要使用*args,当传入字典形式的参数时,就要使用**kwargs。如两者在同一个方法使用中,*args需要在**kwargs前面。

两者区分是靠*和**,跟后面的名字没关系。args和kwargs表示的形参,可以随意起名字,用a,b,c都没问题,但是在代码编写的时候,变量名字,最好不要随意起名,中英文混合。
下面代码演示一下基本使用:
def method(data, *a, **b):
print(f"data:{data}")
print(f"a:{a}")
print(f"b:{b}")
method("data", (1, 2, 3), ["a", "b"], 3, a="a1", b="b2", c="c3")
#代码输出结果:
data:data
a:((1, 2, 3), ['a', 'b'], 3)
b:{'a': 'a1', 'b': 'b2', 'c': 'c3'}在实际的使用中,动态参数使用地方最多的一般是在封装的包里,来实现一些丰富的功能,比如最常用requests的包中的get方法。
代码如下:
def get(url, params=None, **kwargs):
r"""Sends a GET request.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary, list of tuples or bytes to send
in the query string for the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault('allow_redirects', True)
return request('get', url, params=params, **kwargs)作用呢,就是发送一个get请求,有三个参数,分别是url, params=None, **kwargs。
其中url是一个必选参数,为请求的地址,得让requests知道,你要访问哪个地址吧。
Params是表示传参的参数,支持字典,列表,元组等等,一般是就是在url?之后的内容,多数是用字典。
后面的**kwargs 就表示关键字可选参数,这样参数可有可没有。有的时候不用也可以成功请求,但是有的网站会拒绝无头请求,那么需要在kwargs 中设置上请求头。
不过呢,要是点进去,看下get请求的代码,其实在最外层看到的,是为了方便使用的调用方法,其实在创建请求对象的时候,也设置了很多的参数。
以下来自request包中的Session类中的方法request方法的节选。
def request(self, method, url,
params=None, data=None, headers=None, cookies=None, files=None,
auth=None, timeout=None, allow_redirects=True, proxies=None,
hooks=None, stream=None, verify=None, cert=None, json=None):
# Create the Request.
req = Request(
method=method.upper(),
url=url,
headers=headers,
files=files,
data=data or {},
json=json,
params=params or {},
auth=auth,
cookies=cookies,
hooks=hooks,
)
......其实在创建Request对象的时候,也需要很多的参数,但是核心的参数有1个或者2个,其余的是可选的。
当创建具体对象的时候,根据实际的不同的参数,创建不同的对象。这样暴露到最外层的调用方法就比较简洁。对于使用者也比较友好,对于非必选的参数不需要太在意。
那么Java中也可以这样吗?肯定是不能,在参数中根本没办法定义**啊。假设呢,现在也在java中实现一个类似的功能,如何搞呢?

我们先按照Request对象的属性,在Java中创建一个Request的类,下面是随便写的。
//为了方便,这里把所有的字段都设置成string
public class Request {
private String method;
private String url;
private String headers;
private String files;
private String data;
private String json;
private String params;
private String auth;
private String cookies;
private String hooks;
//get set 方法
}通常情况下,如果创建一个Request对象,在类中默认有一个无参构造器,Request() ,但是在初始化的时候,一般都是带参数的,不然这个Request对象请求的发给谁,什么方式去发,都不知道。
所以通常会加一些带参数的构造器,大概如下:
//部分参数的
public Request(String method, String url, String headers) {
this.method = method;
this.url = url;
this.headers = headers;
}
public Request(String method, String url, String data, String json) {
this.method = method;
this.url = url;
this.data = data;
this.json = json;
}
//全部参数的
public Request(String method, String url, String headers, String files, String data, String json, String params, String auth, String cookies, String hooks) {
this.method = method;
this.url = url;
this.headers = headers;
this.files = files;
this.data = data;
this.json = json;
this.params = params;
this.auth = auth;
this.cookies = cookies;
this.hooks = hooks;
}在需要新建对象的时候,进行如下的操作:
Request r1 = new Request("1", "2", "3");
Request r2 = new Request("1", "2", "3", "4");
Request r3 = new Request("1", "2", "3","3","3","3", null, null, null,"7");//这里有10个参数如果参数过多,很容易分不清顺序,好在像IDEA中创建对象的传参的时候是有每个参数是谁的提示。

但这种方式有着明显的缺点就是:它们都不能很好的扩展到大量的可选参数。而且代码的可读性变差。如果有可选参数比较多,那么为了适应每一种构造方式,可能还需要创建多种对象的构造方法。

除此之外,还有一种方法就是JavaBeans模式,也是在代码中最常用的,就是新建一个无参对象,通过set方法进行赋值。
最开始写的Request类就是JavaBean方式创建的,目前多数场景都是使用此方式来创建。
JavaBean是一个遵循特定写法的Java类,它通常具有如下特点:(1)这个Java类必须具有一个无参的构造函数(2)属性必须私有化。(3)私有化的属性必须通过public类型的方法暴露给其它程序,并且方法的命名也必须遵守一定的命名规范。
Request request = new Request();
request.setMethod("1");
request.setUrl("2");
request.setHeaders("3");
// 456789JavaBeans 模式自身有着严重的缺点: 那就是构造过程被分解到了几个调用中,在构造过程中JavaBean可能处于不一致的状态。
从全部参数都具有set方法来说,并不能明确知道,那些是必须参数,那些是可选参数,可能request 不seturl,这个对象没有实际意义的,并不是最初设计Request 应该达到的状态。

所以接下来,还有一种创建对象的方式就是建造者(Builder)模式。
Builder模式:不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端再builder对象上调用类似于setter方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生产不可变对象。
按照建造者模式构建对象的写法应该是这样的:
public class Request {
private String method;
private String url;
private String headers;
private String files;
private String data;
private String json;
private String params;
private String auth;
private String cookies;
private String hooks;
public static class Builder {
private String method;
private String url;
private String headers;
private String files;
private String data;
private String json;
private String params;
private String auth;
private String cookies;
private String hooks;
public Builder(String method, String url) {
this.method = method;
this.url = url;
}
public Builder headers(String headers) {
this.headers = headers;
return this;
}
public Builder files(String files) {
this.files = files;
return this;
}
public Builder data(String data) {
this.data = data;
return this;
}
public Builder json(String json) {
this.json = json;
return this;
}
public Builder params(String params) {
this.params = params;
return this;
}
public Builder auth(String auth) {
this.auth = auth;
return this;
}
public Builder cookies(String cookies) {
this.cookies = cookies;
return this;
}
public Builder hooks(String hooks) {
this.hooks = hooks;
return this;
}
public Request builder() {
return new Request(this);
}
}
private Request(Builder builder) {
method = builder.method;
url = builder.url;
headers = builder.headers;
files = builder.files;
data = builder.data;
json = builder.json;
params = builder.params;
auth = builder.auth;
cookies = builder.cookies;
hooks = builder.hooks;
}
}新建对象是这样的:
Request request = new Request.Builder("method", "url")
.params("1").auth("2").builder();这样的Builder 构造器中传入必选的参数(Builder("method", "url")),其后添加可选参数(.params("1").auth("2")),想配置什么可以动态的添加,更方便的快速创建一个对象。
这样写法非常简洁,容易让别人读懂,那些是必选,那些是可选,也间接的实现了像Python一样,具有动态参数的语法功能。
这也是《Effective Java》中的第二条,遇到多个构造器参数的时要考虑使用构建器,上文中部分内容引用在书中原话。
Builder模式适用于属性多,但很多为属性是可选的时候,可以优雅的创建对象。如果只有两三个属性,那么就没有必要了。
不过即便是需要使用Builder,也不需要写这么多的代码,在lombok里,提供了@Builer的注解,可以替代上面那些代码。
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class Request {
private String method;
private String url;
private String headers;
private String files;
private String data;
private String json;
private String params;
private String auth;
private String cookies;
private String hooks;
}在实际的Java中的发起http请求的httpcilent包中,一些地方也使用了build模式。比如
RequestConfig 类。

感兴趣的可以自行了解阅读。
好了,今天就分享到这里,我是马拉松程序员,可不止于代码。