针对Activity而言,Android存在一个回退栈维护界面体验。当一个应用程序图标被点击时,应用的Task就会来到前台,并把应用的主Activity压入BackStack的栈顶,并获得焦点,这个Activity称之为根Activity,而BackStack中的Activity可以通过点击回退键弹出栈并销毁,使上一个Activity获得焦点,直到用户返回到Home页,而当BackStack中的Activity都被销毁之后,这个Task就不服存在,这个程序的进程还存在(不在此时销毁)。

Task的状态

每个个Task都存在一个BackStack,而系统中可以存在多个Task,但是每次只有一个Task获得前台焦点,一般而言,系统允许用户在多个Task中切换,而被置于后台的Task的Activity将被置于Stopped状态。实际上,同一个Task中的Activity,只要不存在于栈顶并且获得前台焦点,那么它就是一个Stopped的状态。

Activity启动模式

根据Activity的不同启动模式,它在BackStack中的状态时不一样的。Activity可以通过AndroidManifest.xml清单文件配置,在节点中的android:laucherMode属性设置。它有四个选项:

  • standard
  • singleTop
  • singleTask
  • singeInstance

    standard

    标准启动模式,也是默认缺省值。standard模式下的Activity会依照启动顺序压入BackStack中,示意图如下:

    singleTop

    单顶模式,这种模式下,启动一个Activity的时候如果发现BackStack的栈顶已经存在这个Activity了,就不会去重建新的Activity,而是复用这个栈顶已经存在的Activity,避免同一个Activity被重复开启。示意图如下:

    singleTop应用的场景很多,一般适用于可以复用而又有多个开启渠道的Activity,避免当一个Activity已经开启并获得焦点后,再次重复开启。比如说Android系统浏览器的书签页面。

    singleTask

    开启一个Activity的时候,检查BackStack里面是否有这个Activity的实例存在,如果存在,清空BackStack里这个Activity上所有的其他Activity。

    singleInstance

    被标记为singleInstance启动模式的Activity,在启动的时候,会开启一个新的BackStack,这个BackStack里只有一个Activity的实例存在,并且使这个BackStack获得焦点。这是一种极端的模式,它会导致整个设备的操作系统里,只存在一个这个Activity实例,不论从何处启动的。

    singleInstance一般只存在一个适用场景,Android系统的来电页面,多次来电均使用的是一个Activity。

当然,在Android中,除了在AndroidManifest.xml清单文件中配置LauncherMode属性外,还可以在代码中设置启动模式。在组件中,启动一个Activity,需要用到startActivity()方法,其中传递一个Intent,可以使用Intent.setFlags(int flags)来设置新启动的Activity的启动模式,而通过代码设置Activity的启动模式的方式,优先级要高于在AndroidManifest.xml清单文件中的配置。

Intent.setFlag(int flags)方法传递的一个整形的数据,被Android系统设置为了常量:

  • FLAG_ACTIVITY_NEW_TASK:这个标识会使新启动的Activity独立创建一个Task。
  • FLAG_ACTIVITY_CLEAR_TOP:这个标识会使新启动的Activity检查是否存在于Task中,如果存在则清除其之上的Activity,使它获得焦点,并不重新实例化一个Activity,一般结合FLAG_ACTIVITY_NEW_TASK一起使用。
  • FLAG_ACTIVITY_SINGLE_TOP:等同于在LauncherMode属性设置为singleTop。

  1. 开启WebView的JS支持

    1
    webView.getSettings().setJavaScriptEnabled(true);
  2. 对WebView进行JavascriptInterface绑定,JS脚本通过这个借口来调用Java代码

    1
    2
    //	this为借口对象,第二个参数为JS中改参数的别名
    webView.addJavascriptInterface(this,"zoe");
  3. Java中调用JS

    1
    2
    // 调用JS的test函数并传参"test"
    webView.loadUrl("javascript:test('"+test+"')");
  4. js中调用Java函数

    1
    <div id='b'><a onclick="window.zoe.onClick">b.c</a></div>

示例

layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<WebView
android:id="@+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="11" />


<Button
android:id="@+id/button1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="调用js无参函数" />
<Button
android:id="@+id/button2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="调用js有参函数" />

</LinearLayout>

activity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class MainActivity extends Activity {

private WebView contentView = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
contentView = (WebView)findViewById(R.id.webview);
contentView.getSettings().setJavaScriptEnabled(true);
contentView.loadUrl("file:///android_asset/web.html");
contentView.addJavascriptInterface(this, "zoe");
Button button1 = (Button)findViewById(R.id.button1);
Button button2 = (Button)findViewById(R.id.button2);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
contentView.loadUrl("javascript:fun1()");
}
});
button2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
contentView.loadUrl("javascript:fun2('参数')");
}
});
}
public void startFunction() {
Toast.makeText(this, "js调用了java函数", Toast.LENGTH_SHORT).show();
runOnUiThread(new Runnable() {
@Override
public void run() {

}
});
}
public void startFunction(final String str) {
Toast.makeText(this, "js调用了java函数,并传参:"+str, Toast.LENGTH_SHORT).show();
runOnUiThread(new Runnable() {
@Override
public void run() {

}
});
}
}

web.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<script type="text/javascript">
function fun1(){
document.getElementById("content").innerHTML +=
"<br\>java调用了js函数fun1";
}

function fun2(arg){
document.getElementById("content").innerHTML +=
("<br\>"+"java调用了js函数fun2,并传参:"+arg);
}

</script>
</head>
<body>
HTML标题 <br/>
<a onClick="window.zoe.startFunction()">调用java函数</a><br/>
<a onClick="window.zoe.startFunction('hello java')">调用java函数并传递参数</a>
<br/>
<div id="content">这里是内容</div>
</body>
</html>


例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<shape>
<!-- 填充 -->
<solid android:color="# ff9d77">
<!-- 渐变 -->
<gradient
android:startColor="# ff8c00"
android:endColor="# FFFFFF"
android:angle="270" />
<!-- 描边 -->
<stroke
android:width="2dp"
android:color="# dcdcdc" />
<!-- 圆角 -->
<corners android:radius="2dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />

solid:填充
gradient:渐变
startColor和endColor分别为起始和结束颜色
angle是渐变角度,必须是45的整倍数
type是渐变模式,可选linear,即线性渐变;也可以为radial径向渐变,径向渐变需要制定半径gradientRadius=”50”
strokr:描边
width指宽度,color值颜色,dashWitdh指定虚线宽,dashGap指定相隔距离
corners:原件
radius角的弧度,值越大角越圆

Swift初识

WWDC2014年6月3日苹果开发者大会发布,2010年7约开始开发

  • 基于C语言和Objective-C语言,使用现有的Cocoa和Cocoa Touch框架,无缝兼容C、Objective-C语言
  • 兼具编译语言的高性能(Performance)和脚背语言的交互性(Interactive)
  • 支持Playground,它允许程序实时预览,无需频繁创建和运行App
  • 简洁、安全、容易、灵活、高效

    使用let来声明常量,使用var来声明变量。常量只能赋一次值。

值声明的类型必须和赋的值一致,声明时类型是可选的,声明的同时赋值,编译器会自动推断类型。也可以在声明时,指定类型。

1
2
3
var myVariable = 42
let myConstant = 42
let explicitDouble:Double = 70

值永远不会被隐式转换为其他类型,如果需要把一个值转换成其他类型,需要显示转换。

1
2
3
let label = "The width is"
let width = 94
let widthLabel = label + String(width)

有一种更简单的把值转换成字符串的方法:把值写到括号里,并且在括号之前加一个反斜杠。

1
2
let apples = 3
let appleSummary = "I have \(apples) apples."

使用方括号[]来创建数组和字典,并使用下标或者键(key)来访问元素。

1
2
3
4
5
6
7
8
var shoppingList = [catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"

var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"

创建一个空数组或者字典,可以使用初始化语法

1
2
let emptyArray = String[]()
let emptyDictionary = Dictionary<String,Float>()

如果类型信息可以被推断出来,你可以用[]和[:]来创建数组和空字典————就像声明变量或者给函数传参数的时候一样。

1
shoppingList = []

控制流

使用ifswitch来进行条件操作,使用for-inforwhiledo-while来进行循环,包括条件和循环变量的括号可以省略,但是语句体的大括号是必须的。

if语句中,条件必须是一个布尔表达式

switch支持任意类型的数据及各种比较操作

1
2
3
4
5
6
7
8
9
10
let vegetable = "red pepper"
switch vegetable {
let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress":
let vegetableComment = "That would make a good tea sandwich."
case let x where x.hasSuffix("pepper"):
let vegetableComment = "Is it a spicy \(x)?"
default:
let vegetableComment = "Everything tastes good in soup."
}

运行switch中匹配到的子句之后,程序会退出switch语句,并不会继续向下运行,所以不需要在每个子句结尾写break

for-in可以遍历字典

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
largest

使用while来重复运行一段代码直到不满足条件。循环条件可以在开头也可以在结尾。

1
2
3
4
5
6
7
8
9
10
11
var n = 2
while n < 100 {
n = n * 2
}
n

var m = 2
do {
m = m * 2
} while m < 100
m

你可以在循环中使用..来表示范围,也可以使用传统的写法,两者是等价的:

1
2
3
4
5
6
7
8
9
10
11
var firstForLoop = 0
for i in 0..3 {
firstForLoop += i
}
firstForLoop

var secondForLoop = 0
for var i = 0; i < 3; ++i {
secondForLoop += 1
}
secondForLoop

函数和闭包

使用func来声明一个函数,使用名字和参数来调用函数,使用->来指定函数返回值

1
2
3
4
func greet(name:String,day:String) -> String{
return "Hello \(name),today is \(day)."
}
greet("Bob","Tuesday")

使用一个元组来返回多个值

1
2
3
4
func getGasPrices() -> (Double,Double,Double){
return (3.59,3.69,3.79)
}
getGasPrices()

函数可以带有可变个数的参数,这些参数在函数内表现为数组的形式:

1
2
3
4
5
6
7
8
func sumOf(number:Int...) -> Int {
var sum = 0
for number in numbers {
sum += number
}
return sum
}
sumOf(42,597,12)

函数可以嵌套,被嵌套的函数可以访问外侧函数的变量

1
2
3
4
5
6
7
8
9
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()

函数也可以作为另一个函数的返回值
func makeIncrementer() -> (Int-> Int){
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
函数也可以当做参数传入另一个函数
```swift
func hasAnyMatches(list:Int[],condition:Int -> Bool) -> Bool{
for item in list {
if condition(item){
return true
}
}
return false;
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20,19,7,12]
hasAnyMatches(numbers,lessThanTen)

函数实际上是一种特殊的闭包,可以使用{}来创建一个匿名闭包,使用in来将参数和返回值类型声明与闭包函数体进行分离

1
2
3
4
5
numbers.map({
(number:Int) -> Int in
let result = 3 * number
return result
})

如果一个闭包的类型已知,比如作为一个回调函数,可以忽略参数的类型和返回值,单个语句闭包会把它语句的值当作结果返回

1
number.map({number in 3 *number})

可以通过参数位置而不是参数名字来引用参数————这种方法在非常短的闭包中很有用,当一个闭包作为最后一个参数传给一个函数的时候,它可以直接跟在括号后面

1
2
3
4
5
6
7
8
9
10
11
sort([1,5,3,12,2]){$0 > $1}

## 对象和类
使用`class`和类名来创建一个类,类中属性的声明和常量、变量声明一样,唯一区别的是上下文是类,同样方法和函数声明也一样
```swift
class Shape{
var numberOfSides = 0
func simpleDescriotion() -> String{
return "A shape with \(numberOfSides) sides."
}
}

创建一个类的实例,在类名后加上括号,使用点语法来访问实例的属性和方法

1
2
3
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

使用init来创建一个构造器

1
2
3
4
5
6
7
8
9
10
11
12
class NamedShape{
vae numberOfSides:Int = 0
var name:String

init(name:String) {
self.name = name
}

func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}

如果你需要在删除对象之前进行一些清理工作,使用deinit创建一个析构函数。

子类的定义方法是在类名后加上父类的名字,用冒号分割,创建类的时候并不需要一个标准的根类,可以忽略父类

子类如果重写父类的方法的话,需要用override标记————如果没有添加override就重写父类方法的话编译器会报错,编译器同样会检测override标记的方法是否确实在父类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Square:NamsedShape {
var sideLength:Double

init(sideLength:Double,name:String){
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}

override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()

属性可以有 getter 和 setter 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0

init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}

var perimeter: Double {
get {
return 3.0 * sideLength
}
set {
sideLength = newValue / 3.0
}
}

override func simpleDescription() -> String {
return "An equilateral triagle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
triangle.perimeter
triangle.perimeter = 9.9
triangle.sideLength

perimeter的setter中,新值的名字是newValue。也可以再set之后显示的设置一个名字

不需要计算属性,但是仍然需要在设置一个新值之前或者之后运行代码,使用willSetdidSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 确保三角形的边长总是和正方形的边长相同
class TriangleAndSquare {
var triangle : EquilateralTriangle {
willSet{
square.sideLength = newValue.sideLength
}
}
var square:Square {
willSet {
triangle.sideLength = newValue.sideLength
}
}
init(size:Double,name:String){
square = Square(sideLength:size,name:name)
triangle = EquilateralTriangle(sideLength:size,name:name)
}
}
var triangeleAndSquare = TriangleAndSquare(size:10,name:"another test shape")
triangleAndSquare.square.sideLength
triangleAndSquare.triangle.sideLenth
triangleAndSquare.square = Square(sideLength:50,name:"larger square")
triangleAndSquare.triangle.sideLength

类中的方法和一般的函数有一个重要的区别,函数的参数名只在函数内部使用,但是方法的参数名需要在调用的时候显示说明(除了第一个参数)。默认情况下,方法的参数名和它在方法内部的名字一样,不过也可以定义第二个名字,这个名字被用在方法内部

1
2
3
4
5
6
7
8
class Counter {
var count:Int = 0
func incrementBy(amout:Int,numberOfTimers times:Int){
count += amount * times
}
}
var counter = Counter()
counter.incrementyBy(2,numberOfTimes:8)

出来变量的可选值时,可以再操作(比如方法、属性和子脚本)之前加?,如果?之前的值是nil,?后面的东西都会被忽略,并且整个表达式返回nil,否则,?之后的东西都会被运行,这两种情况下,整个表示式的值也是一个可选值

1
2
let optionalSquare:Square ?= Square(sideLength:2.5,name:"optional square")
let sideLength = optionalSquare?.sideLength

枚举和结构体

使用enum来创建一个枚举,枚举可以包含方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enum Rank:Int {
case Ace = 1
case Two,Three,Four,Five,Six,Seven,Eight,Nine,Ten
case Jack,Queen,King
func simpleDesription() -> String {
switch self{
case .Ace:
return "ace"
case .Jack:
return "Jack"
case .Queen:
return "Queen"
case .King:
return "King"
default:
return String(self.toRaw())
}
}
}
let ace = Rank.Ace
let aceRawValue = ace.toRaw()

上面的枚举原始值的类型是Int,所以需要设置当一个原始值,剩下的原始值会按照顺序赋值。也可以使用字符串或者浮点数作为枚举的原始值。

使用toRawfromRaw函数来在原始值和枚举值之间进行转换

1
2
3
if let convertedRank = Rank.froRaw(3) {
let threeDescription = convertedRank.simpleDescription()
}

枚举的成员是实际值,并不是原始值的另一种表达式。如果原始值没有意义,不重要设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum Suit {
case Spades,Hearts,Diamonds,Clubs
func simpleDescription() -> String {
switch self {
case .Spades:
return "spades"
case .Hearts:
return "hearts"
case .Diamonds:
return "diamonds"
case .Clubs
return "clubs"
}
}
}
let hearts = Suit.Hearts
let heartsDescription = hearts.simpleDescription()

使用struct来创建一个结构体,结构体和类有很多相同的地方,比如方法和构造器,他们之间最大的区别就是,结构体是值传递,类是引用传递

1
2
3
4
5
6
7
8
9
struct Card{
var rank:Rank
var suit:Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription)"
}
}
let threeOfSpades = Card(rank: .Three,suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simleDescription()

协议和扩展

使用protocol来声明一个协议

1
2
3
4
protocol ExampleProtocol {
var simpleDescription: String{get}
mutating func adjust()
}

类、枚举和结构体都可以实现协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class SimpleClass:ExampleProtocol {
var simpleDescription:String = "A very simple class"
var anotherProperty: Int = 69105
func adjust(){
simpleDescription += "Noew 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription

struct SimpleStructire:ExampleProtocol{
var simpleDescription:String = "A simple structure"
mutating func adjust(){
simpleDescription += "(adjust)"
}
}
var b = SimpleStructire()
b.adjust()
let bDescruption = b.simpleDescription

声明的时候mutating关键字用来标记一个会修改结构体的方法。

使用extension来为现有的类型添加功能,比如新的方法和参数。

1
2
3
4
5
6
7
8
9
extension Int: ExampleProtocol{
var simpleDescription:String{
return "The number \(self)"
}
mutating func adjust(){
self += 42
}
}
7.simpleDescription

你可以像使用其他命名类型一样使用协议名——例如,创建一个有不同类型但是都实现一个协议的对象集合。当你处理类型是协议的值时,协议外定义的方法不可用。

1
2
3
let protocolValue: ExampleProtocol = a
protocolValue.simpleDescription
// protocolValue.anotherProperty // Uncomment to see the error

泛型

在尖括号里写一个名字来创建一个泛型函数或者类型

1
2
3
4
5
6
7
8
func repeat<ItemType>(item: ItemType,times:Int) -> ItemType[]{
var result = ItemType[]()
for i in 0..times{
result += item
}
return result
}
repeat("knock",4)

也可以创建泛型类、枚举和结构体

1
2
3
4
5
6
enum OptionalValue<T> {
case None
case Some(T)
}
var possibleInteger: OptionalValue<Int> = .None
possibleInteger = .Some(100)

在类型名后面使用where来指定类型的需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类

1
2
3
4
5
6
7
8
9
10
11
func anyCommonElements <T,U where T:Sequence,U:Sequence,T.GeneratorType.Element:Equatable,T.GeneratorType.Element == U.GeneratorType.Element> (1hs: T, rhs: U) -> Bool {
or lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])

简单起见,可以忽略where,只在冒号后面写协议或者类名。<T: Equatable>和是等价的。

在Python中,一个.py文件就称为一个模块(Module)。
为了避免模块名冲突,Python按目录来组织模块,称之为包(backage)。
每个包目录下面都会有一个__init__py文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以使空文件,也可以有Python代码。

使用模块

Python本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用。
sys模块为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# !/usr/bin/env python
# -*- coding:utf-8 -*-

' a test module '
# 第一个字符串会被默认视为模块的文档说明
__author__ = 'Zoe'

import sys

def test():
args = sys.argv
if len(args) == 1:
print 'Hello,world!'
elif len(args) == 2:
print 'Hello,%s!' % args[1]
else:
print 'Too many arguments!'

if __name__=='__main__':
test()
# 当命令行运行本模块时,Python解释器吧一个特殊变量__name__置为__main__,而如果在其他地方导入该模块,if判断将会失败。
# 要导入后调用才会生效if判断

别名

导入模块,还可以使用别名,这样,可以在运行时根据当前环境选择最合适的模块。
比如Python标准库一般会提供StringIO和cStringIO两个库,这两个库的接口和功能是一样的,但是cStringIO是C写的,速度更快,所以,你会经常看到这样的写法:

1
2
3
4
try:
import cStringIO as StringIO
except ImportError: # 导入失败会捕获到ImportError
import StringIO

还有类似simplejson这样的库,在Python 2.6之前是独立的第三方库,从2.6开始内置,所以,会有这样的写法:

1
try: import json #  python >= 2.6 except ImportError: import simplejson as json #  python <= 2.5

作用域

在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们不希望别人使用,有的函数和变量我们希望仅仅在模块内部使用,在Python中,是通过_前缀来实现的。
正常的函数和变量名是公开的(public),可以被直接饮用。
类似xxx这样的变量是特殊变量,可以被直接饮用,但是有特殊用途,比如__name____author__,模块定义的文档注释课可以用__doc__访问。
类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接饮用。

安装第三方模块

首先需要安装setuptools工具,Mac和Linux自带了此工具,Windows
下载get-pip文件,运行Python get-pip.py命令,并将\Python27\Spripts路径添加到Path
安装第三方模块easy_install XXX即可
安装一个第三方库——Python Imaging Library,这是Python下非常强大的处理图像的工具库。一般来说,第三方库都会在Python官方的pypi.python.org网站注册,要安装一个第三方库,必须先知道该库的名称,可以在官网或者pypi上搜索,比如Python Imaging Library的名称叫PIL,因此,安装Python Imaging Library的命令就是:

1
easy_install PIL

模块搜索路径

当加载一个模块时,Python会在指定的路径下搜索对应的.py文件(搜索当前目录、所有已安装的内置模块和第三方模块),如果找不到会报错。
要添加自己的搜索目录,一是直接修改模块的目录,即修改.path变量,而是设置在环境变量中。

使用future

Python提供了future模块,把下一个新版本的特性导入到当前版本,于是我们就可以在当前版本中测试一些新版本的特性。举例说明如下:

为了适应Python 3.x的新的字符串的表示方法,在2.7版本的代码中,可以通过unicode_literals来使用Python 3.x的新的语法:

1
2
3
4
5
6
7
8
#  still running on Python 2.7

from __future__ import unicode_literals

print '\'xxx\' is unicode?', isinstance('xxx', unicode)
print 'u\'xxx\' is unicode?', isinstance(u'xxx', unicode)
print '\'xxx\' is str?', isinstance('xxx', str)
print 'b\'xxx\' is str?', isinstance(b'xxx', str)

函数是一种把大段代码封装的一种形式,通过一层层的函数调用,可以把复杂的任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
函数式编程——Functional Programming可以归结到面向过程的程序设计,但其思想更接近数学计算。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程编写的函数没有变量。因此任意一个函数只要输入是确定的,输出就是确定的,这种函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于韩式内部的变量状态不稳定,同样的输入,可能得到不同的输出,因此这种函数就是有副作用的。
函数式编程的一个特点就是允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python对函数式编程提供部分支持,由于Python允许使用变量,因此,Python不是纯函数式编程语言。

高阶函数

传入函数

map()函数接收两个参数,一个函数,一个是序列,map将传入函数一次作用到序列的每个元素,并把结果作为新的list返回。
举例说明,比如我们有一个函数f(x)=x^2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现如下:

1
2
3
4
5
>>> def f(x):
... return x * x
...
>>> map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]

map()函数这种能够接收函数作为参数的函数,称之为高阶函数Higher-order function)。

再看reduce的用法。reduce把一个函数作用在一个序列[x1, x2, x3…]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

1
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

比方说对一个序列求和,就可以用reduce实现:

1
2
3
4
5
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25

考虑到字符串str也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把str转换为int的函数:

1
2
3
4
5
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, map(int, '13579'))
13579

整理成一个str2int的函数就是:

1
2
3
4
def str2int(s):
def fn(x, y):
return x * 10 + y
return reduce(fn, map(int, s))

还可以用lambda函数进一步简化成:

1
2
def str2int(s):
return reduce(lambda x,y: x*10+y, map(int, s))

排序算法

Python内置的sorted()函数可以对list进行排序

1
2
>>> sorted([34,12,43,65,1])
[1, 12, 34, 43, 65]

sorted()函数也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序,比如实现一个倒叙排序

1
2
3
4
5
6
def reversed_cmp(x,y):
if x > y:
return -1
if x < y:
return 1
return 0

传入自定义的比较函数reversed_cmp,就可以实现倒序排序:

1
2
>>> sorted([36, 5, 12, 9, 21], reversed_cmp)
[36, 21, 12, 9, 5]

再看一个字符串排序的例子:

1
2
>>> sorted(['about', 'bob', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']

默认情况下,对字符串排序,是按照ASCII的大小比较的,由于’Z’ < ‘a’,结果,大写字母Z会排在小写字母a的前面。

函数作为返回值

高阶函数除了可以接收函数作为参数外,也可以把函数作为结果值返回。

1
2
3
4
5
6
7
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum

调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

1
2
3
4
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function sum at 0x10452f668>
>>> f() # 调用函数f时,才真正计算求和的结果

匿名函数

当我们在传入函数时,有时候,不需要显式地定义函数,直接传入匿名函数更方便。
在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:

1
2
>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]

通过对比可以看出,匿名函数lambda x: x * x实际上就是:

1
2
def f(x):
return x * x

关键字lambda表示匿名函数,冒号前面的x表示函数参数。

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

1
2
3
4
5
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x10453d7d0>
>>> f(5)
25

同样,也可以把匿名函数作为返回值返回,比如:

1
2
def build(x, y):
return lambda: x * x + y * y

装饰器

由于函数也是一个对象,而且函数对象可以被赋值给变量,所以通过变量也能调用该函数

1
2
3
4
5
6
>>> def now():
... print '2014-03-16'
...
>>> f = now
>>> f()
2014-03-16

函数对象有一个__name__属性,可以拿到函数的名字:

1
2
3
4
>>> now.__name__
'now'
>>> f.__name__
'now'

假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

1
2
3
4
5
def log(func):
def wrapper(*args, **kw):
print 'call %s():' % func.__name__
return func(*args, **kw)
return wrapper

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

1
2
3
@log
def now():
print '2013-12-25'

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

1
2
3
>>> now()
call now():
2013-12-25

把@log放到now()函数的定义处,相当于执行了语句:

1
now = log(now)

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

1
2
3
4
5
6
7
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print '%s %s():' % (text, func.__name__)
return func(*args, **kw)
return wrapper
return decorator

这个3层嵌套的decorator用法如下:

1
2
3
@log('execute')
def now():
print '2013-12-25'

执行结果如下:

1
2
3
>>> now()
execute now():
2013-12-25

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

1
>>> now = log('execute')(now)

我们来剖析上面的语句,首先执行log(‘execute’),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的’now’变成了’wrapper’:

1
2
>>> now.__name__
'wrapper'

因为返回的那个wrapper()函数名字就是’wrapper’,所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

1
2
3
4
5
6
7
8
import functools

def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print 'call %s():' % func.__name__
return func(*args, **kw)
return wrapper

或者针对带参数的decorator:

1
2
3
4
5
6
7
8
9
10
import functools

def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print '%s %s():' % (text, func.__name__)
return func(*args, **kw)
return wrapper
return decorator

import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上`@functools.wraps(func)`即可。

偏函数

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Parial function)
functools.partial的作用就是,把一个函数的某些参数(不管有没有默认值)给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

1
2
3
4
5
6
7
>>> import functools
>>> def fun(i):
... print i
...
>>> fun2 = functools.partial(fun,i = 1)
>>> fun2()
1

SwipRefreshLayout是Google官方推出的一款下拉刷新组件,可以实现Google Now上的下拉刷新效果。
SwipRefreshLayout只能有一个直接子View,且该组件必须是支持下拉刷新的,比如ListView和ScrollView

Demo示例

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</android.support.v4.widget.SwipeRefreshLayout>

</LinearLayout>

listview_item.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:gravity="center"
android:singleLine="true"
android:textSize="16sp"
android:textStyle="bold" />
</RelativeLayout>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class MainActivity extends Activity implements SwipeRefreshLayout.OnRefreshListener{
private SwipeRefreshLayout swipeLayout;
private ListView listView;
private ListViewAdapter adapter;
private List<ItemInfo> infoList;

@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

swipeLayout = (SwipeRefreshLayout)this.findViewById(R.id.swipe_refresh);
swipeLayout.setOnRefreshListener(this);

swipeLayout.setColorScheme(android.R.color.holo_red_light,android.R.color.holo_green_light,android.R.color.holo_blue_bright,android.R.color.orange_light);

infoList = new ArrayList<ItemInfo>();
Item info = new ItemInfo();
info.setName("icon");
infoList.add(info);
listView = (ListView) findViewById(R.id.listView);
adapter = new ListViewAdapter(this,infoList);

}

public void onRefresh(){
new Handler().postDelayed(new Runnable(){
public void run(){
ItemInfo info = new ItemInfo();
info.setName("icon-refresh");
info.add(info);
adapter.notifyDataSetChanged();
swipeLayout.setRefreshing(false);
}
},1500);
}
}
class ItemInfo {

/**
* id
*/
private int id;

/**
* name
*/
private String name;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}
class ListViewAdapter extends ArrayAdapter<ItemInfo> {

private LayoutInflater inflater;

public ListViewAdapter(Context context, List<ItemInfo> list) {
super(context, 0, list);
inflater = LayoutInflater.from(context);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ItemInfo info = getItem(position);

if (convertView == null) {
convertView = inflater.inflate(R.layout.item_listview, null);
}

TextView name = (TextView) convertView.findViewById(R.id.item_name);
name.setText(info.getName());

return convertView;
}

}

切片

比如一个list去前3个元素,取指定索引范围的操作,用循环十分繁琐,因此,Python提供了切片(Slice)操作符,能大大简化这种操作。

1
2
3
>>> r = ['a','b','c','d','e','f','g']
>>> r[0:3]
['a', 'b', 'c']

L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。
记住倒数最后一个元素的索引是-1。

切片操作十分有用。我们先创建一个0-99的数列:

1
2
3
>>> L = range(100)
>>> L
[0, 1, 2, 3, ..., 99]

可以通过切片轻松取出某一段数列。比如前10个数:

1
2
>>> L[:10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

后10个数:

1
2
>>> L[-10:]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

前11-20个数:

1
2
>>> L[10:20]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

前10个数,每两个取一个:

1
2
>>> L[:10:2]
[0, 2, 4, 6, 8]

所有数,每5个取一个:

1
2
>>> L[::5]
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]

甚至什么都不写,只写[:]就可以原样复制一个list:

1
2
>>> L[:]
[0, 1, 2, 3, ..., 99]

tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:

1
2
>>> (0, 1, 2, 3, 4, 5)[:3]
(0, 1, 2)

字符串’xxx’或Unicode字符串u’xxx’也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:

1
2
3
4
>>> 'ABCDEFG'[:3]
'ABC'
>>> 'ABCDEFG'[::2]
'ACEG'

迭代

因为Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。

list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代:

1
2
3
4
5
6
7
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
... print key
...
a
c
b

默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.itervalues(),如果要同时迭代key和value,可以用for k, v in d.iteritems()。

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for value in d.itervalues():
... print value
...
1
3
2
>>> for k,v in d.iteritems():
... print k,v
...
a 1
c 3
b 2

如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:

1
2
3
4
5
6
7
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False

Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

1
2
3
4
5
6
>>> for i, value in enumerate(['A', 'B', 'C']):
... print i, value
...
0 A
1 B
2 C

for循环里,同时引用了两个变量,在Python里是很常见的,比如下面的代码:

1
2
3
4
5
6
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
... print x, y
...
1 1
2 4
3 9

列表生成式

列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
举个例子,要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用range(1, 11):

1
2
>>> range(1, 11)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

如果要生成[1x1, 2x2, 3x3, …, 10x10]怎么做?

1
2
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

1
2
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]

还可以使用两层循环,可以生成全排列:

1
2
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:

1
2
3
>>> import os #  导入os模块
>>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录
['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Library', 'Movies', 'Music', 'Pictures', 'Public', 'VirtualBox VMs', 'Workspace', 'XCode']

内建的isinstance函数可以判断一个变量是不是字符串:

1
2
3
4
5
6
>>> x = 'abc'
>>> y = 123
>>> isinstance(x, str)
True
>>> isinstance(y, str)
False

生成器

列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。

1
2
3
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x104feab40>

如果要一个一个打印出来,可以通过generator的next()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> g.next()
0
>>> g.next()
1
>>> g.next()
4
>>> g.next()
9
>>> g.next()
16
>>> g.next()
25
>>> g.next()
36
>>> g.next()
49
>>> g.next()
64
>>> g.next()
81
>>> g.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, …

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

1
2
3
4
5
6
def fab(max):
n, a, b = 0, 0, 1
while n < max:
print b
a, b = b, a + b
n = n + 1

上面的函数可以输出斐波那契数列的前N个数:

1
2
3
4
5
6
7
>>> fab(6)
1
1
2
3
5
8

仔细观察,可以看出,fab函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fab函数变成generator,只需要把print b改为yield b就可以了:

1
2
3
4
5
6
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

1
2
>>> fab(6)
<generator object fab at 0x104feaaa0>

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> def fun():
... print 'step1'
... yield
... print 'step2'
... yield
... print 'step3'
... yield
...
>>> o = fun()
>>> o.next()
step1
>>> o.next()
step2
>>> o.next()
step3
>>> o.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

  • padding 控件的内容相对控件边缘的边距
  • margin 控件边缘相对父控件的边距

  • android:gravity 该view内容的限定限定位置

  • android:layout_gravity 设置该view中的子view相对于父view的限定位置

list 和 tuple

list

list是Python内置的一种数据类型是列表,为一种有序的集合,可以随时添加和删除其中的元素。
例如,列出班级中所有同学的名字:

1
2
classmates = ['Michael','Bob','Tracy']
print classmates

可以用len()函数获取list元素的个数

1
2
>>> len(classmates)
3

用索引来访问list中每一个位置的元素,记得索引是从0开始的:

1
2
3
4
5
6
7
8
9
10
>>> classmates[0]
'Michael'
>>> classmates[1]
'Bob'
>>> classmates[2]
'Tracy'
>>> classmates[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range

当索引超出了范围时,Python会报一个IndexError错误,所以,要确保索引不要越界,记得最后一个元素的索引是len(classmates) - 1

如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素:

1
2
>>> classmates[-1]
'Tracy'

以此类推,可以获取倒数第2个、倒数第3个:

1
2
3
4
5
6
7
8
>>> classmates[-2]
'Bob'
>>> classmates[-3]
'Michael'
>>> classmates[-4]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range

当然,倒数第4个就越界了。

list是一个可变的有序表,所以,可以往list中追加元素到末尾:

1
2
3
>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']

也可以把元素插入到指定的位置,比如索引号为1的位置:

1
2
3
>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']

要删除list末尾的元素,用pop()方法:

1
2
3
4
>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']

要删除指定位置的元素,用pop(i)方法,其中i是索引位置:

1
2
3
4
>>> classmates.pop(1)
'Jack'
>>> classmates
['Michael', 'Bob', 'Tracy']

要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:

1
2
3
>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']

list里面的元素的数据类型也可以不同,比如:

1
>>> L = ['Apple', 123, True]

list元素也可以是另一个list,比如:

1
2
3
>>> s = ['python', 'java', ['asp', 'php'], 'scheme']
>>> len(s)
4

要注意s只有4个元素,其中s[2]又是一个list,如果拆开写就更容易理解了:

1
2
>>> p = ['asp', 'php']
>>> s = ['python', 'java', p, 'scheme']

要拿到’php’可以写p[1]或者s[2][1],因此s可以看成是一个二维数组,类似的还有三维、四维……数组,不过很少用到。

如果一个list中一个元素也没有,就是一个空的list,它的长度为0:

1
2
3
>>> L = []
>>> len(L)
0

tuple

另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改,比如同样是列出同学的名字:

1
>>> classmates = ('Michael', 'Bob', 'Tracy')

现在,classmates这个tuple不能变了,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素。

不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。

tuple的陷阱:当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来,比如:

1
2
3
>>> t = (1, 2)
>>> t
(1, 2)

如果要定义一个空的tuple,可以写成():

1
2
3
>>> t = ()
>>> t
()

但是,要定义一个只有1个元素的tuple,如果你这么定义:

1
2
3
>>> t = (1)
>>> t
1

定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1。
所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:

1
2
3
>>> t = (1,)
>>> t
(1,)

Python在显示只有1个元素的tuple时,也会加一个逗号,,以免你误解成数学计算意义上的括号。

最后来看一个“可变的”tuple:

1
2
3
4
5
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])

这个tuple定义的时候有3个元素,分别是’a’,’b’和一个list。不是说tuple一旦定义后就不可变了吗?怎么后来又变了?

别急,我们先看看定义的时候tuple包含的3个元素:

当我们把list的元素’A’和’B’修改为’X’和’Y’后,tuple变为:

表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向’a’,就不能改成指向’b’,指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!

小结

list和tuple是Python内置的有序集合,一个可变,一个不可变。根据需要来选择使用它们。

条件判断和循环

条件判断

根据Python的缩进规则,如果if语句判断是True,就把缩进的语句执行,否则不执行。
也可以给if添加一个else语句,如果if判断是False,就不执行if的内容,而执行else的内容

1
2
3
4
if(3>2):
print '执行这里'
else:
print '没执行这里'

注意不要少写冒号":"

1
2
3
4
5
6
7
8
9
elifelse if的缩写,完全可以有多个elif,所以if语句的完整形式就是:
if <条件判断1>:
<执行1>
elif <条件判断2>:
<执行2>
elif <条件判断3>:
<执行3>
else:
<执行4>

循环

Python的循环有两种,一种是for…in循环,用于遍历list或者tuple

1
2
3
colors = ['red','blue','green']
for color in colors:
print color

执行这段代码会依次打印colors的每一个元素

1
2
3
red
blue
green

Python提供了一个range()函数,可以生成一个整数序列,我们可以计算一个1-100的整数之和

1
2
3
4
sum = 0
for x in range(101):
sum = sum + x
print sum

第二种循环是while循环,和C语言一样,只要条件为True,就会执行循环体

1
2
3
4
5
6
sum = 0
n = 1
while n <= 100:
sum = sum + n
n = n + 1
print sum

如果死循环了,记得用Ctrl + C退出循环

dict和set

dict

dict就是dictionary,在Java里也成为map,使用键值对(key-value)存储,具有极快的查找速度
例如记录同学们的成绩

1
2
d = {'Michael':95,'Bob':75,'Tracy':85}
print d['Micheal']

除了初始化时指定外,存储value时,通过key放入(相同的key赋值的话,会覆盖掉之前的value):

1
>>> d['Adam'] = 76

如果key不存在dict会报错,为了避免这种错误,可以通过in判断key是否存在,或者使用dict的get方法,获取value,不存在的话会返回None

1
2
3
dic = {"a":67,"c":12,"b":1}
print 'd' in dic
print dic.get("d")

要删除一个key,使用pop(key)方法
与list相比,dict有以下几个特点

  1. 查找和插入的速度极快,不会随着key的增加而增加
  2. 需要占用大量内存,内存浪费多
    而list相反
  3. 查找和插入的时间随着元素的增加而增加
  4. 占用空间小,浪费内存很小

Set

Set和dict类似,但是不存在value,只存一组key,而且key不能重复,所以Set中没有重复的key

1
2
3
>>> s = set([1,2,3,4,5,1,2,3,4,5])
>>> s
set([1, 2, 3, 4, 5])

重复的元素会被自动过滤掉
添加元素可以使用set的add(key)方法,可以重复添加,但不会有效果,删除元素使用remove(key)方法

1
2
3
4
5
6
7
8
>>> s = set([])
>>> s.add(1)
>>> s.add(2)
>>> s.add(3)
>>> s.add(4)
>>> s.remove(3)
>>> s
set([1, 2, 4])

注意删除的元素必须存在,否则报错
set可以看成数学上的无序和无重复元素的集合,所以也可以做交集和并集的运算操作

1
2
3
4
5
6
>>> s1 = set([1,2,3,4])
>>> s2 = set([2,4,6,8])
>>> s1 & s2
set([2, 4])
>>> s1 | s2
set([1, 2, 3, 4, 6, 8])

函数

Python内置了很多很有用的函数,我们可以直接调用。可以在Python的官网上查看文档
可以在命令行通过help()查看某个函数的帮助信息

1
2
3
4
5
6
7
help(abs)
Help on built-in function abs in module __builtin__:

abs(...)
abs(number) -> number

Return the absolute value of the argument.

数据类型转换

Python内置常用的函数,比如int()函数就可以把其他数据类型转换为整数:

1
2
>>> int('123')
123

函数名其实就是函数对象的引用,所以可以用一个变量指向该引用,实现别名的效果

1
2
3
>>> a = abs
>>> a(-1)
1

主要使用函数时,一定要传入正确的参数,否则会出错。

函数的定义

在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。

1
2
def fun(x,y):
return x+y

空函数

定义一个什么事也不做的空函数,可以使用pass语句

1
2
def nop():
pass

参数检查

Python会进行参数个数的检查,如果不对会抛出TypeError,但是类型并不会进行检查

默认参数

在定义时为某个参数初始化一个值,若调用时,该参数缺省时,便会使用默认的值

1
2
3
4
5
6
def power(x,n = 2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s

可变参数

函数参数个数可变时,我们可以通过list或者tuple包装参数,传入函数,函数会接收到tuple

1
2
3
4
5
6
7
def add(numbers):
sum = 0
for number in numbers:
sum = sum + number
return sum

add([1,2,3,4,5])

1
2
3
4
5
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum

如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:

1
2
3
>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
14

关键字参数

关键字参数允许传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装成为一个dict

1
2
def person(name,age,**kw):
print 'name:',name,'age':,age,'other:',kw

函数person除了参数nameage外,还接受关键字参数kw。在调用该函数时,可以之传入必选参数,或者任意个关键字参数

1
2
3
4
5
6
>>> person('Michael', 30)
name: Michael age: 30 other: {}
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

参数组合

在Python中定义函数,可以用必选参数、默认参数、可变参数和关键字参数,这4种参数都可以一起使用,或者只用其中的一部分,但定义的顺序必须是:必选参数、默认参数、可变参数和关键字参数。
比如:

1
2
def fun(a,b,c=0,*args,**kw)
print 'a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw

调用时,Python接收器自动按照参数位置和参数名把对应的参数传进去

1
2
3
4
5
6
7
8
>>> func(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> func(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> func(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> func(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}

最神奇的是通过一个tuple和dict,你也可以调用该函数:

1
2
3
4
>>> args = (1, 2, 3, 4)
>>> kw = {'x': 99}
>>> func(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'x': 99}

递归函数

如果一个函数在内部调用自身,这个函数就是递归函数。
在Python中使用递归函数要注意栈溢出,在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

  • 尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。