函数

调用函数

Python 系统内置了许多函数,可以直接调用,调用函数时,不仅会检查参数的个数,还会检查参数的类型,如果有异常系统会报错。

>>> abs(100)
100
>>> abs(-20)
20
>>> abs(12.34)
12.34

>>> abs(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: abs() takes exactly one argument (2 given)

>>> abs('a')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'

>>> max(1, 2)
2
>>> max(2, 3, 1, -5)
3

数据类型转换

>>> int('123')
123
>>> int(12.34)
12
>>> float('12.34')
12.34
>>> str(1.23)
'1.23'
>>> str(100)
'100'
>>> bool(1)
True
>>> bool('')
False

函数别名

>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1

函数定义

def my_abs(x):
if x >= 0:
return x
else:
return -x

函数参数

位置参数

# x 就是位置参数
def power(x):
return x * x

默认参数

# n 是默认参数,默认值2
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s

必选参数在前,默认参数在后,当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。

默认参数的使用还有一个坑,简单来说就是如果默认参数是一个容器,需要区别容器是只生成一次,还是每次调用都生成一个新的容器,如果是前者,可以给默认参数一个默认的容器,如果是后者,需要给默认参数一个 None 值,在函数内部判空再创建容器,详细说明见这篇文章

可变参数

在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。

>>> def calc(*numbers):
... sum = 0
... for n in numbers:
... sum = sum + n * n
... return sum
...
>>> calc(1, 2)
5

如果已经有一个list或者tuple,还可以这样调用可变参数:

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

关键字参数

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

>>> 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'}

如果已经有一个 dict,还可以这样调用关键字参数:

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra

参数组合

除了上述的参数类型,还有一种命名关键字参数,详细见这里,相对用的比较少,就不多提。

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数.

面向对象

定义类

正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。定义的class如果没有父类,就继承object类,这类似于OC的NSObject。

class Student(object):
pass

s = Student()
s.name = 'Michael'
print(s.name)

Python 可以动态绑定类方法,不过做业务开发使用较少,有兴趣的话可以看这里

大部分情况下,当我们定义一个类的时候,已经确定了该类的属性和方法:

class Student(object):

def __init__(self, name, age):
super(Student, self).__init__()
self.name = name
self.age = age

def sayHello(self):
print('Hello, my name is %s, I am %d.' % (self.name, self.age))

s = Student('Michel', 19)
s.sayHello()

@property

在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数。这显然不够合理,我们可以通过封装 getter and setter 方法来达到检查参数的目的,不过 Python 提供了 @property 装饰器用来实现对属性的封装,装饰器的详细知识笔者并没有深入了解,目前只是当做约定语法来使用,以后如果有需求再深入了解。直接来看看怎么使用吧:

class Student(object):

@property
def score(self):
return self._score

@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value

把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:

>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!

只定义getter方法,不定义setter方法就是一个只读属性。

调用父类方法

class A:
def method(self, arg):
pass

class B(A):
def method(self, arg):
# A.method(self,arg) # 1
# super(B, self).method(arg) # 2
super().method(arg) # 3
  1. 直接写类名调用(不建议使用)
  2. 用 super(type, obj).method(arg) 方法调用
  3. 在类定义中调用本类的父类方法,可以直接 super().method(arg)

多重继承

Python 支持多重继承,通过多重继承,一个子类可以同时获得多个父类的所有功能。

class Animal(object):
pass

class Mammal(Animal):
pass

class RunnableMixIn(object):
def run(self):
print('Running...')

class FlyableMixIn(object):
def fly(self):
print('Flying...')

class Dog(Mammal, RunnableMixIn):
pass

class Bat(Mammal, FlyableMixIn):
pass

在设计类的继承关系时,通常,主线都是单一继承下来的。如果需要“混入”额外的功能,通过多重继承就可以实现。这种设计通常称之为MixIn。

为了更好地看出继承关系,我们把Runnable和Flyable类改为RunnableMixIn和FlyableMixIn。

类方法

和大部分语言一样,Python也支持类方法,这样就可以在不创建实例的情况下,执行一些公共方法。

class Student(object):

def __init__(self, name, age):
super(Student, self).__init__()
self.name = name
self.age = age

@classmethod
def Func(cls):
print('cls:', cls)

s = Student('Michel', 19)
s.Func()

# cls: <class '__main__.Student'>
# cls: <class '__main__.Student'>

从上面的例子中可以看出,Python不但可以通过类名调用类方法,还可以通过实例调用类方法。

定制类

Python的class中有许多特殊用途的函数,可以帮助我们定制类。常用的有以下几个:

  • __slots__ 限制class实例能添加的属性
  • __str__() 格式化字符串时返回的字符串
  • __iter__() 返回一个迭代对象,用于实现 for ... in 循环
  • __getitem__() 用于取出指定下标的对象
  • __getattr__() 用于动态返回一个不存在的属性

这里就介绍一下最常用的 __str__(),其他的如果有兴趣可看这里

class Student(object):

def __init__(self, name, age):
super(Student, self).__init__()
self.name = name
self.age = age

def __str__(self):
return '%s: {name: %s, age: %d}' % (super().__str__(), self.name, self.age)

s = Student('Michel', 19)
print(s)

# <__main__.Student object at 0x1012355c0>: {name: Michel, age: 19}

这还是一个如何调用父类方法的例子。

枚举

枚举可以用来定义一组相关常量,Python中的Enum功能是通过定义一个class实现的,每个常量都是class的一个唯一实例。

>>> from enum import Enum

>>> Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员:

>>> for name, member in Month.__members__.items():
... print(name, '=>', member, ',', member.value)
...
Jan => Month.Jan , 1
Feb => Month.Feb , 2
Mar => Month.Mar , 3
Apr => Month.Apr , 4
May => Month.May , 5
Jun => Month.Jun , 6
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12

这时value属性则是自动赋给成员的int常量,默认从1开始计数。

如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:

from enum import Enum, unique

@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6

@unique装饰器可以帮助我们检查保证没有重复值。

枚举的用法:

>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
# 枚举转 value
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
# 值判断
>>> print(day1 == Weekday.Tue)
False
# value 转枚举
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True
>>> Weekday(7)
Traceback (most recent call last):
...
ValueError: 7 is not a valid Weekday
# 遍历枚举值
>>> for name, member in Weekday.__members__.items():
... print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat

顺带提一下,Python枚举的value不一定是int型,还可以是其他类型,比如str:,例如

from enum import Enum
class UploadServer(Enum):
Intranet = 'http://www.xxxxx.com/upload'
Extranet = 'http://www.yyyyy.com/upload'

单例

开发多线程的产品,经常会需要使用单例模式管理一些公用资源,比如缓存数据的操作。Python的单例模式实现方式有很多,附上一个Python式的线程安全的单例实现:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import threading

__all__ = ['singleton']


def singleton(cls, *args, **kw):
_instances = {}
_lock = threading.Lock()

def _singleton():
_lock.acquire()

if cls not in _instances:
_instances[cls] = cls(*args, **kw)

_lock.release()
return _instances[cls]

return _singleton


if __name__ == '__main__':

@singleton
class ClassA(object):
pass

print(ClassA())
print(ClassA())


# <__main__.ClassA object at 0x101340550>
# <__main__.ClassA object at 0x101340550>

模块 & 包

到这里基本上已经可以开始写Python程序了,唯一的问题就是你肯定不会想把所有的功能塞到一个文件里,那样的代码维护不说维护起来想死,开发起来都想哭吧。

模块

在Python中,一个.py文件就称之为一个模块(Module)。使用模块最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括Python内置的模块和来自第三方的模块。

使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突。点这里查看Python的所有内置函数。

使用模块

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

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'

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()

第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用author变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;
以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。
后面开始就是真正的代码部分。
你可能注意到了,使用sys模块的第一步,就是导入该模块,导入sys模块后,我们就有了变量sys指向该模块,利用sys这个变量,就可以访问sys模块的所有功能。

最后,注意到这两行代码:

if __name__=='__main__':
test()

当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量name置为main,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

这个功能很实用,这样在开发的时候就可以快速验证自己的不确定的想法,对于初学者来说,简直完美。

开发模块

其实上文在介绍使用模块的时候已经把开发模块的注意事项讲的差不多了,这里补充一点。

回到上面单例的代码块,读者是否注意到下面这行代码:

__all__ = ['singleton']

这行代码通常紧跟在 import 语句之后,列出了当前模块默认开放的功能。

这样使用该模块时,可以通过下面的方法导入若干模块:

from singleton import *

如果模块内部没有定义 __all__ 或者 __all__ 中没有列出模块外部所需的方法,就需要使用下面的方法导入指定的功能:

from moduleA import fun1, fun2

或者导入整个模块:

import moduleB

# 通过 moduleB 调用所需方法
moduleB.xxx()

为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。

事实上,在写这篇博文的时候还是没有搞懂python3怎么开发一个分包的模块,稍后看下是否有时间研究研究。

结尾

Python 语法的介绍到这里就差不多了,笔者的基础语法知识主要是从廖雪峰的官方网站上学习的,之后的开发过程中遇到的问题,基本靠搜索,这里要提一下,搜索到的解决方案很多都是基于 Python2.7 的,Python3.5 的 API 变化比较大,建议对照官方文档