java字符类型转字符串类型六种方式

1. 使用String.valueOf()方法

1
2
3
4
5
6
7
8
@Test
public void givenChar_whenCallingStringValueOf_shouldConvertToString() {
char givenChar = 'x';

String result = String.valueOf(givenChar);

assertThat(result).isEqualTo("x");
}

2. 使用 Character.toString() 方法

1
2
3
4
5
6
7
8
@Test
public void givenChar_whenCallingToStringOnCharacter_shouldConvertToString() {
char givenChar = 'x';

String result = Character.toString(givenChar);

assertThat(result).isEqualTo("x");
}

3. 使用Character的构造方法

1
2
3
4
5
6
7
8
@Test
public void givenChar_whenCallingCharacterConstructor_shouldConvertToString() {
char givenChar = 'x';

String result = new Character(givenChar).toString();

assertThat(result).isEqualTo("x");
}

4. 通过String的隐式转换

1
2
3
4
5
6
7
8
@Test
public void givenChar_whenConcatenated_shouldConvertToString() {
char givenChar = 'x';

String result = givenChar + "";

assertThat(result).isEqualTo("x");
}

5.

1
2
3
4
5
6
7
8
@Test
public void givenChar_whenFormated_shouldConvertToString() {
char givenChar = 'x';

String result = String.format("%c", givenChar);

assertThat(result).isEqualTo("x");
}

总结: 常用Stirng的隐式转换方式.

分享到

java中String类型转Integer或int类型

1. 使用 Integer.parseInt()

1
2
3
4
5
6
7
8
@Test
public void givenString_whenParsingInt_shouldConvertToInt() {
String givenString = "42";

int result = Integer.parseInt(givenString);

assertThat(result).isEqualTo(42);
}

2. 使用 Integer.valueOf() ,(不建议使用)内部使用缓存机制

1
2
3
4
5
6
7
8
@Test
public void givenString_whenCallingIntegerValueOf_shouldConvertToInt() {
String givenString = "42";

Integer result = Integer.valueOf(givenString);

assertThat(result).isEqualTo(new Integer(42));
}

3. 使用Integer构造方法

1
2
3
4
5
6
7
8
@Test
public void givenString_whenCallingIntegerConstructor_shouldConvertToInt() {
String givenString = "42";

Integer result = new Integer(givenString);

assertThat(result).isEqualTo(new Integer(42));
}

4. 使用 Integer.decode()方法

1
2
3
4
5
6
7
8
@Test
public void givenString_whenCallingIntegerDecode_shouldConvertToInt() {
String givenString = "42";

int result = Integer.decode(givenString);

assertThat(result).isEqualTo(42);
}

以上方法如果转换错误会抛出NumberFormatException异常

1
2
3
4
5
@Test(expected = NumberFormatException.class)
public void givenInvalidInput_whenParsingInt_shouldThrow() {
String givenString = "nan";
Integer.parseInt(givenString);
}

5. 使用guava 工具 ,如果解析失败,会跳过返回空值

1
2
3
4
5
6
7
8
@Test
public void givenString_whenTryParse_shouldConvertToInt() {
String givenString = "42";

Integer result = Ints.tryParse(givenString);

assertThat(result).isEqualTo(42);
}

总结: 使用java原生方式简单,但每次要考虑到解析异常也挺烦的,建议使用guava 的Ints.tryParse方法

分享到

java分割字符串的三种方式

使用java原生String.split()方法

1
2
3
4
5
String[] splitted = "peter,james,thomas".split(",");  // 逗号分割
String[] splitted = "car jeep scooter".split(" "); // 空格分割
String[] splitted = "192.168.1.178".split("\\.") // 点分割

String[] splitted = "b a, e, l.d u, n g".split("\\s+|,\\s*|\\.\\s*")); //则这表达式多个分割符

使用工具类Common包下的StringUtils.split()方法

1
String[] splitted = StringUtils.split("car jeep scooter"); //默认空白分割

使用guava工具包的Splitter

1
2
3
4
List<String> resultList = Splitter.on(',')
.trimResults()
.omitEmptyStrings()
.splitToList("car,jeep,, scooter");

总结: String成员方法分割字符串处理简单分割比较容易,但是复杂一点使用工具类还是比较方法. 预先善其事必先利其器,多学习一些工具类的使用,提高开发效率也还不错.

分享到

java回文数字判断方式

1. 使用原生java方式

1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean isPalindrome(String text) {
String clean = text.replaceAll("\\s+", "").toLowerCase();
int length = clean.length();
int forward = 0;
int backward = length - 1;
while (backward > forward) { //两边指针同时变动,比较.
char forwardChar = clean.charAt(forward++);
char backwardChar = clean.charAt(backward--);
if (forwardChar != backwardChar)
return false;
}
return true;
}

2. 使用字符串reverse比较

1
2
3
4
5
6
7
8
9
public boolean isPalindromeReverseTheString(String text) {
    StringBuilder reverse = new StringBuilder();
    String clean = text.replaceAll("\\s+", "").toLowerCase();
    char[] plain = clean.toCharArray();
    for (int i = plain.length - 1; i >= 0; i--) {
        reverse.append(plain[i]);
    }
    return (reverse.toString()).equals(clean);
}

3. 使用stringbuffer stringbuilder直接翻转字符串比较

1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean isPalindromeUsingStringBuilder(String text) {
String clean = text.replaceAll("\\s+", "").toLowerCase();
StringBuilder plain = new StringBuilder(clean);
StringBuilder reverse = plain.reverse();
return (reverse.toString()).equals(clean);
}

public boolean isPalindromeUsingStringBuffer(String text) {
String clean = text.replaceAll("\\s+", "").toLowerCase();
StringBuffer plain = new StringBuffer(clean);
StringBuffer reverse = plain.reverse();
return (reverse.toString()).equals(clean);
}

4. 使用java8 intStream

1
2
3
4
5
public boolean isPalindromeUsingIntStream(String text) {
String temp = text.replaceAll("\\s+", "").toLowerCase();
return IntStream.range(0, temp.length() / 2)
.noneMatch(i -> temp.charAt(i) != temp.charAt(temp.length() - i - 1));
}

5. 使用递归调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean isPalindromeRecursive(String text){
String clean = text.replaceAll("\\s+", "").toLowerCase();
return recursivePalindrome(clean,0,clean.length()-1);
}

private boolean recursivePalindrome(String text, int forward, int backward) {
if (forward == backward) {
return true;
}
if ((text.charAt(forward)) != (text.charAt(backward))) {
return false;
}
if (forward < backward + 1) {
return recursivePalindrome(text, forward + 1, backward - 1);
}

return true;
}

总结: 回文数验证分两种 一种是移动下标比较 另一种是 翻转 比较,翻转比较性能没有下标比较好,
所以建议使用java8 InStreannomatch方法

分享到

java统计字符串中字符出现的次数

1. 使用java原生方式for循环

1
2
3
4
5
6
7
8
9
10
String someString = "elephant";
char someChar = 'e';
int count = 0;

for (int i = 0; i < someString.length(); i++) {
if (someString.charAt(i) == someChar) {
count++;
}
}
assertEquals(2, count);

2. 使用java原生递归调用

1
2
3
4
5
6
7
8
9
10
private static int countOccurences(
String someString, char searchedChar, int index) {
if (index >= someString.length()) {
return 0;
}

int count = someString.charAt(index) == searchedChar ? 1 : 0;
return count + countOccurences(
someString, searchedChar, index + 1);
}

3. 使用正则表达式

1
2
3
4
5
6
7
8
Pattern pattern = Pattern.compile("[^e]*e");
Matcher matcher = pattern.matcher("elephant");
int count = 0;
while (matcher.find()) {
count++;
}

assertEquals(2, count);

4.使用java8

1
2
3
4
5
6
String someString = "elephant";
long count = someString.chars().filter(ch -> ch == 'e').count();
assertEquals(2, count);

long count2 = someString.codePoints().filter(ch -> ch == 'e').count();
assertEquals(2, count2);

使用外部jar包

5. 使用lang包

1
2
int count = StringUtils.countMatches("elephant", "e");
assertEquals(2, count);

6. 使用guava

1
2
int count = CharMatcher.is('e').countIn("elephant");
assertEquals(2, count);

7.使用spring utils

1
2
int count = StringUtils.countOccurrencesOf("elephant", "e");
assertEquals(2, count);

总结: 原生方式简单粗暴,也可以使用其他工具类,但使用java8感觉最优雅.

分享到

java截取字符串的最后一个字符方法

1. 原生java方式 先判断是否为空

1
2
3
4
5
public static String removeLastChar(String s) {
return (s == null || s.length() == 0)
? null
: (s.substring(0, s.length() - 1));
}

2. 使用java8

1
2
3
4
5
6
public static String removeLastCharOptional(String s) {
    return Optional.ofNullable(s)
      .filter(str -> str.length() != 0)
      .map(str -> str.substring(0, str.length() - 1))
      .orElse(s);
}

3. 使用 apache common lang StringUtils.substring()

1
2
String TEST_STRING = "ACBDEF";
StringUtils.substring(TEST_STRING,0,TEST_STRING.length()-1);

4. 使用 StringUtils.chop()方法 因对边缘场景的情况(empty or null)时

1
StringUtils.chop(TEST_STRING)

5. 使用replaceAll() 的正则表达式方式

1
2
3
4
5
6
7
8
9
public static String removeLastCharRegex(String s) {
return (s == null) ? null : s.replaceAll(".$", "");
}
// java8
public static String removeLastCharRegexOptional(String s) {
return Optional.ofNullable(s)
.map(str -> str.replaceAll(".$", ""))
.orElse(s);
}

总结: 以上主要使用substring方法截取字符串,复杂的话建议使用正则表达式方式处理。

分享到

java生成随机字符串的五种方式

1. 使用原生java生成无边界字符串

1
2
3
4
5
6
7
8
@Test
public void givenUsingPlainJava_whenGeneratingRandomStringUnbounded_thenCorrect() {
byte[] array = new byte[7]; // length is bounded by 7
new Random().nextBytes(array);
String generatedString = new String(array, Charset.forName("UTF-8"));

System.out.println(generatedString);
}

2.使用原生java生成右边界字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void givenUsingPlainJava_whenGeneratingRandomStringBounded_thenCorrect() {

int leftLimit = 97; // letter 'a'
int rightLimit = 122; // letter 'z'
int targetStringLength = 10;
Random random = new Random();
StringBuilder buffer = new StringBuilder(targetStringLength);
for (int i = 0; i < targetStringLength; i++) {
int randomLimitedInt = leftLimit + (int)
(random.nextFloat() * (rightLimit - leftLimit + 1));
buffer.append((char) randomLimitedInt);
}
String generatedString = buffer.toString();

System.out.println(generatedString);
}

3.使用 apache common lang 生成有边界字符串(只用字母)

1
2
3
4
5
6
7
8
9
10
@Test
public void givenUsingApache_whenGeneratingRandomStringBounded_thenCorrect() {
  
    int length = 10;
    boolean useLetters = true;
    boolean useNumbers = false;
    String generatedString = RandomStringUtils.random(length, useLetters, useNumbers);
 
    System.out.println(generatedString);
}

4. 使用 apache common lang 生成有边界字母

1
2
3
4
5
6
@Test
public void givenUsingApache_whenGeneratingRandomAlphabeticString_thenCorrect() {
String generatedString = RandomStringUtils.randomAlphabetic(10);

System.out.println(generatedString);
}

5. 使用 apache common lang 生成有边界字母和数字

1
2
3
4
5
@Test
public void givenUsingApache_whenGeneratingRandomAlphanumericString_thenCorrect() {
String generatedString = RandomStringUtils.randomAlphanumeric(10);
System.out.println(generatedString);
}

总结: 使用lang包的生成方式简单,自己原生方式也可以实现.

分享到

windows下完全卸载sourcetree重装

windows下完全卸载sourcetree重装

一般windows版本下建议使用2.3.1.0版本一下

如果使用最新版2.5.1等版本需要下载.NET
点击下载sourcetree

如果你安装过sourcetree又问题,建议完全删除再按装。

下面试卸载sourcetree的方法

  1. 去控制面板点击卸载
  2. 删除几个相关sourcetree遗留无法
    1. 去 %LocalAppData% 目录下删除 \Atlassian\SourceTree\这个目录
    2. 删除 删除的文件
    3. 查看其他如果文件名是sourcetree 也把它删除了
  3. 重新安装

  4. 关闭软件

  5. 去%LocalAppData%\Atlassian\SourceTree\目录下添加accounts.json文件
    添加accounts.json文件

    acounts.json文件内容,可以不用更改什么。 点击下载account.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    [
    {
    "$id": "1",
    "$type": "SourceTree.Api.Host.Identity.Model.IdentityAccount, SourceTree.Api.Host.Identity",
    "Authenticate": true,
    "HostInstance": {
    "$id": "2",
    "$type": "SourceTree.Host.Atlassianaccount.AtlassianAccountInstance, SourceTree.Host.AtlassianAccount",
    "Host": {
    "$id": "3",
    "$type": "SourceTree.Host.Atlassianaccount.AtlassianAccountHost, SourceTree.Host.AtlassianAccount",
    "Id": "atlassian account"
    },
    "BaseUrl": "https://id.atlassian.com/"
    },
    "Credentials": {
    "$id": "4",
    "$type": "SourceTree.Model.BasicAuthCredentials, SourceTree.Api.Account",
    "Username": "",
    "Email": null
    },
    "IsDefault": false
    }
    ]
  6. 重启软件
    成功
    下一步–>下一步
    成功

  1. 总结: 删除安装过的所有文件记录,再装sourcetree就可以使用了.
分享到

python与js语法比较

基本概念

Python和Javascript都是脚本语言,所以它们有很多共同的特性,都需要解释器来运行,都是动态类型,都支持自动内存管理,都可以调用eval()来执行脚本等等脚本语言所共有的特性。

然而它们也有很大的区别,Javascript这设计之初是一种客户端的脚本语言,主要应用于浏览器,它的语法主要借鉴了C,而Python由于其“优雅”,“明确”,“简单”的设计而广受欢迎,被应用于教育,科学计算,web开发等不同的场景中。

编程范式

Python和Javascript都支持多种不同的编程范式,在面向对象的编程上面,它们有很大的区别。Javascript的面向对象是基于原型(prototype)的, 对象的继承是由原型(也是对象)创建出来的,由原型对象创建出来的对象继承了原型链上的方法。而Python则是中规中矩的基于类(class)的继承,并天然的支持多态(polymophine)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
OO in Pyhton

class Employee:
'Common base class for all employees'
empCount = 0 ##类成员
def __init__(self,name,salary):
self.name = name
self.salary = salary
Employee.empCount += 1

def displayCount(self):
print "Total Employee %d" % Employee.empCount
def displayEmployee(self):
print "Name : ",self.name, ", Salary: ",self.salary

创建实例

ea = Employee(“a”,1000)
eb = Employee(“b”,2000)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var empCount = 0;
//构造函数
function Employee(name,salary){
this.name = name;
this.salary = salary;
this.empCount += 1;
}
Employee.prototype.displayCount = function(){
console.log("Total Employee " + empCount);
}
Employee.prototype.displayEmployee = function(){
console.log("Name " + this.name + ",Salary " + this.salary);
}
//创建实例
var ea = new Employee("a",1000);
var eb = new Employee("b", 2000);

因为是基于对象的继承,在Javascript中,我们没有办法使用类成员empCount,只好声明了一个全局变量,当然实际开发中我们会用更合适的scope。注意Javascript创建对象需要使用new关键字,而Python不需要。

除了原生的基于原型的继承,还有很多利用闭包或者原型来模拟类继承的Javascript OO工具,因为不是语言本身的属性,我们就不讨论了。

线程模型

在Javascript的世界中是没有多线程的概念的,并发使用过使用事件驱动的方式来进行的, 所有的JavaScript程序都运行在一个线程中。在HTML5中引入web worker可以并发的处理任务,但没有改变Javascript单线程的限制。

Python通过thread包支持多线程。

不可改变类型 (immutable type)

在Python中,有的数据类型是不可改变的,也就意味着这种类型的数据不能被修改,所有的修改都会返回新的对象。而在Javascript中所有的数据类型都是可以改变的。Python引入不可改变类型我认为是为了支持线程安全,而因为Javascript是单线程模型,所以没有必要引入不可改变类型。

当然在Javascript可以定义一个对象的属性为只读。

1
2
3
var obj = {};Object.defineProperty(obj, "prop",{
value: "test",
writable: false});

在ECMAScript5的支持中,也可以调用Object的freeze方法来是对象变得不可修改。

Object.freeze(obj)

数据类型

Javascript的数据类型比较简单,有object、string、boolean、number、null和undefined,总共六种

Python中一切均为对象,像module、function、class等等都是。

Python有五个内置的简单数据类型bool、int、long、float和complex,另外还有容器类型,代码类型,内部类型等等。

布尔

Javascript有true和false。Python有True和False。它们除了大小写没有什么区别。

字符串

Javascript采用UTF16编码。

Python使用ASCII码。需要调用encode、decode来进行编码转换。使用u作为前缀可以指定字符串使用Unicode编码。

数值

Javascript中所有的数值类型都是实现为64位浮点数。支持NaN(Not a number),正负无穷大(+/-Infiity)。

Python拥有诸多的数值类型,其中的复数类型非常方便,所以在Python在科研和教育领域很受欢迎。这应该也是其中一个原因吧。Python中没有定义NaN,除零操作会引发异常。

列表

Javascript内置了array类型(array也是object)

Python的列表(List)和Javascript的Array比较接近,而元组(Tuple)可以理解为不可改变的列表。

除了求长度在Python中是使用内置方法len外,基本上Javascript和Python都提供了类似的方法来操作列表。Python中对列表下标的操作非常灵活也非常方便,这是Javascript所没有的。例如l[5:-1],l[:6]等等。

字典、哈希表、对象

Javascript中大量的使用{}来创建对象,这些对象和字典没有什么区别,可以使用[]或者.来访问对象的成员。可以动态的添加,修改和删除成员。可以认为对象就是Javascript的字典或者哈希表。对象的key必须是字符串。

Python内置了哈希表(dictS),和Javascript不同的是,dictS可以有各种类型的key值。

空值

Javascript定义了两种空值。 undefined表示变量没有被初始化,null表示变量已经初始化但是值为空。

Python中不存在未初始化的值,如果一个变量值为空,Python使用None来表示。

Javascript中变量的声明和初始化

1
2
3
4
5
v1;
v2 = null;
var v3;
var v4 = null;
var v5 = 'something';

在如上的代码中v1是全局变量,未初始化,值为undefined;v2是全局变量,初始化为空值;v3为局部未初始化变量,v4是局部初始化为空值的变量;v5是局部已初始化为一个字符处的变量。

Python中变量的声明和初始化

1
2
v1 = None
v2 = 'someting'

Python中的变量声明和初始化就简单了许多。当在Python中访问一个不存在的变量时,会抛出NameError的异常。当访问对象或者字典的值不存在的时候,会抛出AttributeError或者KeyError。因此判断一个值是否存在在Javascript和Python中需要不一样的方式。

Javascript中检查某变量的存在性:

1
2
3
4
5
6
if(!v){
// do something ifvdoes notexist orisnull orisfalse
}
if(v === undefined){
// do something ifvdoes notinitialized
}

注意使用!v来检查v是否初始化是有歧义的因为有许多种情况!v都会返回true

Python中检查某变量的存在性:

1
2
3
4
try:
v
except NameError
## do something if v does not exist

在Python中也可以通过检查变量是不是存在于局部locals()或者全局globals()来判断是否存在该变量。

类型检查

Javascript可以通过typeof来获得某个变量的类型:

typeof in Javascript 的例子:

1
2
3
4
5
6
7
8
typeof 3 // "number"
typeof "abc" // "string"
typeof {} // "object"
typeof true // "boolean"
typeof undefined // "undefined"
typeof function(){} // "function"
typeof [] // "object"
typeof null // "object"

要非常小心的使用typeof,从上面的例子你可以看到,typeof null居然是object。因为javscript的弱类型特性,想要获得更实际的类型,还需要结合使用instanceof,constructor等概念。具体请参考这篇文章(http://tobyho.com/2011/01/28/checking-types-in-javascript/)

Python提供内置方法type来获得数据的类型。

1
2
3
4
5
6
7
8
>>> type([])is list
True
>>> type({})is dict
True
>>> type('')is str
True
>>> type(0)is int
True

同时也可以通过isinstance()来判断类的类型

1
2
3
4
5
6
7
8
classA:
pass
classB(A):
pass
isinstance(A(),A) # returns True
type(A()) == A # returns True
isinstance(B(),A) # returns True
type(B()) == A # returns False

但是注意Python的class style发生过一次变化,不是每个版本的Python运行上述代码的行为都一样,在old style中,所有的实例的type都是‘instance’,所以用type方法来检查也不是一个好的方法。这一点和Javascript很类似。

自动类型转换

当操作不同类型一起进行运算的时候,Javascript总是尽可能的进行自动的类型转换,这很方便,当然也很容易出错。尤其是在进行数值和字符串操作的时候,一不小心就会出错。我以前经常会计算SVG中的各种数值属性,诸如x,y坐标之类的,当你一不小心把一个字符串加到数值上的时候,Javascript会自动转换出一个数值,往往是NaN,这样SVG就完全画不出来啦,因为自动转化是合法的,找到出错的地方也非常困难。

Python在这一点上就非常的谨慎,一般不会在不同的类型之间做自动的转换。

Python使用缩进来决定逻辑行的结束非常具有创造性,这也许是Python最独特的属性了,当然也有人对此颇具微词,尤其是需要修改重构代码的时候,修改缩进往往会引起不小的麻烦。

Javascript虽然名字里有Java,它的风格也有那么一点像Java,可是它和Java就好比雷峰塔和雷锋一样,真的没有半毛钱的关系。到时语法上和C比较类似。这里必须要提到的是coffeescript作为构建与Javascript之上的一种语言,采用了类似Python的语法风格,也是用缩进来决定逻辑行。

1
2
3
def func(list):
for i in range(0,len(list)):
print list[i]
1
2
3
4
5
function funcs(list){
for(vari=0,len = list.length();i < len;i++){
console.log(list[i]);
}
}

从以上的两个代码的例子可以看出,Python确实非常简洁。

作用范围和包管理

Javascript的作用域是由方法function来定义的,也就是说同一个方法内部拥有相同的作用域。这个严重区别与C语言使用{}来定义的作用域。Closure是Javascript最有用的一个特性。

Python的作用域是由module,function,class来定义的。

Python的import可以很好的管理依赖和作用域,而Javascript没有原生的包管理机制,需要借助AMD来异步的加载依赖的js文件,requirejs是一个常用的工具。

赋值逻辑操作符

Javascript使用=赋值,拥有判断相等(==)和全等(===)两种相等的判断。其它的逻辑运算符有&& 和||,和C语言类似。

Python中没有全等,或和与使用的时and 和 or,更接近自然语言。Python中没有三元运算符 A :B ?C,通常的写法是

(A and B) or C

因为这样写有一定的缺陷,也可以写作

B if A else C

Python对赋值操作的一个重要的改进是不允许赋值操作返回赋值的结果,这样做的好处是避免出现在应该使用相等判断的时候错误的使用了赋值操作。因为这两个操作符实在太像了,而且从自然语言上来说它们也没有区别。

++运算符

Python不支持++运算符,没错你再也不需要根据++符号在变量的左右位置来思考到底是先加一再赋值呢还是先赋值再加一。

连续赋值

利用元组(tuple),Python可以一次性的给多个变量赋值

(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)

函数参数

Python的函数参数支持命名参数和可选参数(提供默认值),使用起来很方便,Javascript不支持可选参数和默认值(可以通过对arguments的解析来支持)

1
2
def info(object,spacing=10,collapse=1):
......

其它

立即调用函数表达式 (IIFE)

Javascript的一个方便的特性是可以立即调用一个刚刚声明的匿名函数。也有人称之为自调用匿名函数。

下面的代码是一个module模式的例子,使用闭包来保存状态实现良好的封装。这样的代码可以用在无需重用的场合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var counter = (function(){
vari = 0;
return{
get: function(){
returni;
},
set: function(val){
i = val;
},
increment: function(){
return ++i;
}
};
}());

Python没有相应的支持。

生成器和迭代器(Generators & Iterator)

在我接触到的Python代码中,大量的使用这样的生成器的模式。

Python生成器的例子

1
2
3
4
5
6
7
8
# a generator that yields items instead of returning a list
def firstn(n):
num = 0
while num < n:
yield num
num += 1

sum_of_first_n = sum(firstn(1000000))

Javascript1.7中引入了一些列的新特性,其中就包括生成器和迭代器。然而大部分的浏览器除了Mozilla(Mozilla基本上是在自己玩,下一代的Javascript标准应该是ECMAScript5)都不支持这些特性

Javascript1.7 迭代器和生成器的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
function fib(){
vari = 0,j = 1;
while(true){
yieldi;
vart = i;
i = j;
j += t;
}
};
varg = fib();
for(vari = 0;i < 10;i++){
console.log(g.next());
}

列表(字典、集合)映射表达式 (List、Dict、Set Comprehension)

Python的映射表达式可以非常方便的帮助用户构造列表、字典、集合等内置数据类型。

下面是列表映射表达式使用的例子:

1
2
3
4
5
6
>>> [x + 3 for x in range(4)]
[3,4,5,6]
>>> {x + 3 for x in range(4)}
{3,4,5,6}
>>> {x: x + 3 for x in range(4)}
{0: 3,1: 4,2: 5,3: 6}

Javascript1.7开始也引入了Array Comprehension

1
2
var numbers = [1,2,3,4];
var doubled = [i * 2 for(i of numbers)];

Lamda表达式 (Lamda Expression )

Lamda表达式是一种匿名函数,基于著名的λ演算。许多语言诸如C#,Java都提供了对lamda的支持。Pyhton就是其中之一。Javascript没有提供原生的Lamda支持。但是有第三方的Lamda包。

1
g = lambda x : x*3

装饰器(Decorators)

Decorator是一种设计模式,大部分语言都可以支持这样的模式,Python提供了原生的对该模式的支持,算是一种对程序员的便利把。

Decorator的用法如下。

1
2
3
@classmethod
deffoo(arg1,arg2):
....

分享到

简单12步理解python装饰器

  1. 函数

在 Python 中,使用关键字 def 和一个函数名以及一个可选的参数列表来定义函数。函数使用 return 关键字来返回值。定义和使用一个最简单的函数例子:

def foo():
… return 1
foo()
1

函数体(和 Python 中所有的多行语句一样)由强制性的缩进表示。在函数名后面加上括号就可以调用函数。

  1. 作用域

在 Python 函数中会创建一个新的作用域。Python 高手也称函数有自己的命名空间。也就是说,当在函数体中遇到变量时,Python 会首先在该函数的命名空间中寻找变量名。Python 有几个函数用来查看命名空间。下面来写一个简单函数来看看局部变量和全局变量的区别。

a_string = “This is a global variable”
def foo():
… print locals()
print globals() # doctest: +ELLIPSIS
{…, ‘a_string’: ‘This is a global variable’}
foo() # 2
{}

内建函数 globals 返回一个包含所有 Python 能识别变量的字典。(为了更清楚的描述,输出时省略了 Python 自动创建的变量。)在注释 #2 处,调用了 foo 函数,在函数中打印局部变量的内容。从中可以看到,函数 foo 有自己单独的、此时为空的命名空间。

  1. 变量解析规则

当然,以上并不意味着我们不能在函数内部使用全局变量。Python 的作用域规则是, 变量的创建总是会创建一个新的局部变量但是变量的访问(包括修改)在局部作用域查找然后是整个外层作用域来寻找匹配。所以如果修改 foo 函数来打印全部变量,结果将是我们希望的那样:

a_string = “This is a global variable”
def foo():
… print a_string # 1
foo()
This is a global variable

在 #1 处,Python 在函数 foo 中搜索局部变量 a_string,但是没有找到,然后继续搜索同名的全局变量。

另一方面,如果尝试在函数里给全局变量赋值,结果并不是我们想要的那样:

a_string = “This is a global variable”
def foo():
… a_string = “test” # 1
… print locals()
foo()
{‘a_string’: ‘test’}
a_string # 2
‘This is a global variable’

从上面代码可见,全部变量可以被访问(如果是可变类型,甚至可以被修改)但是(默认)不能被赋值。在函数 #1 处,实际上是创建了一个和全局变量相同名字的局部变量,并且“覆盖”了全局变量。通过在函数 foo 中打印局部命名空间可以印证这一点,并且发现局部命名空间有了一项数据。在 #2 处的输出可以看到,全局命名空间里变量 a_string 的值并没有改变。

  1. 变量生命周期

值得注意的是,变量不仅是在命名空间中有效,它们也有生命周期。思考下面的代码:

def foo():
… x = 1
foo()
print x # 1
Traceback (most recent call last):

NameError: name ‘x’ is not defined

这个问题不仅仅是因为 #1 处的作用域规则(虽然那是导致 NameError 的原因),也与 Python 和很多其他语言中函数调用的实现有关。没有任何语法可以在该处取得变量 x 的值——它确确实实不存在!函数 foo 的命名空间在每次函数被调用时重新创建,在函数结束时销毁。

  1. 函数的实参和形参

Python 允许向函数传递参数。形参名在函数里为局部变量。

def foo(x):
… print locals()
foo(1)
{‘x’: 1}

Python 有一些不同的方法来定义和传递函数参数。想要深入的了解,请参考 Python 文档关于函数的定义。来说一个简单版本:函数参数可以是强制的位置参数或者可选的有默认值的关键字参数。

def foo(x, y=0): # 1
… return x - y
foo(3, 1) # 2
2
foo(3) # 3
3
foo() # 4
Traceback (most recent call last):

TypeError: foo() takes at least 1 argument (0 given)
foo(y=1, x=3) # 5
2

在 #1 处,定义了有一个位置参数 x 和一个关键字参数 y的函数。接着可以看到,在 #2 处通过普通传参的方式调用该函数——实参值按位置传递给了 foo 的参数,尽管其中一个参数是作为关键字参数定义的。在 #3 处可以看到,调用函数时可以无需给关键字参数传递实参——如果没有给关键字参数 y 传值,Python 将使用声明的默认值 0 为其赋值。当然,参数 x (即位置参数)的值不能为空——在 #4 示范了这种错误异常。

都很清楚简单,对吧?接下来有些复杂了—— Python 支持在函数调用时使用关键字实参。看 #5 处,虽然函数是用一个关键字形参和一个位置形参定义的,但此处使用了两个关键字实参来调用该函数。因为参数都有名称,所以传递参数的顺序没有影响。

反过来也是对的。函数 foo 的一个参数被定义为关键字参数,但是如果按位置顺序传递一个实参——在 #2 处调用 foo(3, 1),给位置形参 x 传实参 3 并给第二个形参 y 传第二个实参(整数 1),尽管 y 被定义为关键字参数。

哇哦!说了这么多看起来可以简单概括为一点:函数的参数可以有名称或位置。也就是说这其中稍许的不同取决于是函数定义还是函数调用。可以对用位置形参定义的函数传递关键字实参,反过来也可行!如果还想进一步了解请查看 Python 文档。

  1. 内嵌函数

Python 允许创建内嵌函数。即可以在函数内部声明函数,并且所有的作用域和生命周期规则仍然适用。

def outer():
… x = 1
… def inner():
… print x # 1
… inner() # 2

outer()
1

以上代码看起来有些复杂,但它仍是易于理解的。来看 #1 —— Python 搜索局部变量 x 失败,然后在属于另一个函数的外层作用域里寻找。变量 x 是函数 outer 的局部变量,但函数 inner 仍然有外层作用域的访问权限(至少有读和修改的权限)。在 #2 处调用函数 inner。值得注意的是,inner 在此处也只是一个变量名,遵循 Python 的变量查找规则——Python 首先在 outer 的作用域查找并找到了局部变量 inner。

  1. 函数是 Python 中的一级对象

在 Python 中有个常识:函数和其他任何东西一样,都是对象。函数包含变量,它并不那么特殊。

issubclass(int, object) # all objects in Python inherit from a common baseclass
True
def foo():
… pass
foo.class # 1

issubclass(foo.class, object)
True

也许你从未考虑过函数可以有属性——但是函数在 Python 中,和其他任何东西一样都是对象。(如果对此感觉困惑,稍后你会看到 Python 中的类也是对象,和其他任何东西一样!)也许这有点学术的感觉——在 Python 中函数只是常规的值,就像其他任意类型的值一样。这意味着可以将函数当做实参传递给函数,或者在函数中将函数作为返回值返回。如果你从未想过这样使用,请看下面的可执行代码:

def add(x, y):
… return x + y
def sub(x, y):
… return x - y
def apply(func, x, y): # 1
… return func(x, y) # 2
apply(add, 2, 1) # 3
3
apply(sub, 2, 1)
1

这个示例对你来说应该不陌生——add 和 sub 是标准的 Python 函数,都是接受两个值并返回一个计算的值。在 #1 处可以看到变量接收一个就像其他普通变量一样的函数。在 #2 处调用了传递给 apply 的函数 fun——在 Python 中双括号是调用操作符,调用变量名包含的值。在 #3 处展示了在 Python 中把函数作为值传参并没有特别的语法——和其他变量一样,函数名就是变量标签。

也许你之前见过这种写法—— Python 使用函数作为实参,常见的操作如:通过传递一个函数给 key 参数,来自定义使用内建函数 sorted。但是,将函数作为值返回会怎样?思考下面代码:

def outer():
… def inner():
… print “Inside inner”
… return inner # 1

foo = outer() #2
foo # doctest:+ELLIPSIS

foo()
Inside inner

这看起来也许有点怪异。在 #1 处返回一个其实是函数标签的变量 inner。也没有什么特殊语法——函数 outer 返回了并没有被调用的函数 inner。还记得变量的生命周期吗?每次调用函数 outer 的时候,函数 inner 会被重新定义,但是如果函数 ouer 没有返回 inner,当 inner 超出 outer 的作用域,inner 的生命周期将结束。

在 #2 处将获得返回值即函数 inner,并赋值给新变量 foo。可以看到如果鉴定 foo,它确实包含函数 inner,通过使用调用操作符(双括号,还记得吗?)来调用它。虽然看起来可能有点怪异,但是目前为止并没有什么很难理解的,对吧?hold 住,因为接下来会更怪异!

  1. 闭包

先不着急看闭包的定义,让我们从一段示例代码开始。如果将上一个示例稍微修改下:

def outer():
… x = 1
… def inner():
… print x # 1
… return inner
foo = outer()
foo.func_closure # doctest: +ELLIPSIS
(,)

从上一个示例可以看到,inner 是 outer 返回的一个函数,存储在变量 foo 里然后用 foo() 来调用。但是它能运行吗?先来思考一下作用域规则。

Python 中一切都按作用域规则运行—— x 是函数 outer 中的一个局部变量,当函数 inner 在 #1 处打印 x 时,Python 在 inner 中搜索局部变量但是没有找到,然后在外层作用域即函数 outer 中搜索找到了变量 x。

但如果从变量的生命周期角度来看应该如何呢?变量 x 对函数 outer 来说是局部变量,即只有当 outer 运行时它才存在。只有当 outer 返回后才能调用 inner,所以依据 Python 运行机制,在调用 inner 时 x 就应该不存在了,那么这里应该有某种运行错误出现。

结果并不是如此,返回的 inner 函数正常运行。Python 支持一种名为函数闭包的特性,意味着 在非全局作用域定义的 inner 函数在定义时记得外层命名空间是怎样的。inner 函数包含了外层作用域变量,通过查看它的 func_closure 属性可以看出这种函数闭包特性。

记住——每次调用函数 outer 时,函数 inner 都会被重新定义。此时 x 的值没有变化,所以返回的每个 inner 函数和其它的 inner 函数运行结果相同,但是如果稍做一点修改呢?

def outer(x):
… def inner():
… print x # 1
… return inner
print1 = outer(1)
print2 = outer(2)
print1()
1
print2()
2

从这个示例可以看到闭包——函数记住其外层作用域的事实——可以用来构建本质上有一个硬编码参数的自定义函数。虽然没有直接给 inner 函数传参 1 或 2,但构建了能“记住”该打印什么数的 inner 函数自定义版本。

闭包是强大的技术——在某些方面来看可能感觉它有点像面向对象技术:outer 作为 inner 的构造函数,有一个类似私有变量的 x。闭包的作用不胜枚举——如果你熟悉 Python中 sorted 函数的参数 key,也许你已经写过 lambda 函数通过第二项而非第一项来排序一些列表。也可以写一个 itemgetter 函数,接收一个用于检索的索引并返回一个函数,然后就能恰当的传递给 key 参数了。

但是这么用闭包太没意思了!让我们再次从头开始,写一个装饰器。

  1. 装饰器

装饰器其实就是一个以函数作为参数并返回一个替换函数的可执行函数。让我们从简单的开始,直到能写出实用的装饰器。

def outer(some_func):
… def inner():
… print “before some_func”
… ret = some_func() # 1
… return ret + 1
… return inner
def foo():
… return 1
decorated = outer(foo) # 2
decorated()
before some_func
2

请仔细看这个装饰器示例。首先,定义了一个带单个参数 some_func 的名为 outer 的函数。然后在 outer 内部定义了一个内嵌函数 inner。inner 函数将打印一行字符串然后调用 some_func,并在 #1 处获取其返回值。在每次 outer 被调用时,some_func 的值可能都会不同,但不论 some_func 是什么函数,都将调用它。最后,inner 返回 some_func() 的返回值加 1。在 #2 处可以看到,当调用赋值给 decorated 的返回函数时,得到的是一行文本输出和返回值 2,而非期望的调用 foo 的返回值 1。

我们可以说变量 decorated 是 foo 的装饰版——即 foo 加上一些东西。事实上,如果写了一个实用的装饰器,可能会想用装饰版来代替 foo,这样就总能得到“附带其他东西”的 foo 版本。用不着学习任何新的语法,通过将包含函数的变量重新赋值就能轻松做到这一点:

foo = outer(foo)
foo # doctest: +ELLIPSIS

现在任意调用 foo() 都不会得到原来的 foo,而是新的装饰器版!明白了吗?来写一个更实用的装饰器。

想象一个提供坐标对象的库。它们可能主要由一对对的 x、y坐标组成。遗憾的是坐标对象不支持数学运算,并且我们也无法修改源码。然而我们需要做很多数学运算,所以要构造能够接收两个坐标对象的 add 和 sub 函数,并且做适当的数学运算。这些函数很容易实现(为方便演示,提供一个简单的 Coordinate 类)。

class Coordinate(object):
… def init(self, x, y):
… self.x = x
… self.y = y
… def repr(self):
… return “Coord: “ + str(self.dict)
def add(a, b):
… return Coordinate(a.x + b.x, a.y + b.y)
def sub(a, b):
… return Coordinate(a.x - b.x, a.y - b.y)
one = Coordinate(100, 200)
two = Coordinate(300, 200)
add(one, two)
Coord: {‘y’: 400, ‘x’: 400}

但是如果 add 和 sub 函数必须有边界检测功能呢?也许只能对正坐标进行加或减,并且返回值也限制为正坐标。如下:

one = Coordinate(100, 200)
two = Coordinate(300, 200)
three = Coordinate(-100, -100)
sub(one, two)
Coord: {‘y’: 0, ‘x’: -200}
add(one, three)
Coord: {‘y’: 100, ‘x’: 0}

但我们希望在不修改 one、two 和 three的基础上,one 和 two 的差值为 {x: 0, y: 0},one 和 three 的和为 {x: 100, y: 200}。接下来用一个边界检测装饰器来实现这一点,而不用对每个函数里的输入参数和返回值添加边界检测。

def wrapper(func):
… def checker(a, b): # 1
… if a.x < 0 or a.y < 0:
… a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
… if b.x < 0 or b.y < 0:
… b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
… ret = func(a, b)
… if ret.x < 0 or ret.y < 0:
… ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
… return ret
… return checker
add = wrapper(add)
sub = wrapper(sub)
sub(one, two)
Coord: {‘y’: 0, ‘x’: 0}
add(one, three)
Coord: {‘y’: 200, ‘x’: 100}

装饰器和之前一样正常运行——返回了一个修改版函数,但在这次示例中通过检测和修正输入参数和返回值,将任何负值的 x 或 y 用 0 来代替,实现了上面的需求。

是否这么做是见仁见智的,它让代码更加简洁:通过将边界检测从函数本身分离,使用装饰器包装它们,并应用到所有需要的函数。可替换的方案是:在每个数学运算函数返回前,对每个输入参数和输出结果调用一个函数,不可否认,就对函数应用边界检测的代码量而言,使用装饰器至少是较少重复的。事实上,如果要装饰的函数是我们自己实现的,可以使装饰器应用得更明确一点。

  1. 函数装饰器 @ 符号的应用

Python 2.4 通过在函数定义前添加一个装饰器名和 @ 符号,来实现对函数的包装。在上面代码示例中,用了一个包装的函数来替换包含函数的变量来实现了装饰函数。

add = wrapper(add)

这种模式可以随时用来包装任意函数。但是如果定义了一个函数,可以用 @ 符号来装饰函数,如下:

@ wrapper
… def add(a, b):
… return Coordinate(a.x + b.x, a.y + b.y)

值得注意的是,这种方式和简单的使用 wrapper 函数的返回值来替换原始变量的做法没有什么不同—— Python 只是添加了一些语法糖来使之看起来更加明确。

使用装饰器很简单!虽说写类似 staticmethod 或者 classmethod 的实用装饰器比较难,但用起来仅仅需要在函数前添加 @装饰器名 即可!

  1. args 和 *kwargs

上面我们写了一个实用的装饰器,但它是硬编码的,只适用于特定类型的函数——带有两个参数的函数。内部函数 checker 接收两个参数,然后继续将参数传给闭包中的函数。如果我们想要一个能适用任何函数的装饰器呢?让我们来实现一个为每次被装饰函数的调用添加一个计数器的装饰器,但不改变被装饰函数。这意味着这个装饰器必须接收它所装饰的任何函数的调用信息,并且在调用这些函数时将传递给该装饰器的任何参数都传递给它们。

碰巧,Python 对这种特性提供了语法支持。请务必阅读 Python Tutorial 以了解更多,但在定义函数时使用 的用法意味着任何传递给函数的额外位置参数都是以 开头的。如下:

def one(args):
… print args # 1
one()
()
one(1, 2, 3)
(1, 2, 3)
def two(x, y,
args): # 2
… print x, y, args
two(‘a’, ‘b’, ‘c’)
a b (‘c’,)

第一个函数 one 简单的打印了传给它的任何位置参数(如果有)。在 #1 处可以看到,在函数内部只是简单的用到了变量 args —— *args 只在定义函数时用来表示位置参数将会保存在变量 args 中。Python 也允许指定一些变量,并捕获任何在 args 里的额外参数,如 #2 处所示。

  • 符号也可以用在函数调用时,在这里它也有类似的意义。在调用函数时,以 * 开头的变量表示该变量内容需被取出用做位置参数。再举例如下:

def add(x, y):
… return x + y
lst = [1,2]
add(lst[0], lst[1]) # 1
3
add(*lst) # 2
3

在 #1 处的代码和 #2 处的作用相同——可以手动做的事情,在 #2 处 Python 帮我们自动处理了。这看起来不错,*args 可以表示在调用函数时从迭代器中取出位置参数, 也可以表示在定义函数时接收额外的位置参数。

接下来介绍稍微复杂一点的用来表示字典和键值对的 *,就像 用来表示迭代器和位置参数。很简单吧?

def foo(**kwargs):
… print kwargs
foo()
{}
foo(x=1, y=2)
{‘y’: 2, ‘x’: 1}

当定义一个函数时,使用 kwargs 来表示所有未捕获的关键字参数将会被存储在字典 kwargs 中。此前 args 和 kwargs 都不是 Python 中语法的一部分,但在函数定义时使用这两个变量名是一种惯例。和 * 的使用一样,可以在函数调用和定义时使用

dct = {‘x’: 1, ‘y’: 2}
def bar(x, y):
… return x + y
bar(**dct)
3

  1. 更通用的装饰器

用学到的新知识,可以写一个记录函数参数的装饰器。为简单起见,仅打印到标准输出:

def logger(func):
… def inner(args, **kwargs): #1
… print “Arguments were: %s, %s” % (args, kwargs)
… return func(
args, **kwargs) #2
… return inner

注意在 #1 处函数 inner 接收任意数量和任意类型的参数,然后在 #2 处将他们传递给被包装的函数。这样一来我们可以包装或装饰任意函数,而不用管它的签名。

@logger
… def foo1(x, y=1):
… return x * y
@logger
… def foo2():
… return 2
foo1(5, 4)
Arguments were: (5, 4), {}
20
foo1(1)
Arguments were: (1,), {}
1
foo2()
Arguments were: (), {}
2

每一个函数的调用会有一行日志输出和预期的返回值。

再聊装饰器

如果你一直看到了最后一个实例,祝贺你,你已经理解了装饰器!你可以用新掌握的知识做更多的事了。

你也许考虑需要进一步的学习:Bruce Eckel 有一篇很赞的关于装饰器文章(http://www.artima.com/weblogs/viewpost.jsp?thread=240808),他使用了对象而非函数来实现了装饰器。你会发现 OOP 代码比纯函数版的可读性更好。Bruce 还有一篇后续文章 providing arguments to decorators,用对象实现装饰器也许比用函数实现更简单。最后,你可以去研究一下内建包装函数 functools,它是一个在装饰器中用来修改替换函数签名的装饰器,使得这些函数更像是被装饰的函数。

分享到