def multiReturn():
return 1,2
>>>multiReturn()
(1,2)
语法非常简单,实际上Python以元组的形式将多个返回值包装并返回。
除了常规的参数(位置参数)语法外,Python也支持默认参数,可变参数以及词典参数语法。
默认参数语法
def func(args=defaultVal):
statements
示例:
def area(width=0, height=0):
return width * height
可变参数语法
def func(args,*varg):
statements
可变参数用*修饰,它作为元组tuple传入。
示例:
>>> print(1,2)
语法:
def func(args,**argdict):
statements
示例:
>>>def func(**dict):
print(dict)
>>>func(key='val')
{'key': 'val'}
可以看到赋值式形式的词典参数被当作词典包装并传入。
如果想要只接收特定命名的词典参数,则需使用命名语法:
def func(args,*,keyName):
statements
示例:
>>>def func(*,keyName):
print(keyName)
>>>func(keyName='val')
命名词典参数需要*作为分隔符,分隔符后的参数需要以赋值式的形式赋实参,若没有传入参数将报错。
在 Python 中定义函数,可以用位置参数、默认参数、可变参数、词典参数和命名词典参数,这 5 种参数都可以组合使用,除了可变参数无法和命名词典参数混合。但是请注意,参数定义的顺序必须是:位置参数、默认参数、可变参数/命名词典参数和词典参数。
Python作为动态类型的语言对函数实参的类型不进行检查,为了避免错误类型实参造成的错误可以使用isinstance(obj,class)
函数检测对象是否为某一类的实例:
>>> isinstance(0,int)
True
lambda是一种匿名函数,使用lambda关键字定义:
lam = lambda args: statements
示例:
>>> lam = lambda x,y: x + y
>>> print(lam(1,2))
3
Python秉持一切都是对象的概念,函数被认为是对象。使用dir()函数可以查看对象的成员。
>>> abs
<built-in function abs>
>>> dir(abs)
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']
函数作为对象是函数式编程中的重要概念。函数作为对象可以作为参数传入另一个函数,也可以作为返回值返回。
和其他对象一样,函数对象也有自己的作用域,函数对象的作用域与def所在的层级相同。函数中可以访问上一级作用域中的对象,这些对象称为函数的环境变量。
函数对象和它的环境变量一起构成了函数的闭包(closure),函数对象的__closure__
属性中保存着函数的环境变量。__closure__
是一个元组(tuple),这个元组中的每个元素是cell类型的对象。
>>>def wrapper():
a = 1
def func():
print(a)
print(func.__closure__)
>>>wrapper()
(<cell at 0x0120D530: int object at 0x1D9F56F0>,)
生成器(generator)是用来生成序列的语法。示例1:
>>> even = (2 * x for x in range(1,5))
>>> even
<generator object <genexpr> at 0x0116D4B0>
通过类似列表生成式的写法可以创建一个generator。与列表保存具体对象不同,generator虽然也是iterable。但是generator保存的是生成序列的算法,只有在使用next()或for循环访问时才会生成具体对象。
generator还可以结合yield关键字,写成类似与函数的语法:
def fibo(max):
a = 1
b = 1
for n in range(0,max):
#print(b)
yield b
a, b = b , a+b
return
max = 10
obj = fibo(max)
print(obj.next())
for i in fibo(max):
print(i)
在使用next()或for循环访问generator时,执行到yield语句时将会记录断点并返回,下次使用next()或for循环访问时从断点处继续执行。
虽然generator的语法类似函数,但是采用obj = gen()返回的不是返回值而是generator对象。
>>> obj = fibo
>>> obj
<function fibo at 0x0000000002F19F98>
>>> obj = fibo(max)
>>> obj
<generator object fibo at 0x0000000002F32438>
send(msg)是generator另外一个重要方法,在后面协程部分有如下代码:
def consumer():
r = ''
while True:
n1 = yield r
if not n1:
return
print('Consuming %s' % n1)
r = 'OK'
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('Producing %s' % n)
r = c.send(n)
print('Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)
第一次启动generator只能使用next()或send(None),在generator第一次遇yield返回时n1没有被定义。
然后,produce()函数调用c.send(n)把实参值赋给yield的左值n1并从下一行开始继续执行generator。
filter()函数接受一个只有一个参数的函数对象func和一个iterable作为参数,将func作用于iterable的每一个元素,若func(item)返回True则将item加入到结果中iterator返回。
示例,删除偶数:
def is_odd(n):
return n % 2 == 1
print(list(filter(is_odd,[1,2,3,4,5,6])))
示例,素数筛:
#prime.py
def odd_iter():
n = 1
while True:
n = n + 2
yield n
def not_divisible(n):
return lambda x: x % n != 0
max = 100
def primes():
n = 2
it = odd_iter()
while n < max:
yield n
n = next(it)
it = filter(not_divisible(n), it)
for i in primes():
print(i)
它有助于理解上述代码。
>>>not_divisible(2)(4)
False
Google的著名论文《MapReduce: Simplified Data Processing on Large Clusters》中提出了map / reduce概念。
Python提供了map函数,接受一个只有一个参数的函数对象func和一个iterable作为参数,将func作用于iterable的每一个元素,并将结果作为iterator返回。
>>> m = map(abs,[-1,2,-3,4])
>>> print(m)
<map object at 0x0116CDB0>
>>> list(m)
[1, 2, 3, 4]
在Python3里,reduce()函数已经被从全局命名空间里移除了,它现在被放置在fucntools模块里。reduce函数接受一个有两个参数的函数对象func和一个iterable作为参数,将func的结果与下一个元素一起计算,并将结果作为iterator返回。
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
示例,求平方和:
>>> from functools import reduce
>>> f = lambda x,y: x*x + y*y
>>> reduce(f,[1,2,3,4])
1172
示例,将字符串转换为数字:
from functools import reduce
def char2num(s):
return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6,'7':7, '8': 8, '9': 9}[s]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
Python的内置函数sorted用于对列表(list)的排序,默认进行升序排列:
>>> sorted([-1,2,-3,4])
[-3, -1, 2, 4]
sorted可以接受一个key函数对象,按照key返回值进行升序排列。按照绝对值进行升序排列。
>>> sorted([-1,2,-3,4],key=abs)
[-1, 2, -3, 4]
Python提供的装饰器(decorator)机制可以在运行期动态的改变函数的功能,decorator是一个接受一个函数对象为参数并返回一个函数对象的函数。
import time
def decorator(func):
def wrapper(arg):
print('logging',time.localtime())
return func(arg)
return wrapper
@decorator
def func(arg):
print(arg)
func(arg)
logging time.struct_time(tm_year=2016, tm_mon=2,...
'arg'
返回的结果中可以发现>>>func(arg)
实际上调用了>>>decorator(now)(arg)
。此时,func.__name__
属性值已经变为"wrapper"。
为了防止依赖函数名的调用出错,需要将wrapper的__name__
等属性更改为func的相应属性,Python中的functools.wraps
装饰器可以完成这一任务:
import time
from functools import wraps
def decorator(func):
@functools.wraps(func)
def wrapper(arg):
print('logging',time.localtime())
return func(arg)
return wrapper
@decorator
def func(arg):
print(arg)
func(arg)
Python同样提供了带有参数的装饰器,它们需要三层嵌套的函数定义:
import time
def log(text):
def decorator(func):
def wrapper(arg):
print(text)
return func(arg)
return wrapper
return decorator
@log('invoke')
def func(arg):
print(arg)
这种装饰器的调用>>>func(arg)
等价于:
>>>log('invoke')(now)(arg)
Python秉持一切都是对象的面向对象的编程理念,包括函数(方法)以及类本身在内元素都可以作为对象处理。结合Python动态类型的特点使得Python具有灵活的动态编程机制。
函数(方法)是一个对象使得类对方法的处理与对属性的处理同样灵活。类作为一个对象意味着类可以在任意作用域下创建,包括函数作用域和类及其实例的作用域。
type函数用来返回对象的类型,因为类的继承与多态机制type返回的类型为引用指向对象的确切类型,而非它的基类类型。在Python中类也是一个对象,它的类型为<class 'type'>。
isinstance(obj,class)用来判断一个对象是不是某一类的实例。某一个类的实例与该类的基类判断结果仍为True。
isinstance(obj,(class1,...))可以判断对象obj是否为元组中类的一种。
dir()以list的形式返回对象的所有属性和方法名。
Python中的类也是一个对象,类与其实例(对象)提供了不同级别的作用域。当试图通过实例访问一个属性时,Python会先在对象的作用域中搜索该属性,若对象作用域中不存在该属性则继续搜索类作用域。若在类作用域中找到了该属性,Python将把这个属性复制一个副本,加入到对象的作用域中。
也就是说,类定义中声明并初始化的属性即为类属性(其它语言中的static属性)。类作为一个对象,允许通过类名来访问属性,ClassName.attribute
。若通过对象修改了某一属性,其它对象以及类中的同名属性将不会发生改变;若通过类名修改了某一属性,已经访问过该属性的对象不会改变(副本已放入对象作用域中),未访问过该属性的对象因为存在拷贝过程,其同名属性会改变。
若想要属性只属于对象不属于类,可以在__init__
方法中声明并初始化该属性。
class ClassName(object):
class_att = 'default class attribute';
def __init__(self):
self.ins_att = 1;
obj1 = ClassName();
obj2 = ClassName();
obj1.class_att = 'class attribute changed by obj1';
print(ClassName.class_att,'\n', obj1.class_att, '\n', obj2.class_att);
print('\n');
ClassName.class_att = 'class attribute changed by ClassName';
print(ClassName.class_att,'\n', obj1.class_att, '\n', obj2.class_att);
Python中作为动态语言可以在类定义完成后仍然可以为类和对象添加成员。对一个未声明和初始化的属性赋值,可以为类或对象动态地添加属性。
ClassName.newAttribute = val;
obj.newAttribute = val;
同样地,将一个函数对象赋值给类或其实例的成员也可以动态地添加方法。但是函数对象必须进行类型转换才可以进行赋值:
from types import MethodType
def func(self,args):
pass
class ClassName(object):
pass
ClassName.newMethod = MethodType(func,ClassName);
与很多OO语言不同的是,Python的对象可以有自己的方法。对象的方法由对象本身持有,类及其它对象无法访问该方法。
obj.newMethod = MethodType(func,obj);
obj.newMethod = MethodType(func,ClassName);
这两种方式均可以为对象添加新方法。
__slots__
元组示例:
>>> class person:
... __slots__=('name',)
...
>>> boy = person()
>>> boy.age = 10
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'person' object has no attribute 'age'
>>>person.age = 10
__slots__
是一个元组,在运行时为对象动态添加的成员(属性和方法)名必须包含在__slots__
中,否则将引发AttributeError。__slots__
必须在类定义时声明并初始化,运行时动态添加的__slots__
没有限制作用。
在定义class时声明并初始化的属性不受__slots__
的限制,运行时动态地为类添加的属性也不受限制。
在试图访问类或其实例的一个成员时,Python首先会试图搜索并访问类的属性,若查找失败Python会调用__getattr__(attrName)
方法,尝试以其返回值作为属性值。如有必要__getattr__
需要显式抛出AttributeError,或定义None作为返回值,避免AttributeError的发生。
上一节提到的"动态地"是指在类的定义已经完成之后,可以由脚本指令为类或其实例添加成员。而__getattr__
方法则是在程序发布之后(脚本代码不再改变),根据用户对Python程序的输入(而非程序员给解释器的指令)动态地添加成员。
class person:
def __getattr__(self,name):
return 'name'
boy = person();
print(boy.name)
class person:
@property
def age(self):
return self._age
@age.setter
def age(self, value):
self._age = value
@property修饰器可以将getter方法名转换为私有属性名,@attr.setter修饰器则可以为属性提供setter方法。在调用时访问属性实际上是通过getter方法进行,设置属性则通过setter方法进行。若要定义常属性则可以不定义setter。
“鸭子类型”是动态面向对象语言设计中的一个概念,即如果一个东���行走像鸭子,游泳像鸭子,进食像鸭子那它就是鸭子。即一个对象具有某一类的特征,即可作为该类来对待,不必强制实现接口。
Python中还有一些其它的特殊方法,重写这些方法可以使类实例可以进行迭代,切片,比较,算术运算等操作,起到Java中继承接口的效果(或者说C++中重载运算符的效果)。
__setitem__
按照索引赋值;__getitem__
按照索引获取值,要注意传入的索引值是单个下标(int)还是切片(slice)对象,可以采用isinstance()
进行区分。__call__
重载函数调用,调用obj(args)
即调用obj.__call__(args)
。
__len__
获得长度,调用len(obj)时即调用obj.__len__()
;__iter__()
方法使得类对象可以使用next()或者for循环迭代,__iter__
方法可以写成generator函数。
其它运算符重载包括:__cmp__
比较运算;__add__
加运算;__sub__
减运算;__mul__
乘运算;__div__
除运算;__mod__
求余运算;__pow__
乘方。
Python中类的类型为"type",type()函数可以建立一个"type"类型对象即一个类。type函数有三个参数,class的名称,基类元组和成员名-成员对象字典。
def func(args):
print("fuck");
ClassObj = type('ClassName',(object,),dict(method1=func))
print(ClassObj)
print(dir(ClassObj))
Python的class语法实际上是调用type()函数进行动态编译。元类metaclass是进行类动态创建与修改的另一种方式。
def func(args):
pass
class aMetaclass(type):
def __new__(classObj, className, bases, attrs):
attrs.update(dict(aMethod=func));
return type.__new__(classObj,className,bases,attrs)
class aClass(object, metaclass=aMetaclass):
pass
obj = aClass();
print(dir(obj))
通常类的创建由ype()
或type.__new__()
完成,在指定了一个类的metaclass参数之后,该类对象的创建将交由metaclass来完成。metaclass.__new__
将会拦截类的创建,进行处理后调用type.__new__
进行创建。
相对于使用type()动态创建类,metaclass可以使用继承等OOP机制且作为类其功能比函数更为强大。metaclass拥有其它成员和类动态添加成员机制,而函数只能以硬编码编制创建逻辑。
示例,使用metaclass编写ORM:
ORM(Object Relational Mapping,对象-关系映射)把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。
tinyORM中首先定义了表中域的类型,然后定义Model类执行创建数据表的逻辑,类Users定义数据表'users'。
Users类创建时首先封装创建类的参数,然后封装Model的创建参数执行基类Model的创建。Model创建过程被ModelMetaclass.__new__
拦截,传入的name参数为'users'(__new__
只处理Model派生类的建立,对Model类的建立不进行处理)。
__new__
将一个成员映射为一列,并存入__mapping__
中和保存数据表名字的__table__
一起添加到Users的属性中。__new__
返回修改后的类, 执行Model.__init__
进行初始化。
#tinyORM.py
#Define field types
class Field(object):
def __init__(self, name, column_type):
self.name = name
self.column_type = column_type
def __repr__(self):
return '<%s:%s>' % (self.__class__.__name__, self.name)
class StringField(Field):
def __init__(self, name):
super(StringField, self).__init__(name, 'varchar(100)')
class IntegerField(Field):
def __init__(self, name):
super(IntegerField, self).__init__(name, 'bigint')
#define Model
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
if name=='Model':
return type.__new__(cls, name, bases, attrs)
print('Found model: %s' % name)
mappings = dict()
for k, v in attrs.items():
if isinstance(v, Field):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v
for k in mappings.keys():
attrs.pop(k)
attrs['__mappings__'] = mappings # save mapping of attribute-column
attrs['__table__'] = name # name the table after the class name
return type.__new__(cls, name, bases, attrs)
class Model(dict, metaclass=ModelMetaclass):
def __init__(self, **kw):
super(Model, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
def save(self):
fields = []
params = []
args = []
for k, v in self.__mappings__.items():
fields.append(v.name)
params.append('?')
args.append(getattr(self, k, None))
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))
#send sql and args to database
#The end of ORM, the following code should be created by ORM's users.
#define table 'user' and its structure
class Users(Model):
# 定义类的属性到列的映射:
id = IntegerField('id')
name = StringField('username')
password = StringField('password')
def test()
u = Users(id=2333, name='username', password='password')
u.save()
test()