这篇来聊聊python的函数和对象
一、函数
Python内置了很多有用的函数,我们可以直接调用。
Python中函数也是一个对象,可以赋值,可以拷贝,可以像普通变量那样使用.
函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!
python函数是一个对象,那么他也就有属性,函数对象有一个
__name__
属性,可以拿到函数名字
定义函数
def function_name(args):
function_body;
调用函数
调用函数的方式function_name(formal_args):
>>> def power(x):
... return x*x;
...
>>> power(4)
16
>>>
高阶函数
能够接收函数作为参数的函数称之为高阶函数
-
map()
- map()函数接收两个参数,一个是函数,一个是序列,map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回。
>>> 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]
-
reduce()
- reduce把一个函数作用在一个序列[x1, x2, x3…]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,效果如下:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
# 例如求和计算
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
-
filter()
- 和map()类似,filter()也接收一个函数和一个序列。和map()不同的时,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
def is_odd(n):
return n % 2 == 1
filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])
# 结果: [1, 5, 9, 15]
匿名函数
关键字lambda表示匿名函数,冒号前面的x表示函数参数。它没有名字,只有参数和表达式:
lambda args: expression
>>> d = lambda x: x*x;
>>> d(2)
4
如果想定义一个什么事也不做的空函数,可以用pass语句:
def nop():
pass
二、模块
有时候我们把很多函数移到一个python文件中,作为工具类使用,这时候我们要怎么去使用这些外部函数呢?使用import导入即可。
比如你写一个myutils.py的工具类
def addition(x1,x2):
return x1+x2
这时候你要使用myutils里面的addition方法,便如下引入:
from myutils import addition
这样我们即从myutils里引入了addition方法,你直接使用即可。当然你也可以这样:
import myutils
print myutils.addition(1,2)
不过这样就有点繁琐了,我们通过上面的方法直接引入,直接使用更方便。
如果你有一系列的公共Python文件需要放在一个另一个文件夹下,这时候我们又怎么引入呢?
首先,我们把文件放入到一个新的文件夹下,结构如下:
/demo
-/common
-myutils.py
-test.py
-home.py
这时候我们要在home.py文件中使用common包下的myutils.py内的addition方法怎么操作?
首先我们要在common包下创建__init__.py
文件,文件内容可以不写。
这样我们就创建了一个common的包,然后我们在home.py中如下引入:
from common.myutils import addition
这样我们就可以使用addition方法了。
这就是Pyhont的模块引入。感兴趣的同学可以自行搜索深入学习。
三、面向对象
类和实例
用class关键字定义类:
class ClassName(object):
...
在python中,__init__()
可以被当成构造器,如下所示:
class ClassName(object):
def __init__(self, args):
pass
我们可以在构造方法中传入创建对象所需要的参数。
self是必须的,这是类方法都必须有的,外部调用时候不用传入。
不过不像其他语言中的构造器,它并不创建实例——它仅仅是你的对象创建后执行的第一个方法。它的目的是执行一些该对象的必要的初始化工作。因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。
Python对象有如下一些特性:
可以自由地给一个实例变量绑定属性
对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数和关键字参数。
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线
__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
(双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量)有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
获取对象信息
type(): 获取对象类型
isinstance(): 判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上
dir(): 获得一个对象的所有属性和方法,返回一个包含字符串的list
getattr(): getattr(obj, 'y') # 获取属性'y' 'y'可以是类的属性也可以是类的方法
setattr(): setattr(obj, 'y', 19) # 设置一个属性'y'
hasattr(): hasattr(obj, 'x') # 有属性'x'吗?
如果试图获取不存在的属性,会抛出AttributeError的错误
可以传入一个default参数,如果属性不存在,就返回默认值:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
多态
和java的多态差不多,子类继承父类,重写父类的方法,实现多态。
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
定义一个方法
def run_twice(animal):
animal.run()
animal.run()
然后:
>>> run_twice(Dog())
Dog is running...
Dog is running...
>>> run_twice(Cat())
Cat is running...
Cat is running...
静态语言 vs 动态语言
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:
class Timer(object):
def run(self):
print('Start...')
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。
实际上可以有另外一种解释:
run_twice方法中,animal只是一个普通变量
的名字,形参换成anything再来看呢?答案显然是可以的。
四、错误和异常处理
捕捉异常
所以高级语言通常都内置了一套try…except…finally…的错误处理机制,Python也不例外。
# 不含finally
try:
pass
except Exception as e:
raise e
# 含有finally
try:
pass
except Exception as e:
raise e
finally:
pass
一般我们使用如上两种情况即可,当然,你可能也知道异常是有不同的种类的,常见的有参数异常,空指针异常等等,感兴趣的同学可以自行去搜索,我这里就简单粗暴全部给把所有异常都给捕获了。
调试
在python的开发中,我们肯定经常碰到各种错误和异常,这时候我们怎么去排查问题呢?
一是使用print
二是使用python的logging模块
使用print就不用多说了,这里我们着重来说下使用logging模块吧,直接上代码:
import logging
# 配置输出等级为debug和输出格式
logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s][%(thread)d][%(filename)s][line: %(lineno)d][%(levelname)s] ## %(message)s')
logging.debug('debug')
logging.info('info')
logging.warning('warning')
logging.error('error')
logging.critical('critical')
这样我们便可以在控制台上看到我们的信息。
[2018-08-17 11:33:57,345][140104970061632][testlog.py][line: 5][DEBUG] ## debug
[2018-08-17 11:33:57,345][140104970061632][testlog.py][line: 6][INFO] ## info
[2018-08-17 11:33:57,345][140104970061632][testlog.py][line: 7][WARNING] ## warning
[2018-08-17 11:33:57,345][140104970061632][testlog.py][line: 8][ERROR] ## error
[2018-08-17 11:33:57,345][140104970061632][testlog.py][line: 9][CRITICAL] ## critical
logging的参数解释:
debug : 打印全部的日志,详细的信息,通常只出现在诊断问题上
info : 打印info,warning,error,critical级别的日志,确认一切按预期运行
warning : 打印warning,error,critical级别的日志,一个迹象表明,一些意想不到的事情发生了,或表明一些问题在不久的将来(例如。磁盘空间低”),这个软件还能按预期工作
error : 打印error,critical级别的日志,更严重的问题,软件没能执行一些功能
critical : 打印critical级别,一个严重的错误,这表明程序本身可能无法继续运行
仅仅输出到控制台是不够的,我们肯定是需要把日志输出到文件中的的,下面我们来自定义一个logger工具类提供给其他模块使用:
import logging
# 创建一个logger
logger = logging.getLogger('mylogger')
logger.setLevel(logging.DEBUG) # 设置log信息的输出级别
# 创建一个handler,用于写入日志文件
fh = logging.FileHandler('test.log')
fh.setLevel(logging.DEBUG)
# 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# 定义handler的输出格式
formatter = logging.Formatter('[%(asctime)s][%(thread)d][%(filename)s][line: %(lineno)d][%(levelname)s] ## %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 给logger添加handler
logger.addHandler(fh)
logger.addHandler(ch)
# 记录一条日志
logger.info('foorbar')
这样便可以输出到当前目录的test.log文件中
我们可以把这样的一个自定义logger放到公共模块中,就可以提供给其他模块用了。