Python进阶

Python函数进阶

返回多个返回值

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是一种匿名函数,使用lambda关键字定义:

lam = lambda args: statements

示例:

>>> lam = lambda x,y: x + y
>>> print(lam(1,2))
3

Python函数式编程基础

函数对象与闭包

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循环访问时才会生成具体对象。

  • yield

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

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

map / reduce

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

sorted

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]

装饰器decorator

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动态类型的特点使得Python具有灵活的动态编程机制。

函数(方法)是一个对象使得类对方法的处理与对属性的处理同样灵活。类作为一个对象意味着类可以在任意作用域下创建,包括函数作用域和类及其实例的作用域。

获取对象信息

  • type()

type函数用来返回对象的类型,因为类的继承与多态机制type返回的类型为引用指向对象的确切类型,而非它的基类类型。在Python中类也是一个对象,它的类型为<class 'type'>。

  • isinstance()

isinstance(obj,class)用来判断一个对象是不是某一类的实例。某一个类的实例与该类的基类判断结果仍为True。

isinstance(obj,(class1,...))可以判断对象obj是否为元组中类的一种。

  • dir()

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__的限制,运行时动态地为类添加的属性也不受限制。

  • 重写__getattr__方法

在试图访问类或其实例的一个成员时,Python首先会试图搜索并访问类的属性,若查找失败Python会调用__getattr__(attrName)方法,尝试以其返回值作为属性值。如有必要__getattr__需要显式抛出AttributeError,或定义None作为返回值,避免AttributeError的发生。

上一节提到的"动态地"是指在类的定义已经完成之后,可以由脚本指令为类或其实例添加成员。而__getattr__方法则是在程序发布之后(脚本代码不再改变),根据用户对Python程序的输入(而非程序员给解释器的指令)动态地添加成员。

class person:
def __getattr__(self,name):
    return 'name'

boy = person();
print(boy.name)

@property

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__乘方。

元类metaclass

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

你可能感兴趣的:(Python进阶)