Python


Python

range和xrange区别

要生成很大数字序列的时候,使用xrangerange性能优很多,因为不需要一上来就开辟一块很大的内存空间。

字典items和iteritems区别

都是python字典的内建函数,分别会返回python列表和迭代器

enumerate()函数

enumerate()函数用于将一个可遍历的数据对象(如列表,元祖或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在for循环中。

enumerate(sequence, [start=0])

  • sequenece: 一个序列,迭代器或其他支持迭代的对象。
  • start: 下标起始位置
  • 返回值: 返回enumerate(枚举)对象
>>>> seasons = ['Spring','Summer','Fall','Winter']
>>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>>> list(enumerate(seasons, 1))
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')   ]

itertools 函数

Python内置模块itertools提供非常有用的用于操作迭代对象的函数。其中combinations()可以生成一个list的全排列。题目举例https://leetcode.com/problems/largest-triangle-area/description/

枚举所有三角形计算出面积最大的面积,多边形面积计算公式可以参考 https://en.wikipedia.org/wiki/Shoelace_formula

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

import itertools

class Solution:
    @staticmethod
    def area(p1, p2, p3):
        return .5 * abs(p1[0]*p2[1] + p2[0]*p3[1] + p3[0]*p1[1] -
                p2[0]*p1[1] - p3[0]*p2[1] - p1[0]*p3[1])

    def largestTriangleArea(self, points):
        """
        :type points: List[List[int]]
        :rtype: float
        """
        return max(Solution.area(*triangle)
                for triangle in itertools.combinations(points, 3))


if __name__ == "__main__":
    tester = Solution()
    print(tester.largestTriangleArea([[0,0],[0,1],[1,0],[0,2],[2,0]]))
import itertools

a_list = [[1, 2], [3, 4], [5, 6]]

print(list(itertools.chain.from_iterable(a_list)))
#Output: [1, 2, 3, 4, 5, 6]

print(list(itertools.chain(*a_list)))
#Output: [1, 2, 3, 4, 5, 6]

以下原文: https://eastlakeside.gitbooks.io/interpy-zh/content/Generators/Generators.html

*args 和 **kwargs

def test_var_args(f_arg, *argv):
    print("first normal arg:", f_arg)
    for arg in argv:
        print("another arg through *argv:", arg)

test_var_args('yasoob', 'python', 'eggs', 'test')
## Output:
('first normal arg:', 'yasoob')
('another arg through *argv:', 'python')
('another arg through *argv:', 'eggs')
('another arg through *argv:', 'test')
def greet_me(**kwargs):
    for key,value in kwargs.items():
        print("{0} == {1}".format(key, value))

greet_me(name="yasoob")
## Output:
name == yasoob

标准参数与 *args, **kwargs 在使用时的顺序: some_func(fargs, *args, **kwargs)

生成器(Generators)

根据维基百科,迭代器是一个让程序员可以遍历一个容器(特别是列表)的对象。然而,一个迭代器在遍历并读取一个容器的数据元素时,并不会执行一个迭代。迭代器可以概括为:

def fibon(n):
    a = b = 1
    for i in range(n):
        yield a
        a, b = b, a + b

for x in fibon(1000000):
    print(x)

这种方式, 可以不用担心它会使用大量资源。Pythonstr对象不是一个迭代器,但是是可迭代对象, 内置函数 iter 会根据一个可迭代对象返回一个迭代器对象。

my_string = "Yasoob"
my_iter = iter(my_string)
print(next(my_iter))
# Output: 'Y'

Map, Filter 和 Reduce

Map Map会将一个函数映射到一个输入列表的所有元素上。

map(function_to_apply, list_of_inputs)

例子1:
items = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, items))

例子2:
def multiply(x):
    return (x*x)

def add(x):
    return (x+x)

funcs = [multiply, add]
for i in range(5):
    value = map(lambda x: x(i), funcs)
    print(list(value)) 
    # 加`list`转换是为了python2/3兼容 
    # python2中map直接返回列表, python3中返回迭代器

Filter filter 过滤列表中的元素,并且返回一个由所有符合要求(函数映射到该元素时返回值为True)的元素所构成的列表。

filter(function, iterable)

  • function: 判断函数
  • iterable: 可迭代对象
number_list = range(-5, 5)
less_than_zero = filter(lambda x: (x < 0), number_list)
print(list(less_than_zero))

Reduce 当需要对一个列表进行一些计算并返回结果时, reduce 是个非常有用的函数。

reduce(function, iterable[, initializer])

  • function: 函数有两个参数
  • iterable: 可迭代对象
  • initializer: 可选,开始参数
from functools import reduce

product = reduce((lambda x, y: x*y), [1, 2, 3, 4])

# Output: 24

set(集合)数据结构

set(集合)是一个非常有用的数据结构。它与列表(list)的行为类似,区别在于set不能包含重复的值。假设要检查列表中是否包含重复元素,可以有两种方法:

方式一:

 some_list = ['a', 'b', 'c', 'b', 'd', 'm', 'n', 'n']

duplicates = []
for value in some_list:
    if some_list.count(value) > 1:
        if value not in duplicates:
            duplicates.append(value)

print(duplicates)
# Output: ['b', 'n']

方式二:

some_list = ['a', 'b', 'c', 'b', 'd', 'm', 'n', 'n']

duplicates = set([x for x in some_list if some_list.count(x) > 1])

print(duplicates)

#Output: set(['b', 'n'])

set交集

valid = set(['yellow', 'red', 'blue', 'green', 'black'])
input_set = set(['red', 'brown'])
print(input_set.intersection(valid))

#Output: set(['red'])

set差集

# -*- coding: UTF-8 -*-
#!usr/bin/env python

valid = set(['yellow', 'red', 'blue', 'green', 'black'])
input_set = set(['red', 'brown'])
print(input_set.difference(valid))

#Output: set(['brown'])

# 也可以用`{}`符号来创建集合

a_set = {'red', 'blue', 'green'}
print(type(a_set))
## Output: <type 'set'>

三元运算符

三元运算符在Python里通常被称为条件表达式,这些表达式基于True False的条件判断,在Python 2.4以上才有三元操作。

condition_is_true if condition else condition_is_false

is_fat = True
state = "fat" is_fat else "not fat"

装饰器

装饰器(Decorators)Python的一个重要部分。简单的说:他们是修改其他函数的功能函数,他们有助于让我们的代码更简短。

def hi(name="yasoob"):
    return "hi " + name

print(hi())
#Output: hi yasoob

greet = hi

print(greet())
#Output: hi yasoob

del hi
print(hi())
#Output: NameError

print(greet())
#Output: hi yasoob
def hi(name="yasoob"):
    print("now you are inside the hi() function")

    def greet():
        return "now you are in the greet() function"

    def welcome():
        return "now you are in the welcome() function"

    print(greet())
    print(welcome())
    print("now you are back in the hi() function")

hi()

#Output: now you are inside the hi() function
#        now you are in the greet() function
#        now you are in the welcome() function
#        now you are back in the hi() function
def hi(name="yasoob"):

    def greet():
        return "now you are in the greet() function"

    def welcome():
        return "now you are in the welcome() function"

    if name == "yasoob":
        return greet
    else:
        return welcome
a = hi()
print(a)
#Output: <function greet at 0x10a64e050>

print(a())
#Output: now you are in the greet() function
def hi():
    return "hi yasoob!"

def doSomethingBeforeHi(func):
    print("I am doing some boring work before executing hi()")
    print(func())

doSomethingBeforeHi(hi)

#Output: I am doing some boring work before executing hi()
#        hi yasoob!

第一个装饰器

def a_new_decorator(a_func):
    def wrapTheFUnction():
        print("I am doing some boring work before executing a_func()")

        a_func()

        print("I am doing some boring work after executing a_func()")

    return wrapTheFUnction

def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my foul smell")

a_function_requiring_decoration()
#Output: I am the function which needs some decoration to remove my foul smell

a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
# now a_function_requiring_decoration is wrapped by wrapTheFUnction()

a_function_requiring_decoration()
#Output:  I am doing some boring work before executing a_func()
#         I am the function which needs some decoration to remove my foul smell
#         I am doing some boring work after executing a_func()

这个就是Python中装饰器做的事,它们封装一个函数,并且用这样或那样的方式来修改它的行为。@ 符号只是用一个简短的方式来生成一个被装饰的函数。可以修改成@来运行之前的代码:

def a_new_decorator(a_func):
    def wrapTheFUnction():
        print("I am doing some boring work before executing a_func()")

        a_func()

        print("I am doing some boring work after executing a_func()")

    return wrapTheFUnction

a_new_decorator
def a_function_requiring_decoration():
    """Hey you! Decorate me!"""
    print("I am the function which needs some decoration to remove my foul smell")

a_function_requiring_decoration()
#Output:  I am doing some boring work before executing a_func()
#         I am the function which needs some decoration to remove my foul smell
#         I am doing some boring work after executing a_func()

#the @a_new_decorator is just a short way of saying:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

但是我们运行如下代码会存在一个问题:

print(a_function_requiring_decoration.__name__)
#Output: wrapTheFunction

这不是我们想要的! Output输出应该是a_function_requiring_decoration。 这里的函数被 wrapTheFunction 替代了。 它重写了我们函数的名字和注释文档(docstring)。幸运的是Python提供给我们一个简单的函数来解决这个问题, 那就是 functools.wraps:

from functools import wraps

def a_new_decorator(a_func):
    @wraps(a_func)
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")

        a_func()

        print("I am doing some boring work after executing a_func()")

    return wrapTheFunction

a_new_decorator
def a_function_requiring_decoration():
    """Hey you! Decorate me!"""
    print("I am the function which needs some decoration to remove my foul smell")


print(a_function_requiring_decoration.__name__, a_function_requiring_decoration.__doc__)
#Output: ('a_function_requiring_decoration', 'Hey you! Decorate me!')

装饰器有一些使用场景

from functools import wraps

def decorator_name(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not can_run:
            return "Function will not run"

        return f(*args, **kwargs)

    return decorated

decorator_name
def func():
    return "Function is running"

can_run = True
print(func())
#Output: Function is running

can_run = False
print(func())
#Output: Function will not run

@wraps接受一个函数来进行装饰,并加入复制函数名称注释文档参数列表等功能。这可以让我们在装饰器里访问在装饰之前函数的属性。

from functools import wraps

def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if (not auth) or (not check_auth(auth.username, auth.password)):
            authenticate()

        return f(*args, **kwargs)

    return decorated
from functools import wraps

def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " war called")
        return func(*args, **kwargs)

    return with_logging

logit
def addition_func(x):
    """Do some math."""
    return x + x

print(addition_func(4))
#Output: addition_func war called
#        8

带参数的装饰器

@wraps也是一个装饰器,接收一个参数,就像任何普通函数能做的那样。 为什么我们不也那样做? 这是因为,当使用 @my_decorator 语法时,是在应用一个以单个函数作为参数的一个包裹函数。Python中每个东西都是一个对象,我们可以编写一个能返回一个包裹函数的函数。

from functools import wraps

def logit(logfile='out.log'):
    def loggine_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)

            with open(logfile, 'a') as opened_file:
                opened_file.write(log_string + ' \n')

            return func(*args, **kwargs)
        return wrapped_function
    return loggine_decorator

@logit()
def myfunc1():
    pass

myfunc1()

@logit(logfile='func2.log')
def myfunc2():
    pass

myfunc2()

装饰器类

现在我们有了能用于正式环境的logit装饰器,但我们应用的某些部分还是比较脆弱的,异常也许是需要更紧急关注的事情。比如只想打日志到一个文件,而有时想把问题发送一个 email, 同时也保留日志,留个记录。这是一个使用继承的场景,幸运的是,类也可以用来构建装饰器。我们以一个类而不是一个函数来重新构建logit

# -*- coding: UTF-8 -*-
#!usr/bin/env python

import types
from functools import wraps

class logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile

    def __call__(self, func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打开logfile并写入
            with open(self.logfile, 'a') as opened_file:
                # 现在将日志打到指定的文件
                opened_file.write(log_string + '\n')
            # 现在,发送一个通知
            self.notify()
            return func(*args, **kwargs)
        return wrapped_function

    def notify(self):
        # logit只打日志,不做别的
        print(self.logfile)
        pass


@logit(logfile='zhang.log')
def myfunc1():
    pass

myfunc1()
#Output: myfunc1 was called
#        zhang.log

class email_logit(logit):
    '''
    一个logit的实现版本,可以在函数调用时发送email给管理员
    '''
    def __init__(self, email='admin@myproject.com', *args, **kwargs):
        self.email = email
        super(email_logit, self).__init__(*args, **kwargs)

    def notify(self):
        # 发送一封email到self.email
        print(self.email)
        pass

@email_logit(email='garretthh07@gmail.com')
def myfunc2():
    pass

myfunc2()
#Output: myfunc2 was called
#        garretthh07@gmail.com

对象变动(Mutation)

Python可变(mutable)意味”可以被改动”, 不可变(immutable)就是常量(constant)

# -*- coding: UTF-8 -*-
#!usr/bin/env python

foo = ['hi']
print(foo)
#Output: ['hi']

bar = foo
bar += ['bye']
print(foo)
#Output: ['hi', 'bye']

这不是一个bug。这是由于对象可变性(mutability),每当将一个变量赋值为另一个可变类型的变量时,对这个数据的任意改动会同时反应到这两个变量上去。新变量只不过是老变量的一个别名而已。

# -*- coding: UTF-8 -*-
#!usr/bin/env python

def add_to(num, target=[]):
    target.append(num)
    return target

print(add_to(1))
#Output: [1]

print(add_to(2))
#Output: [1, 2]

print(add_to(3))
#Output: [1, 2, 3]

这是列表的可变性在作怪。在Python中当函数被定义时,默认参数只会运算一次,而不是每次被调用时都会重新运算。永远都不要定义可变类型的默认参数。

# -*- coding: UTF-8 -*-
#!usr/bin/env python

def add_to(num, target=None):
    if target is None:
        target = []

    target.append(num)
    return target

print(add_to(1))
#Output: [1]

print(add_to(2))
#Output: [2]

print(add_to(3))
#Output: [3]

__slots__ 魔法

Python中,每个类都有实例属性。默认情况下Python用一个字典来保存一个对象的实例属性。它允许我们在运行时去设置任意的新属性。然而,对于有着已知属性的小类来说,它可能是个瓶颈。这个字典浪费类很多内存。Python不能在对象创建时直接分配一个固定量的内存来保存所有的属性。因此如果你创建许多对象,它会消耗掉很多内存。不过还是有一个方法来规避这个问题。这个方法需要使用 __slots__来告诉Python不要使用字典,而且只给一个固定集合的属性分配空间。

class MyClass(object):
    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier
class MyClass(object):
    __slots__ = ['name', 'identifier']
    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier

第二段代码会为你的内存减轻负担。PyPy已经默认做了所有这些优化。https://github.com/ianozsvald/ipython_memory_usage可以用来查看在有与没有__slots时精确的内存占用。

虚拟环境(virtualenv)

virtualenv 是一个工具,能帮我们创建一个独立的Python环境。如果一个应用程序,依赖版本为2的第三方模块,另一个程序依赖版本为3, 怎么使用和开发这些应用程序?

如果把一切安装到/usr/lib/python2.7/site-packages(或其他平台的标准位置), 那很容易出现某个模块被升级而你却不知道的情况。 另一种情况,假如你已开发完成一个程序,但你不想更新它所依赖的第三方模块版本;但开始的另一程序,需要这些第三方模块版本。

使用virtualenv针对每个程序创建独立的Python环境,而不是在全局安装所依赖的模块。安装命令:

# install
sudo easy_install pip
sudo pip install virtualenv
sudo pip install nose
sudo pip install tornado

# run
virtualenv myproject
source myproject/bin/activate

执行第一个命令在myproject文件夹创建一个隔离的virtualenv环境,第二个命令激活这个隔离的环境(virtualenv)。在创建virtualenv时,必须作出决定:这个virtualenv是使用系统全局的模块,还是只使用这个virtualenv内的模块。 默认情况下,virtualenv不会使用系统全局模块。如果想让virtualenv使用系统全局模块,可以:

virtualenv --system-site-packages mycoolproject

使用以下命令可以退出这个virtualenv, 运行后会恢复使用系统全局的Python模块:

deactivate

可以使用 https://github.com/cxreg/smartcd来帮助管理环境,当切换目录时,可以帮助你激活和退出你的virtualenv

容器(Collections)

Python附带一个模块,包含许多容器数据类型,叫作collections。我们讨论它的作用和用法:

from collections import defaultdict

colours = (
    ('Yasoob', 'Yellow'),
    ('Ali', 'Blue'),
    ('Arham', 'Green'),
    ('Ali', 'Black'),
    ('Yasoob', 'Red'),
    ('Ahmed', 'Silver'),
)

favourite_colours = defaultdict(list)

for name,colour in colours:
    favourite_colours [name].append(colour)

print(favourite_colours)

#Output: defaultdict(<type 'list'>,
#       {'Arham': ['Green'], 'Yasoob': ['Yellow', 'Red'], 'Ahmed': ['Silver'], 'Ali': ['Blue', 'Black']})

当一个字典中对一个键进行潜逃赋值时,如果这个键不存在,会触发keyError异常。defaultdict允许我们用一个聪明的方式绕过这个问题。

'''
some_dict = {}
some_dict['colours']['favourite'] = "yellow"
'''
#Output: KeyError: 'colours'

import collections, json

tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['colours']['favourite'] = "yellow"

print(json.dumps(some_dict))
#Output: {"colours": {"favourite": "yellow"}}
# -*- coding: UTF-8 -*-
#!usr/bin/env python

from collections import Counter

colours = (
    ('Yasoob', 'Yellow'),
    ('Ali', 'Blue'),
    ('Arham', 'Green'),
    ('Ali', 'Black'),
    ('Yasoob', 'Red'),
    ('Ahmed', 'Silver'),
)

favs = Counter(name for name, coulout in colours)
print(favs)
# Output: Counter({'Yasoob': 2, 'Ali': 2, 'Arham': 1, 'Ahmed': 1})

with open('filename', 'a') as f:
    f.write('test01' + '\n')

# 可以用来统计一个文件相同的行
with open('filename', 'rb') as f:
    line_count = Counter(f)

print(line_count)
#Output: Counter({'test01\n': 3, 'test\n': 2})
# -*- coding: UTF-8 -*-
#!usr/bin/env python

from collections import deque

d = deque(maxlen = 30)
d.append('1')
d.append('2')

print(len(d), d[0], d[-1])
#Output: (2, '1', '2')

d = deque(range(5))
print(len(d))
#Output: 5

print(d.popleft())
#Output: 0

print(d.pop())
#Output: 4

print(d)
#Output: deque([1, 2, 3])

d = deque(range(5))
d.extendleft([1])
d.extend([6,7,8])

print(d)
#Output: deque([1, 0, 1, 2, 3, 4, 6, 7, 8])
man = ('Ali', 30)
print(man[0])

#Output: Ali

namedtuples就是把元组变成一个针对简单任务的容器。不必使用整数索引来访问一个namedtuples的数据。可以像字典(dict)一样访问namedtuples, 但 namedtuples 是不可变的。

from collections import namedtuple

Animal = namedtuple('Animal', 'name age type')
perry = Animal(name='perry', age=31, type='cat')

print(perry, perry.name, perry[0])

#Output: (Animal(name='perry', age=31, type='cat'), 'perry', 'perry')

可以看到,可以用名字来访问namedtuple中的数据。一个命名元组(namedtuple)有两个必须的参数1.元组名称 2.字段名称。上面的例子中,元组名称为Animal, 字段名称为name, age, typenamedtuple让元组变的自文档, 只要看一眼就很容易理解代码是做什么的。不必使用整数索引来访问一个命名元组,让代码更容易维护。而且, namedtuple的每个实例没有对象字典, 所以它们很轻量,与普通元组比,并不需要更多内存,使得比字典快。

namedtuple是一个元组,属性值是不可变的。应该使用命名元组让代码自文档,它们向后兼容于普通元组,既可以整数索引,也可以使用名称来访问namedtuple。如下可以把命名元组转换成字典:

from collections import namedtuple

Animal = namedtuple('Animal', 'name age type')
perry = Animal(name='perry', age=31, type='cat')

dic = perry._asdict()
dic.setdefault('gender', 'male')
print(dic)

#Output: OrderedDict([('name', 'perry'), ('age', 31), ('type', 'cat'), ('gender', 'male')])
# -*- coding: UTF-8 -*-
#!usr/bin/env python3

from collections import namedtuple
from enum import Enum

class Species(Enum):
    cat = 1
    dog = 2
    horse = 3
    aardvark = 4
    butterfly = 5
    owl = 6
    platypus = 7
    dragon = 8
    unicorn = 9
    # 依次类推

    # 我们不关心同一物种的年龄,所以我们可以使用一个别名:w
    kitten = 1
    puppy = 2

Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="Perry", age=31, type=Species.cat)
drogon = Animal(name="Drogon", age=4, type=Species.dragon)
tom = Animal(name="Tome", age=75, type=Species.cat)
charlie = Animal(name="Charlie", age=2, type=Species.kitten)

print(charlie.type, charlie.type == tom.type)
#Output: Species.cat True

print(Species(1), Species['cat'], Species.cat)
#Output: Species.cat Species.cat Species.cat

对象自省

自省(introspection), 在计算机编程领域里,是指在运行时判断一个对象类型的能力。它是Python的强项之一。Python中所有一切都是对象,而且我们可以仔细勘察那些对象。Python还包含许多内置函数和模块来帮助我们。

my_list = [1, 2, 3]
print(dir(my_list))

#Output:
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
'__delslice__', '__doc__', '__eq__', '__format__', '__ge__', 
'__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', 
'__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', 
'__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop',
'remove', 'reverse', 'sort']

上面自省给我们一个列表所有方法和名字。当你没法回忆起一个方法的名字,这回非常有帮助。如果我们运行dir()而不传入参数,那么它会返回当前作用域的名字。

print(type(''))
#Output: <type 'str'>

print(type([]))
#Output: <type 'list'>

print(type({}))
#Output: <type 'dict'>

print(dict)
#Output: <type 'dict'>

print(type(3))
#Output: <type 'int'>

name = "Yasoob"
print(id(name))
#Output: 4439591360
import inspect
print(inspect.getmembers(str))

推导式 Comprehension

推导式(又称解析式)是Python一种独有特性,可以从一个数据序列构建另一个新的数据序列的结构体。共3种推导:

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

# 规范:variable = [out_exp for out_exp in input_list if out_exp == 2]

multiples = [i for i in range(30) if (i%3) is 0]
print(multiples)
#Output: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]

squared = [x**2 for x in range(10)]
print(squared)
#Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

这对快速生成列表非常有用。有些人甚至更喜欢使用它而不是filter函数。

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

mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}

mcase_frequency = {
        k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0)
        for k in mcase.keys()
}

print(mcase_frequency)
#Output: {'a': 17, 'z': 3, 'b': 34}

# 还可以快速对换一个字典的键和值
# {v: k for k, v in some_dict.items()}
# -*- coding: UTF-8 -*-
#!usr/bin/env python3

squared = {x**2 for x in [1, 1, 2]}
print(squared)
#Output:set([1, 4])

异常

异常处理是一种艺术,最基本的术语就是我们知道的try/except从句。可能会触发异常产生的代码放到try中,而处理异常的代码会放在except语句块里实现:

try:
    file = open('test.txt', 'rb')
except IOError as e:
    print('An IOError Occurred. {}'.format(e.args[-1]))

#Output: An IOError Occurred. No such file or directory

我们可以使用3种方法来处理多个异常:

try:
    file = open('test.txt', 'rb')
except (IOError, EOFError) as e:
    print("An error occurred. {}".format(e.args[-1]))
try:
    file = open('test.txt', 'rb')
except EOFError as e:
    print("An EOF error occurred.")
    raise e
except IOError as e:
    print("An error occurred.")
    raise e

如果异常没有被第一个except语句块处理,那么会被下一个语句块处理,或者根本不会被处理。

try:
    file = open('test.txt', 'rb')
except Exception:
    print("An error occurred.")
    raise

我们把主程序代码包裹在try从句中,然后我们把一些代码包裹在except从句中,他会在try中的代码触发异常时会执行。包裹在finally从句中的代码不管异常是否触发都会被执行。可以被用来在脚本执行之后做清理工作:

try:
    file = open('test.txt', 'rb')
except IOError as e:
    print('An IOError occurred. {}'.format(e.args[-1]))
finally:
    print("This would be printed whether or not an exception occurred!")

# Ooutput: An IOError occurred. No such file or directory
#       This would be printed whether or not an exception occurred!

我们常想在没有触发异常时执行一些代码。可以通过一个else从句来达到。不直接把代码放在try为了避免任意异常都会被try捕获。

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

try:
    print('I am sure no exception is going to occur!')
except Exception:
    print('exception')
else:
    # 这里的代码会在`try`语句没有触发异常时运行
    # 但这里的异常不会被捕获
    print('This would only run if no exceptino occurs. And an error here '
            'would NOT be caught.')
finally:
    print('This would be printed in every case.')

#Output: I am sure no exception is going to occur!
#       This would only run if no exceptino occurs. And an error here would NOT be caught.
#       This would be printed in every case.

lambda表达式

lambda表达式是一行函数。在其他语言中也被称为匿名函数。如果不想在程序中对一个函数使用两次,可以使用lambda表达式,和普通函数完全一样。

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

# 原型: lambda 参数:操作(参数)

add = lambda x, y: x + y

print(add(3, 5))
# Output: 8


# 列表排序
a = [(1, 2), (4, 1), (9, 10), (13, -3)]
a.sort(key=lambda x: x[1])
print(a)
# Output: [(13, -3), (4, 1), (1, 2), (9, 10)]

# 列表并行排序
list1 = [1, 7, 5]
list2 = [2, 8, 3]
data = zip(list1, list2)
data = sorted(data)
list1, list2 = map(lambda t: list(t), zip(*data))

print(list1, list2)
#Output: ([1, 5, 7], [2, 3, 8])

一行式

 python -m SimpleHTTPServer
# -*- coding: UTF-8 -*-
#!usr/bin/env python3

from pprint import pprint
import json

my_dict = {'name': 'Yasoob', 'age': 'undefined', 'personality': 'awesome'}
pprint(my_dict)

with open('file.json', 'w') as openedfile:
    openedfile.write(json.dumps(my_dict))
    openedfile.close()

以下命令可以快速的从文件打印出json数据:

cat file.json | python -m json.tool

脚本性能分析可能定位脚本中的性能瓶颈:

python -m cProfile 463.py

For - Else

循环是任何语言都有的。for循环就是Python的一个重要组成部分。

fruits = ['apple', 'banana', 'mango']

for fruit in fruits:
    print(fruit.capitalize())

# Output: Apple
#         Banana
#         Mango

else 从句

for循环还有一个else从句, 这个else从句会在循环正常结束时执行。这意味着,循环没有遇到任何break. 一旦掌握何时何地使用它,会非常有用。有个常见的例子是: 跑一个循环,并查找一个元素。如果这个元素找到可以使用break来中断这个循环。有两个场景会让循环停下来:

findKey = 8
some_list = [1, 2, 3, 4, 5, 6]
for val in some_list:
    if val == findKey:
        print("Found it")
        break
else:
    print("Didn't find anything")

#Output: Didn't find anything

以下例子可以找出210之间数字的因子。我们附加上else语句块,可以判断出质数:

for n in range(2, 10):
    for x in range(2, n):
        if n%x == 0:
            print(n, 'equals', x, '*', n/x)
            break
    else:
        print(n, 'is a prime number')

#Output:
    '''
    (2, 'is a prime number')
    (3, 'is a prime number')
    (4, 'equals', 2, '*', 2)
    (5, 'is a prime number')
    (6, 'equals', 2, '*', 3)
    (7, 'is a prime number')
    (8, 'equals', 2, '*', 4)
    (9, 'equals', 3, '*', 3)
    '''

使用C扩展

CPtyon 还为开发者实现一个有趣的特性,使用Python可以轻松调用C代码。开发者有三种方法可以在自己的Python代码中调用C编写的函数 ctypes, SWIG, Python/C API。每种方式都各自有利弊。我们在Python中调用C的常见原因:

CTypes

Python中的https://docs.python.org/2/library/ctypes.html模块可能是Python调用C方法中最简单的一种。ctypes模块提供了和C语言兼容的数据类型和函数来加载dll文件,因此在调用时不需对源文件做任何修改。例如: 实现两数求和的C代码,保存为add.c:

#include <stdio.h>

int add_int(int, int);
float add_float(float, float);

int add_int(int num1, int num2) {
    return num1 + num2;
}

float add_float(float num1, float num2) {
    return num1 + num2;
}

C文件编译为.so文件: $ gcc -shared -Wl,-install_name,adder.so -o adder.so -fPIC add.c

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

from ctypes import *

adder = CDLL('./adder.so')

res_int = adder.add_int(4, 5)
print("Sum of 4 and 5 = " + str(res_int))
#Output: Sum of 4 and 5 = 9

a = c_float(5.5)
b = c_float(4.1)

add_float = adder.add_float
add_float.restype = c_float
print("Sum of 5.5 and 4.1 = ", str(add_float(a, b)))
#Output: ('Sum of 5.5 and 4.1 = ', '9.60000038147')

这个例子中,C文件是自解释的,它包含两个函数,分别实现了整型求和和浮点型求和。在Python文件中,一开始先导入ctypes模块,然后使用CDLL函数来加载我们创建的库文件。这样我们就可以通过变量adder来使用C类库中的函数。当adder.add_int()被调用时,内部将发起一个对C函数add_int的调用。ctypes接口允许我们在调用C函数时使用原生Python中默认的字符串和整型。

而对于其他类似布尔值和浮点型,必须使用正确的ctype类型才可以。如像adder.add_float()函数传参时,我们要先将Python中的十进制值转化为c_float类型,然后才能传送给C函数。这种方法虽然简单,清晰,但却很受限。例如,不能操作C中对象。

SWIG

SWIGSimplified Wrapper and Interface Generator的缩写。是Python中调用C代码的另一种方法。这个方法中,开发人员必须编写一个额外的接口文件来作为SWIG(终端工具)的入口。Python开发者一般不会采用这种方法,因为大多数情况它会带来不必要的复杂。而当你有一个C/C++代码库需要被多种语言调用时,这会是个不错的选择。

Python/C API

Python/C API可能是被最广泛使用的方法。它不仅简单,而且可以在C代码中操作你的Python对象。这种方法需要以特定的方式来编写C代码以供Python去调用它。所有的Python对象都被表示为一种叫做PyObject的结构体,并且Python.h头文件中提供了各种操作它的函数。例如, 如果PyObject表示为PyListType(列表类型)时,那么我们便可以使用PyList_Size()函数来获取该结构的长度,类似Pythonlen(list)函数。大部分对Python原生对象的基础函数和操作在Python.h头文件中都能找到。

示例:编写一个C扩展,添加所有元素到一个Python列表(所有元素都是数字)

//Python.h has all the required function definitions to manipulate the Python objects
#include <Python.h>

//This is the function that is called from your python code
static PyObject* addList_add(PyObject* self, PyObject* args) {
    PyObject* listObj;

    //The input arguments come as a tuple, we parse the args to 
    // get the varipous variables In this case it's only one list
    // variable, which will now be referenced by listObj
    if (!PyArg_ParseTuple(args, "O", &listObj))
        return NULL;

    //length of the list
    long length = PyList_Size(listObj);

    //iterate over all the elements
    int i, sum = 0;
    for (i = 0;i < length; i++) {
        // get an element out of the list - the element is also a python objects
        PyObject* temp = PyList_GetItem(listObj, i);
        // we know that object represents an integer - so convert it into C long
        long elem = PyInt_AsLong(temp);
        sum += elem;
    }

    //value returned back to python code - another python object
    //build value here converts the C long to a python integer

    return Py_BuildValue("i", sum);
}

// This is the docstring that corresponds to our 'add' function
static char addList_docs[] = "add(): add all elements of the list\n";

/* This table contains the relavent info mapping -
 * <function>-name in python module>, <actual-function>
 * <type-of-args the function expects>, <docstring associated with the function>
 */
static PyMethodDef addList_funcs[] = {
    {"add", (PyCFunction)addList_add, METH_VARARGS, addlist_docs},
    {NULL, NULL, 0, NULL}
};

/* addList is the module name, and this is the initialization block of the module
 * <desired module name>, <the-info-table>, <module's-docstring>
 */
PyMODINIT_FUNC initaddList(void) {
    Py_InitModule3("addList", addLIst_funcs, "Add all ze lists");
}

函数addList_add接受的参数类型为PyObject类型结构(同时也表示为元组类型,因为Python中万物皆对象,所以我们先用PyObject来定义)。传入的参数则通过PyArg_ParseTuple()来解析。第一个参数是被解析的参数变量。第二个参数是一个字符串,告诉我们如何去解析元组中每个元素。字符串的第n个字母正是代表元组中第n个参数类型。例如: “i”代表整型, “s”代表字符串,“O”则代表一个Python对象。接下来的参数都是你想要通过PyArg_ParseTuple()函数解析并保存的元素。这样参数的数量和模块中函数期待得到的参数数量就可以保持一致,并保证了位置的完整性。例如,我们想传入一个字符串,一个整数和一个Python列表,可以:

int n;
char *s;
PyObject* list;
PyArg_ParseTuple(args, "siO", &n, &s, &list);

这种情况下,我们只需要提取一个列表对象,并将它存储在listObj变量中。然后用列表对象中的PyList_Size()函数来获取它的长度。就像Python中调用len(list)。现在我们通过循环列表,使用PyList_GetItem(list, index)函数来获取每个元素。这将返回一个PyObject*对象。既然Python对象也能表示PyInType,我们只要使用PyInt_AsLong(PyObj *)函数便可获得我们所需要的值。我们对每个元素都这样处理,最后再得到他们的总和。总和将被转化为一个Python对象并通过Py_BuildValue()返回给Python代码,这里的i表示我们要返回一个Python整型对象。现在我们已经编写完C模块。将下列代码保存为setup.py, 将我们的C文件编译安装到我们的Python模块中。

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

#build the modules

from distutils.core import setup, Extension

setup(name='addList', version='1.0', \
        ext_modules=[Extension('addList', ['adder.c'])])

#Run: python setup.py install

Python调用C代码的另一种方式便是使用CythonPython编译的更快。但是Cython和传统的Python比起来可以将它理解为另一种语言。

open 函数

open函数可以打开一个文件:

f = open('phone.jpg', 'r+')
jgpdata = f.read()
f.close()

有三个错误存在于上面的代码中。open的返回值是一个文件句柄,但前提是只有在read成功的情况下。如果有任何异常正好在 f = open(...) 之后产生,f.close()将不会被调用(取决于Python解释器的做法,文件句柄可能还会被归还)。为了确保不管异常是否触发,文件都能关闭,我们将其包裹成一个with语句:

with open('photo.jpg', 'r+') as f:
    jpgdata = f.read()

open的第一个参数是文件名。第二个(mode代开模式)决定了这个文件如何被打开。

虽然有若干个其他有效的mode字符串,但有可能你将永远不会使用他们。mode很重要,不仅因为它改变了行为,而且可能导致权限错误。比如:我们要是在一个写保护的目录里打开一个jpg文件,open(.., 'r+')就失败了。 mode可能包含一个扩展字符;让我们还可以以二进制的方式打开文件(你将得到字符串)或文本模式(字符串)。

一般来说,如果文件格式是由人写的,那它更可能是文本模式。jpg图像文件一般不是人写的(而且不是人直接可读的), 因此应该以二进制模式打开它,方式是在mode字符串后加一个b。如果你以文本模式打开一些东西(比如,加一个t, 或者用 r/r+/w/a),你还必须知道要使用那种编码。对于计算机来说,所有文件都是字节,而不是字符。

可惜,在Python2.x版本里,open不支持显示地指定编码。然而,io.open函数在Python2.x中和Python3.x中都提供,io.open能做正确的事。你可以传入encoding这个关键字参数来传入编码。如果不传入任意编码,一个系统-以及Python-指定的默认选项将被选中。你也许被诱惑去依赖这个默认选项,但这个默认选项经常是错误的,或者默认编码实际上不能表达文件里的所有字符(这经常发生在Python2.xWindows)。所以去挑选一个编码吧, utf-8是一个非常好的编码。

不幸的是,并没有一个十分简单的方式来检测正在读的文件的编码。在不同的编码中,同样的字节可以表示不同,但同样有效的字符。因此,必须依赖一个元数据(比如,在HTTP头信息里)来找出编码。越来越多的文件格式将编码定义为UTF-8。 重写上面open:

import io

with open('photo.JPG', 'rb') as inf:
    jpgdata = inf.read()

if jpgdata.startswith(b'\xff\xd8'): # JPG 文件头部以字节 FFD8 开始
    text = u'This is a JPEG file (%d bytes logn)\n'
else:
    text = u'This is a random file (%d bytes long)\n'

with io.open('summary.txt', 'w', encoding='utf-8') as outf:
    outf.write(text % len(jpgdata))

目标Python2+3

很多时候可能希望开发的程序能够同时兼容Python2+Python3+。有些技巧可以让脚本同时兼容Python2Python3

Future模块导入

上下文管理器是Python2.6+引入的新特性,如果你想在Python2.5中使用它可以这样做:

from __future__ import with_statement

Python3print已经变为一个函数。如果想在Python2中使用它可以通过__future__导入:

from __future__ import print_function
print(print)
#Output: <built-in function print>

模块重命名

我们在脚本中导入模块,大多时候会这样做:

import foo
# or
from foo import bar

# 其实也可以这样做
import foo as foo

这样做可以起到和上面代码同样的功能,但最重要的是它能让你的脚本同时兼容Python2Python3。现在看下如下代码:

try:
    import urllib.request as urllib_request # for Python3
except ImportError:
    import urllib2 as urllib_request # for Python2

print(urllib_request)
#Output: <module 'urllib.request' from '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/urllib/request.py'>
#       <module 'urllib2' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.pyc'>

我们将模块导入代码包装在 try/except 语句中。我们这样做是因为Python2中并没有urllib.request模块。这将引起一个ImportError异常。而在Python2urllib.request的功能则是由urllib2提供的。所以,当我们试图在Python2中导入urllib.request模块时,一旦我们捕获到ImportError将通过urllib2模块来代替它。

最后,要了解as关键字的作用。它将导入的模块映射到urllib.request, 所以我们通过urllib_request这个别名就可以使用urllib2中所有类和方法。

标准库向下兼容的外部支持

有一些包在非官方的支持下为Python2提供了Python3的功能。例如,我们有:

协程

Python中的协程和生成器很相似但又稍有不同。主要区别在于:

首先我们先来回顾下生成器的创建过程。我们可以这样去创建一个生成器:

def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a+b

for i in fib():
    print(i)

这样做不仅快而且不会给内存带来压力,因为我们所需要的值都是动态生成的而不是将他们存储在一个列表中。更概括的说如果现在我们在上面的例子中使用yield便可获得一个协程。协程会消费掉发送给它的值。Python实现grep就是个很好的例子:

def grep(pattern):
    print("Searching for", pattern)
    while True:
        line = (yield)
        if pattern in line:
            print(line)

search = grep('coroutine')
next(search)
#Output: ('Searching for', 'coroutine')

search.send("I love you")
search.send("Don't you love me?")
search.send("I love coroutine instead!")
#Output: I love coroutine instead!

grep中yield返回了什么?我们已经把它变成一个协程。它将不再包含任何初始值,相反要从外部传值给它。 我们可以通过send()方法向他传值。发送的值会被yield接受。我们为什么要运行next()方法?这样做是为了启动一个协程。就像协程中包含的生成器并不是立刻执行,而是通过next()方法来响应send()方法。因此,你必须通过next()方法来执行yield表达式,我们可以通过调用close()方法来关闭一个协程:

search = grep('coroutine')
search.close()
def countdown(n):
    print "Counting down from", n
    while (n > 0):
        yield n
        n -= 1

#for i in countdown(5):
#    print(i)

x = countdown(10)
print(x)
#Output: <generator object countdown at 0x10c959a00>

print(x.next())
#OUTPUT: Counting down from 10
#       10
print(x.next())
#OUTPUT: 8
print(x.next())
#OUTPUT: 8
print(x.next())
print(x.next())
print(x.next())
print(x.next())
print(x.next())
print(x.next())
print(x.next())
print(x.next())
#Traceback (most recent call last):
#  File "463.py", line 31, in <module>
#    print(x.next())
#StopIteration

更多协程相关参考 http://www.dabeaz.com/coroutines/Coroutines.pdf

函数缓存(Function caching)

函数缓存允许我们将一个函数对于给定参数的返回值缓存起来。当一个I/O密集的函数被频繁使用相同的参数调用时,函数缓存可以节约时间。在Python3.2版本以前我们只有写一个自定义的实现。在Python3.2以后版本,有个lru_cache的装饰器,允许我们将一个函数的返回值快速的缓存或取消缓存:

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

from functools import lru_cache

# maxsize 参数告诉 lru_cache, 最多缓存最近多少个返回值
@lru_cache(maxsize=32)
def fib(n):
    if n < 2:
        return n

    return fib(n-1) + fib(n-2)

print([fib(n) for n in range(10)])
# Output:[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

fib.cache_clear()
from functools import wraps

def memoize(function):
    memo = {}
    @wraps(function)
    def wrapper(*args):
        if args in memo:
            return memo[args]
        else:
            rv = function(*args)
            memo[args] = rv
            return rv
    return wrapper

def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(25))
#Output: 75025

上下文管理器(Context managers)

上下文管理器允许你在有需要时,精确的分配和释放资源。使用上下文管理器最广泛的例子就是with语句:

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

with open("summary.txt", "w") as opened_file:
    opened_file.write("Hola!")

# 上面这段代码打开一个文件,往里写入一些数据,然后关闭文件
# 如果在往文件写数据时发生异常,它也会尝试去关闭文件
# 等价于下面这段代码:

file = open("summary.txt", "w")
try:
    file.write("Hola!")
finally:
    file.close()

当与第一个例子对比时,我们可以看到,通过使用with, 许多样板代码(boilerplate code)被消掉了。这就是with语句的主要优势,它确保我们的文件会被关闭,而不用关注嵌套代码如何退出。 上下文管理器的一个常见用例,是资源加锁和解锁,以及关闭已打开的文件。让我们看看如何来实现我们自己的上下文管理器。这会让我们更完全明白在这些场景背后都发生着什么:

基于类的实现

一个上下文管理器的类,最起码要定义__enter____exit__方法:

class File(object):
    def __init__(self, file_name, method):
        print("== init ==")
        self.file_obj = open(file_name, method)

    def __enter__(self):
        print("== enter ==")
        return self.file_obj

    def __exit__(self, type, value , traceback):
        print("== exit ==")
        self.file_obj.close()

with File("summary.txt", "w") as opened_file:
    opened_file.write("Hola!")

#Output: == init ==
#       == enter ==
#       == exit ==

我们的__exit__函数接受三个参数。这些参数对于每个上下文管理器类中的__exit__方法都是必须的。在底层到底发生了什么:

异常处理

我们还没有谈到__exit__方法的这三个参数:type, valuetraceback。 在第4步和第6步之间,如果发生异常,Python会将异常的type, valuetrackback传递给__exit__方法。它让__exit__方法来决定如何关闭文件以及是否需要其他步骤。在我们的案例中,我们并没有注意他们。

那如果我们的文件对象抛出一个异常呢?万一我们尝试访问文件对象的一个不支持的方法;

class File(object):
    def __init__(self, file_name, method):
        print("== init ==")
        self.file_obj = open(file_name, method)

    def __enter__(self):
        print("== enter ==")
        return self.file_obj

    def __exit__(self, type, value , traceback):
        print("== exit ==")
        self.file_obj.close()

with File("summary.txt", "w") as opened_file:
    opened_file.undefined_function('Hola!')

我们来列一下,当异常发生时,with语句会采取那些步骤:

在我们的例子中, __exit__方法返回的是None(如果没有return语句那么方法会返回None)。因此,with语句抛出异常。我们尝试在__exit__方法中处理异常:

class File(object):
    def __init__(self, file_name, method):
        self.file_obj = open(file_name, method)

    def __enter__(self):
        return self.file_obj

    def __exit__(self, type, value , traceback):
        print("Exception has been handled")
        self.file_obj.close()
        return True

with File("summary.txt", "w") as opened_file:
    opened_file.undefined_function('Hola!')

我们的__exit__方法返回了True, 因此没有异常会被with语句抛出。

基于生成器的实现

我们还可以用装饰器(decorators)和生成器(generators)来实现上下文管理器。Python有个contextlib模块专门用于这个目的。我们可以使用一个生成器函数来实现一个上下文管理器,而不是使用一个类。看下例子:

from contextlib import contextmanager

@contextmanager
def open_file(name):
    f = open(name,  'w')
    yield f
    f.close()

with open_file('summary.txt') as f:
    f.write("Test")

这个实现方式看起来更加直观和简单。然而,这个方法需要关于生成器,yield和装饰器的一些知识。在这个例子中我们还没有捕捉可能产生的任何异常。它的工作方式和之前的方法大致相同:

ShunFa Zhang 02 July 2018
blog comments powered by Disqus