FEEDS
RSS
Atom

这是一个简单的基于 ZODB3 的数据存储,定义了一个用户User类,一个应用 App 类,App类用来操作User数据,连接默认使用的 ZEO。你需要至少安装Python 2.3 及以上版本并且安装了 ZODB 3

#!/usr/bin/env python
# encoding: utf-8
"""
user.py
Created by pantao on 2012-04-17.
Copyright (c) 2012 aitine.com. All rights reserved.
"""
import sys, os
import transaction
from persistent import Persistent
from ZEO import ClientStorage
import ZODB
from ZODB.POSException import ConflictError
from BTrees import OOBTree

class User(Persistent):
    """Class for a user
    """
    def __init__(self, login, email, password):
        self.login = login.lower()
        self.email = email.lower()
        self.password = password

class App():
    """"""
    def __init__(self, conn):
        self.conn = conn
        self.root = self.conn.root()

    def get_users(self):
        if not self.root.has_key('users'):
            return None
        return self.root['users'].values()

    def add_user(self, user):
        if not self.root.has_key('users'):
            print('Create new ')
            self.root['users'] = OOBTree.OOBTree()
            transaction.commit()
        users = self.root['users']
        if not users.has_key(user.login):
            users[user.login] = user
            transaction.commit()
        else:
            return None
        return users[user.login]

def main():
    app = App(ZODB.DB(ClientStorage.ClientStorage('/tmp/zeosocket')).open())
    while 1:
        us = app.get_users()
        if us:
            for u in us:
                print('<User {} : {} >'.format(u.login, u.email))
        l,e,p = raw_input('Enter user info:').split(' ')
        u = User(l,e,p)
        app.add_user(u)

if __name__ == '__main__':
    main()

只接上函数了:

#!/usr/bin/env python
# encoding: utf-8
"""
File: utility.py
Created by Pan Tao on 2011-11-20.
Copyright (c) 2011 CosTony.Com. All rights reserved.
"""
import sys
import os
def load_module(name, path = None):
    '''Load Python modules dynamic via module name. Using this function, you can
    load module like this:
        mod = load_module('modulename')
    for this usage, you will get a module which named 'modulename' and rename it
    as mod. This function is very useful for configuration of INSTALLED_MODULES.
    You can create an INSTALLED_MODULES dictionary, and load installed modules 
    dynamic.
    :param name: the module name string
    :param path: where the module stored, set to None when the module path is in
        sys.path.
    :return:
        python module: if there is a module can be found in sys.path
        None: Nothing found    
    '''
    if sys.modules.has_key(name): #if there is a module named 'name value'
        del sys.modules[name] # delete the exist module
    if path: #if there is a customized path
        if os.path.isabs(path): #If this path is an absolute path
            sys.path.insert(0, path)
        else: #or is not an absolute path
            sys.path.insert(0, os.path.abspath(path)) #find this path's absolute path
    return __import__(name)
#!/usr/bin/env python
# encoding: utf-8
"""
File: prime_list.py
Created by Pan Tao on 2011-11-16.
Copyright (c) 2011 CosTony.Com. All rights reserved.
"""
prime_list = lambda n: set([i for i in range(2,n)]) ^ set([i for i in range(2,n) for x in range(2,int(i**0.5)+1) if i%x == 0])
def main():
	print(prime_list(int(raw_input('Enter the max number:'))))
if __name__ == '__main__':
	main()

不要看上面有那么多行字符哈,其实就 prime_list = lambda: .... 这一行就行了,如果你对质数感兴趣,也可以去看看我以前写的一个专门用来求质数的程序,见这里: Python 验证与获得素数(via Miller Rabin & AKS)

在了解验证与获得素数前,先来了解一下什么是素数:

质数,又称素数,指在一个大于1的自然数中,除了1和此整数自身外,无法被其他自然数整除的数(也可定义为只有1和本身两个因数的数)。比1大但不是素数的数称为合数。1和0既非素数也非合数。素数在数论中有着很重要的地位。最小的素数是2,也是素数中唯一的偶数(双数);其他素数都是奇数(单数)。质数有无限多个,所以不存在最大的质数。围绕著素数存在很多问题、猜想和定理。著名的有孪生素数猜想和哥德巴赫猜想。素数序列的开头是这样的:2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113

根据素数的定义想到的一些解决办法

从上面我们可以知道,任何一个大于 1 且除1和自身之外不能被其它整数整除的整数为素数,那么我们首先可能就会想到一个办法来验证一个整数是不是素数:给定一个整数 n ,让 n 依次除以 1 -> n 之间的所有整数,如果都没有能整除的,那么这个数肯定是素数:

def factor(n):
    '''Get number's factors'''
    end = n+1
    for i in xrange(1,end):
        if n % i == 0 : yield i        
def force_checker(n):
    '''Checker weither a number is a prime number'''
    if len([i for i in factor(n)]) == 2: return True
    return False

上面的代码我们作了什么?其实很简单,根据前面我们的分析,对于任何一个需要验证的数字,我们首先需要得到它的因子,这个工作是由 factor() 函数来完成的,遍历 1 -> n 之间的所有整数,一个一个测试,如果是这个数的因子,则将其返回( yield i )这一句,所以 factor() 是一个“生成器”:/code/python-functions-decorators ,有了这个之后,我再写了一个 force_checker() 函数,来暴力验证,办法还是最原始的,先找到整数 n 的所有因数子,然后检查其个数,如果为两个(也就是 1 与其自身),则这个数是素数。

上面的这两个函数确实是已经可以实现素数的验证了,但是对于一整数,还没有任何关系,但是如果整数过大,那就麻烦了,你的电脑将有很长的时间,CPU 处于 100%的状态,我们可以来作一个测试,来查看一下验证需要多少时间:

>>> from time import time
>>> def test(n):
...     print(time())
...     print force_checker(n)
...     print(time())
... 
>>> test(104729)
1317222986.09
True
1317222986.1
>>> test(1047290)
1317222997.53
False
1317222997.66
>>> test(10472900)
1317223000.42
False
1317223001.55

从上面我们可以看到,对于6位的这个数字,整个验证花了不到0.01 秒,我在 104729 后面追加了一个 0 之后,花了 0.13 秒,但是当我再追加一个 0 之后,就花了 1.13 秒了,经过我自己电脑上面的验证,对于大于 10 位的数字,这种办法根本就行不通,所以,还得想其它办法。

我再想到是,先对数字进行过滤,我想到的是下面这些数字其实根本就不需要去测的:

  1. 未位为(0,2,4,5,6,8)的数字都能被 2 整除,所以肯定不是素数
  2. 每一位相加得到的数字如果能被 3 整除,那么这个数也能被 3 整除
  3. 任何一个大于一个整数 1/2 的整数,绝对不是该整数的因子

跟着上面的思路,我想到了先得修改一下下 force_checker() 函数:

def force_checker(n):
    '''Checker weither a number is a prime number'''
    if n in (2,3,5): return True
    if str(n)[-1:] in ('0','2','4','5', '6','8') or eval('+'.join(str(n))) % 3 == 0: return False
    if len([i for i in factor(n)]) == 2: return True
    return False

来看看现在的效果:

>>> test(10472900)1317223764.19
False
1317223764.19
>>> test(104729001)
1317223822.58
False
1317223822.58

几乎是不花时间就能检测出我所提供的这两个数字是否为素数,但是你可能也看到了,我其实是取了一个特别的数字,一个是 0 结尾的,一个是各位数字之和为 3 的整数倍的,但是我们还是可以通过另一个办法来看看加了上面的一行判断之后的实际效果,我们来生成一个素数列,看看需要花多少时间:

我们先来写一个用来生成素数数列的函数: prime_generator() ,它是一个生成器,同时,为了以后的测试方法,我还写了一个检测运行时间的函数 test() ,现在我的所有函数是下面这样的:

import sys
def factor(n):
    '''Get number's factors'''
    end = n+1
    for i in xrange(1,end):
        if n % i == 0 : yield i
def force_checker_old(n):
    if len([i for i in factor(n)]) == 2: return True
    return False
def force_checker(n):
    if n in (2,3,5): return True
    if str(n)[-1:] in ('0','2','4','5', '6','8') or eval('+'.join(str(n))) % 3 == 0: return False
    if len([i for i in factor(n)]) == 2: return True
    return False
def prime_generator(limit = None, start = 1, end = None, checker = force_checker):
    seek, counter = start, 0
    while True:
        if limit is not None and counter >= limit: break
        if end is not None and seek >= end: break
        if checker(seek):
            yield seek
            seek += 1
            counter += 1
        else:
            seek += 1
def test(limit = None, start = 1, end = None, checker = force_checker):
    import sys
    from time import time
    print('Generate prime numbers with {} checker.'.format(checker.__name__))
    time_start = time()
    column, counter = 1, 1
    for prime in prime_generator(limit,start,end,checker):
        sys.stdout.write('{:>5} : {:<10}'.format(counter, prime))
        counter += 1
        if column % 5 == 0:
            sys.stdout.write('\n')
        column += 1
    time_end = time()
    print('\n\nI got {} results in {} seconds(form {} to {}).'.format(\
            counter-1,time_end - time_start, time_start, time_end))
def main():
    test(1000)
    test(1000,checker=force_checker_old)
if __name__ == '__main__':
    main()

上面代码中 prime_generator() 就是一个素数生成器,我们可以在 for 循环中由 limit 限制的数量个素数,如果 limit 设置为 None,则将不限制素数个数(在 for 循环中,不建议这么干), start 表示我们从哪一个数开始生成,默认为 1 ,表示需要生成 大于 1 的素数, end 默认为 None ,表示不限制最大素数(不是最大素数个数),由于我们需要测试不通的素数测试方法的性能,所以我这里使用了一个 checker 参数,默认使用 force_checker() ,但是我们可以指定不同的验证函数,只要该函数是同样的使用方法(传入一个数,返回 True 或者 False)。

test() 函数是我写的一个测试函数,我们将由它来调用 prime_generator() 函数,并且记录整个程序的运行时间与素数个数,我还将上面的所有代码都保存在一个名为 prime.py 的文件中,这样我可以直接使用我的 TextMate 编辑器运行它,或者你也可以使用 :

$python /path/to/prime.py

命令来运行,我们来测试一下吧,在我的机器上面,同时用两个验证器来验证素数,生成 1000 条记录的时间如下:

Generate prime numbers with force_checker checker.
    1 : 2             2 : 3             3 : 5             4 : 7             5 : 11        
    ...... 中间部分省略 ......
  996 : 7879        997 : 7883        998 : 7901        999 : 7907       1000 : 7919      
I got 1000 results in 1.05513501167 seconds(form 1317225770.58 to 1317225771.64).
Generate prime numbers with force_checker_old checker.
    1 : 2             2 : 3             3 : 5             4 : 7             5 : 11
    ...... 中间部分省略 ......
   996 : 7879        997 : 7883        998 : 7901        999 : 7907       1000 : 7919      
I got 1000 results in 3.41558718681 seconds(form 1317225771.64 to 1317225775.05).

上面的结果一目了然了吧,整整快了2.4秒(机器不同,运行时间与环境不同,可能时间有些许不一样,你的结果也可能和我的不一样,但是绝对是快,而且快很多),在我这里测试获取 2000 个素数的结果是:优化之后的函数使用 4.46425414085 秒,未优化的使用了 15.689232111 秒,获取个数不断增加,它们之间的时间差将成倍的增加。当然,上面优化过后的函数也不会是最好的,为什么我给这个函数取名叫作 force_checker() ?那是因为它是最笨的办法,暴力验证,就是不管你是什么数,我一个一个试,只到试完为止,这是一种没有任何技巧可言的方法。

Miller Rabin 验证法

上面的方法对于小整数而言,速度已经很快了,但是在我们现在的现实应用中,基本上不可能使用上在的这个办法,比如目前素数应用最广的应用领域公共密钥体系中,一般选择的素数都相当的大,至少都在100位以上,而上面的这个办法,对于10位的数字就已经力不从心了,而对于100位的数字,可能需要耗尽你一生的时间才能验证成功,所以,还得选择其它的方法,比如最有名的一个验证素数的办法:Rabin Miller,按我自己的想法,这个属性概率验证法吧,如果证明这个方法的有效性不是我所研究的对象,我也没这个能力,但是至少它是被所有人都认同的快速的验证法。

Miller Rabin 验证法的细节是这样的:

  1. 选择一个代测的随机数 p ,计算 bb 是 2 整除 p - 1 的次数,然后计算 m ,使用 n = 1 + (2**m)
  2. 选择一个小于 p 的随机数 a
  3. j = 0z = (a**m) mod p 。 # 如果 z = 1@ 或者 z = p - 1 ,那么 p 通过测试,可能是素数
  4. 如果 j > 0z = 1 ,那么 p 不是素数
  5. j = j + 1 如果 j < b z != p - 1 , 设 z = (z**m) mod p ,然后咽到上面步骤,如果 z = p - 1 ,那么 p 通过测试,可能为素数
  6. 如果 j = bz != p - 1 ,不是素数

a 被当成证据的概率为 75% ,这意味着我们在上面的测试过程中,迭代次数为 t 时,产生一个假素数所花费的时间不会超过 1/4**t ,当我们迭代 5 次时,产生素数的概率已经高达 99.999% 了, 这几乎可以说是一个素数了,而国际上通用的作用是,必须迭代50次,下面我们来在我们的 prime.py 中实现它:

def miller_rabin_checker(n):
    '''Checker wiether a number is a prime number via rabin miller method'''
    if n <= 1: return False
    if n in (2,3,5): return True
    if str(n)[-1:] in ('0','2','4','5', '6','8') or eval('+'.join(str(n))) % 3 == 0: return False
    import sys, random
    def to_binary(n):
        r = []
        while n > 0:
            r.append(n % 2)
            n = n / 2
        return r
    def test(a, n):
        '''test(a, n) -> bool Test whether n is complex
        Returns:
            - True : if n is complex
            - False: if n is probably prime
        '''
        b = to_binary(n-1)
        d = 1
        for i in xrange(len(b) - 1, -1, -1):
            x = d
            d = (d * d) % n
            if d == 1 and x != 1 and x != n - 1:
                return True
            if b[i] == 1:
                d = (d * a) % n
        if d != 1:
            return True
        return False
    def checker(n, s = 50):
        '''checker(n, s = 50) -> bool checks whether n is prime or not
        Returns:
            - True : if n is probably prime.
            - False: if n is complex.
        '''
        for j in xrange(1, s + 1):
            a = random.randint(1, n - 1)
            if test(a,n):
                return False
        return True
    return checker(n)

我们同样可以使用 test() 函数来验证上面的这个函数的效率:

Generate prime numbers with miller_rabin_checker checker.
    1 : 2             2 : 3             3 : 5             4 : 7             5 : 11 
    ...... 中间部分省略 ......
   96 : 503          97 : 509          98 : 521          99 : 523         100 : 541       
I got 100 results in 0.0682229995728 seconds(form 1317228097.28 to 1317228097.35).
Generate prime numbers with force_checker checker.
    1 : 2             2 : 3             3 : 5             4 : 7             5 : 11
    ...... 中间部分省略 ......
   96 : 503          97 : 509          98 : 521          99 : 523         100 : 541       
I got 100 results in 0.0126042366028 seconds(form 1317228097.35 to 1317228097.38).
Generate prime numbers with force_checker_old checker.
    1 : 2             2 : 3             3 : 5             4 : 7             5 : 11
    ...... 中间部分省略 ......
   96 : 503          97 : 509          98 : 521          99 : 523         100 : 541       
I got 100 results in 0.0203368663788 seconds(form 1317228097.38 to 1317228097.39).

上面的数据显示运行时间由短到长的验证方法分别为:

force_checker < force_checker_old < miller_rabin_checker

这是怎么回事儿?其实很容易解释,对于小整数,force_checker_old 所作的计算量更少而已,但是如果对于大整数,由于 force_checker_old 或者 force_checker 所用的方法都是试除法,就是所有小于 n 的数字都拿去除一次,所以,待测整数越大,其时间就越多,而且时间是成倍的增加,来看下面的验证(下面的测试我将不再验证 force_checker_old() 函数):

生成1000个素数

I got 1000 results in 0.895807027817 seconds(form 1317228892.57 to 1317228893.47).
I got 1000 results in 3.35119318962 seconds(form 1317228898.47 to 1317228901.82).

生成10000个素数

Generate prime numbers with miller_rabin_checker checker.
    1 : 2             2 : 3             3 : 5             4 : 7             5 : 11 
    ...... 中间部分省略 ...... 
    9996 : 104707     9997 : 104711     9998 : 104717     9999 : 104723    10000 : 104729    
I got 10000 results in 11.3178238869 seconds(form 1317228989.72 to 1317229001.03).
Generate prime numbers with force_checker_old checker.
    1 : 2             2 : 3             3 : 5             4 : 7             5 : 11 
    ...... 中间部分省略 ...... 
    9996 : 104707     9997 : 104711     9998 : 104717     9999 : 104723    10000 : 104729    
I got 10000 results in 571.780455112 seconds(form 1317229006.04 to 1317229577.82).

这个差距就有点儿太大了哈,不过,这就是一个事实。

完整的程序文件: prime.py

#!/usr/bin/env python
# encoding: utf-8
"""
File: prime.py
Created by Pan Tao on 2011-09-28.
Copyright (c) 2011 CosTony.Com. All rights reserved.
"""
import sys
def factor(n):
    '''Get number's factors'''
    end = n+1
    for i in xrange(1,end):
        if n % i == 0 : yield i
def force_checker_old(n):
    '''Checker weither a number is a prime number'''
    if len([i for i in factor(n)]) == 2: return True
    return False
def force_checker(n):
    '''Checker weither a number is a prime number'''
    if n <= 1: return False
    if n in (2,3,5): return True
    if str(n)[-1:] in ('0','2','4','5', '6','8') or eval('+'.join(str(n))) % 3 == 0: return False
    if len([i for i in factor(n)]) == 2: return True
    return False
def miller_rabin_checker(n):
    '''Checker wiether a number is a prime number via rabin miller method'''
    if n <= 1: return False
    if n in (2,3,5): return True
    if str(n)[-1:] in ('0','2','4','5', '6','8') or eval('+'.join(str(n))) % 3 == 0: return False
    import sys, random
    def to_binary(n):
        r = []
        while n > 0:
            r.append(n % 2)
            n = n / 2
        return r
    def test(a, n):
        '''test(a, n) -> bool Test whether n is complex
        Returns:
            - True : if n is complex
            - False: if n is probably prime
        '''
        b = to_binary(n-1)
        d = 1
        for i in xrange(len(b) - 1, -1, -1):
            x = d
            d = (d * d) % n
            if d == 1 and x != 1 and x != n - 1:
                return True
            if b[i] == 1:
                d = (d * a) % n
        if d != 1:
            return True
        return False
    def checker(n, s = 50):
        '''checker(n, s = 50) -> bool checks whether n is prime or not
        Returns:
            - True : if n is probably prime.
            - False: if n is complex.
        '''
        for j in xrange(1, s + 1):
            a = random.randint(1, n - 1)
            if test(a,n):
                return False
        return True
    return checker(n)
def prime_generator(limit = None, start = 2, end = None, checker = force_checker):
    '''Generator of prime numbers
        varibles:
            - limit : how many prime numbers
            - start : where to start
            - end : when to stop
            - checker : the prime number checker, default to force_checker
    '''
    seek, counter = start, 0
    while True:
        if limit is not None and counter >= limit: break
        if end is not None and seek >= end: break
        if checker(seek):
            yield seek
            seek += 1
            counter += 1
        else:
            seek += 1
def test(limit = None, start = 2, end = None, checker = force_checker):
    import sys
    from time import time
    print('Generate prime numbers with {} checker.\n'.format(checker.__name__))
    time_start = time()
    column, counter = 1, 1
    for prime in prime_generator(limit,start,end,checker):
        sys.stdout.write('{:>5} : {:<10}'.format(counter, prime))
        counter += 1
        if column % 5 == 0:
            sys.stdout.write('\n')
        column += 1
    time_end = time()
    print('\n\nI got {} results in {} seconds(form {} to {}).'.format(\
            counter-1,time_end - time_start, time_start, time_end))
def main():
    import sys
    checkers = {'fo': force_checker_old, 'f': force_checker, 'mr': miller_rabin_checker, 'e': ''}
    while True:
        checker_help = '''
Which checker you want to use?
    type 'fo' to choose force_checker_old checker
    type 'f' to choose force_checker checker
    type 'mr' to chose miller_rabin_checker
    type 'e' to exit
        '''
        checker = raw_input(checker_help)
        if checker not in checkers:
            print('You can only type "fo", "f" or "rm" to choose checker, type again :\n')
            continue
        if checker == 'e':
            exit()
        action_help = '''
What are you want to do?
    type 'g' to generate prime numbers
    type 'c' to check whether a number is a prime\n
        '''
        action = raw_input(action_help)
        if action == 'g':
            limit = raw_input('How many numbers do you want to generate?\n')
            start = raw_input('The min-number of the prime list is:\n')
            end = raw_input('The max number of the prime list is :\n')
            if int(end) <= int(start):
                print('The end must bigger than start')
                continue
            if int(limit) < 1:
                print('limit must bigger or equal to 1')
                continue
            test(int(limit), int(start), int(end), checkers[checker])
        elif action == 'c':
            if checkers[checker](int(raw_input('Enter the number you want to check:\n'))):
                print('This number is prime number')
            else:
                print('This number is not a prime number')
if __name__ == '__main__':
    main()

Python 编程获取欧尔调和数列

若一个正整数n的所有因子的调和平均是整数,n便称为调和数(Harmonic number)。它又称欧尔数(Ore number),因为它最先出现在一篇奥斯丁·欧尔在1948年发表的论文内。
首几个调和数是: 1,6,28,140,270,496,672,1638,2970,6200,8128,8190 (OEIS:A001599)
所有完全数都是调和数。暂时除了1之外,并没有发现奇调和数。1972年,W. H. Mills证明除了1之外,107内没有奇调和数。

Python这门编程语言里面有一个十分有趣的东西,名为:@decorator@ ,一直没有找到官方的翻译,我们就叫它为“修饰函数”吧,如下面的代码:

def decorator_func(func):
    do_something()
    ....
    return fun
@decorator_func
def func():
    print('realfunc')

在上面的代码中,decorator_func(func) 就是一个修饰函数,说得简单点儿,它可以被用来装饰其它函数,比如我们下面定义的 func() 这个函数就被其装饰过了,整个运行过程,其实可以归结为:

func = decorator_func(func)

在 func 运行之前,会首先将 func 传递给 decorator_func 修饰,之后再得出运行结果:

获取斐波那契数列(Fibonacci Sequence)中某一项的函数与修饰函数

斐波那契数列(Fibonacci Sequence) 我以前就写过一个函数,可以指定一个最大数,然后创建出该最大数以内的所有斐波那契数列的项,今天我这里的这个函数不返回所有项,而是指定项编号返回该位置的斐波那契数列数:

>>> def fib(n):
...     if n in (0,1):
...         return n
...     else:
...         return fib(n-1) + fib(n - 2)
... 
>>> fib(1)
1
>>> fib(2)
1
>>> fib(3)
2
>>> fib(6)
8
>>> fib(10)
55

上面的这个看起来似乎运行得十分不错,但是我们再试试,当n = 30 的时候,就需要运行一近 1 秒钟了,到 n = 33 的时候就快 2 秒了,之后 n 每增加 1 ,运行时间将增加数倍,这个时候,这个函数其实已经没有任何用处了,因为我们等不了这么久的时间,尤其是当我们在公共服务器上运行的时候,如下代码我定义了一个 timer() 函数来计算执行时间:

>>> def timer(x):
...     print(time.time())
...     print('Result is : {}'.format(fib(x)))
...     print(time.time())
... 
>>> timer(10)
1317109795.620802
Result is : 55
1317109795.620916
>>> timer(30)
1317109800.268512
Result is : 832040
1317109800.920388
>>> timer(33)
1317109806.092292
Result is : 3524578
1317109808.82651

从上面的函数我们可以看出:每一次运行 fib 函数,该函数都会被执行无数次,比如:当我们求 fib(4) 时,会运行 fib(3) 与 fib(2),接着再深入一层就需要运行( fib(2) 与 fib(1) ) 以及 (fib(1) 与 fib(0)) ,接着还需要再运行一次 (fib(1) 与 fib(0))最终才得到结果,求 fib(4) 就需要运行该函数 9 次,实在是资源浪费,其实我们不难发现很多时候,都是在重复的运行,比如 fib(3) 需要 运行 fib(2) 与 fib(1) ,而其实 fib(2) 已经运行过了,那我们可以想一个办法,将运行过的数据缓存起来,这样,当我们再一次调用的时候,就不需要运行了,而是直接返回结果,这个时候,我们就可以使用修饰函数了:

>>> def memoize(f):
...     cache = {}
...     def helper(x):
...         if x not in cache:            
...             cache[x] = f(x)
...         return cache[x]
...     return helper
... 
>>> fib = memoize(fib)
>>> timer(30)
1317109914.749524
Result is : 832040
1317109914.749719

上面的代码中,我定义了一个修饰函数,该函数定义了一个 cache 成员,并且还在该函数中定义了一个 helper 函数,从上面的代码中,不难看出,当我们运行 fib 时,首先会运行 helper 函数,它将会检查当前的 fib(n) 的值是否已经保存在缓存中,如果没有,则计算 fib(n) ,并将其值保存到缓存中,如果已经在缓存中了,则直接返回结果,最后 memoize() 函数返回最终的结果,这样一来,我们可以看到后面计算结果,当 n = 30 时,仅仅花了不到 0.0003 秒的时间,而在上面没有使用修饰函数的时候,却花0.6秒以上,速度提高了多少倍?

我们以 n = 4 为例来详细的看一下程序的运行流程:

  1. fib(4) -> fib(3) + fib(2)
  2. 最开始是这样的结果,因为 fib(3) 与 fib(2) 都还没有运行过,所以这两个是必须运行的,
  3. fib(2) -> fib(1) + fib(0) = 1 + 0 = 1,这个时候,fib(2), fib(1), fib(0) 都已经被缓存了
  4. fib(3) = fib(2) + fib(1) ,这个时候 fib(2) 与 fib(1) 将直接返回缓存的结果,而不再重新拆分为 fib(1) + fib(0) + fib(1)

在上面的示例中, memoize() 就是一个修饰函数,其实我们大可不必要使用 func = decorater_func(func) 这样的方法,Python 为我们提供了一个更简单优雅的办法,那就是 @ 修饰符:

def memoize(f):
    ...
@memoize
def fib(n)
    ....

我们在定义了一个 memoize() 函数,然后在定义 fib 函数之前,先使用了 @memoize 修饰,这样一来,当我们调用 fib() 函数时,Python会自动的执行 fib = memoize(fib)

跟踪程序运行的修饰函数

下面我们来写一个跟踪函数,用来跟踪整个 fib 函数的运行,以证明我上面对于程序运行流程的推论:

def memoize(f):
    ....
def trace(f):
    def helper(x):
        call_str = "{0}({1})".format(f.__name__, x)
        print("Calling {0} ...".format(call_str))
        result = f(x)
        print("... returning from {0} = {1}".format(
              call_str, result))
        return result
    return helper

上面定义的都是两个修饰函数, memoize 与最前面的代码都是一样的,不需要做任何修改,而 trace 将显示出当前在干嘛,然后我使用这两个修饰函数同时修饰 fib 函数:

@memoize
@trace
def fib(n):
   ...

下面来看一下下运行结果:

>>> fib(4)
Calling fib(4) ...
Calling fib(3) ...
Calling fib(2) ...
Calling fib(1) ...
... returning from fib(1) = 1
Calling fib(0) ...
... returning from fib(0) = 0
... returning from fib(2) = 1
... returning from fib(3) = 2
... returning from fib(4) = 3
3
>>> fib(5)
Calling fib(5) ...
... returning from fib(5) = 5
5
>>> fib(10)
Calling fib(10) ...
Calling fib(9) ...
Calling fib(8) ...
Calling fib(7) ...
Calling fib(6) ...
... returning from fib(6) = 8
... returning from fib(7) = 13
... returning from fib(8) = 21
... returning from fib(9) = 34
... returning from fib(10) = 55
55

当我们调用 fib(4) 时,因为程序还没有运行过,所以没有任何缓存,所以每一个 n 值都会被计算一次,接着,我们再调用 fib(5),因为 fib(4) 已经计算过了,所以 fib(4) 就不会被计算了,而 fib(5) 是第一次执行,所以还是会被执行,之后我再执行 fib(10),它仅仅只运行 fib(6) – fib(10) 即可。我们现在来测试一下 fib(100)需要花多少时间:

>>> timer(100)
1317111016.522655
Result is : 354224848179261915075
1317111016.522884

这是我第一次在没有任何缓存的情况下运行的结果,需要的时间不超过 0.0003 秒,当然,我不建议你去测试没有 memoize 修饰时的 fib(100) ,因为我等了好长的时间,还没有得到结果。

memoize(f) 函数 与 trace(f) 函数的其它用处

在上面我定义的 memoize 函数其实并不仅仅只能用来修饰 fib() 函数,在某些相似的情况下,还可以用来修饰其它的函数,比如求 f(n) = 1^1 + 2^2 + 3^3 + … + n^n 这个数列的第 n 项的值,其函数可以是下面这样的:

>>> @memoize
... @trace
... def f(n):
...     if n == 1:
...             return n
...     else:
...             return n**n + f(n-1)

>>> f(10)
Calling f(10) …
Calling f(9) …
Calling f(8) …
Calling f(7) …
Calling f(6) …
Calling f(5) …
Calling f(4) …
Calling f(3) …
Calling f(2) …
Calling f(1) …
… returning from f(1) = 1
… returning from f(2) = 5
… returning from f(3) = 32
… returning from f(4) = 288
… returning from f(5) = 3413
… returning from f(6) = 50069
… returning from f(7) = 873612
… returning from f(8) = 17650828
… returning from f(9) = 405071317
… returning from f(10) = 10405071317
10405071317
>>> f(10)
10405071317

什么是字母算术?或者可以被称为 Cryptarthms 或者 Alphametics,如下面这样的就是:

HAWAII + IDAHO + IOWA + OHIO == STATES

510199 + 98153 + 9301 + 3593 == 621246

其字母与数字的对应关系为:

H = 5
A = 1
W = 0
I = 9
D = 8
O = 3
S = 6
T = 2
E = 4

solve() 函数

import re
import itertools
def solve(puzzle):
    words = re.findall('[A-Z]+', puzzle.upper())
    unique_characters = set(''.join(words))
    assert len(unique_characters) <= 10, 'Too many letters'
    first_letters = {word[0] for word in words}
    n = len(first_letters)
    sorted_characters = ''.join(first_letters) + \
        ''.join(unique_characters - first_letters)
    characters = tuple(ord(c) for c in sorted_characters)
    digits = tuple(ord(c) for c in '0123456789')
    zero = digits[0]
    for guess in itertools.permutations(digits, len(characters)):
        if zero not in guess[:n]:
            equation = puzzle.translate(dict(zip(characters, guess)))
            if eval(equation):
                return equation
if __name__ == '__main__':
    import sys
    for puzzle in sys.argv[1:]:
        print(puzzle)
        solution = solve(puzzle)
        if solution:
            print(solution)

solve() 函数详解

words = re.findall('[A-Z]+', puzzle.upper())
unique_characters = set(''.join(words))

获取到所有字母,再将其保存到一个 SET 中,这个是该程序要做的第一件事情,在这里我们就使用 re 模块的 findall() 函数,它接受一个正则表达式和一个字符串作为参数,并且取出所有该字符串中出现该模式的地方并保存到一个列表中。

对于取出之后的列表,我们还不能接着下一步工作,因为我们需要知道每一个字母对该的数字是什么,而不是一个字符串对应什么数字,所以我们需要将所有字符串中出现的字母取出来,这个时候可以用到 set ,它可以去除重复的值,还使用了字符串的 join() 函数,它将多个字符串使用一个空字符连接到一起,由于在Python中,字符串就是一个字符的序列,所以它可以直接被用来作为 set() 的参数,我们最后得到的将是一个以字母为元素的 Set 实例。

>>> words = re.findall('[A-Z]+', 'SEND + MOre = Money'.upper())
>>> words
['SEND', 'MORE', 'MONEY']
>>> set(words)
{'MONEY', 'SEND', 'MORE'}
>>> unique_charactors = set(''.join(words))
>>> unique_charactors
{'E', 'D', 'M', 'O', 'N', 'S', 'R', 'Y'}

接下来我们需要做的是判断字母数量是否大于 10 个,这个原因是:每一个不同的字母都必须对应一个不同的数字,然后总共只有 0 – 9 这 10 个数字,所以,如果字母数大于 10 个的话,那肯定会有多余的字母没有数字与之对应,也就不可能形成字母算术了。

这里使用了一个 assert 语句,该语句后面可以跟任何全法的 Python 表达式,如果表达式的结果为 True,那么该 assert 语句就与普通的表达式一样,但是如果运行结果为 False 的话,该语句将会抛出 AssertionError 异常,assert 语句后面还可以再添加一个异常说明的字符串,当抛出异常时,该字符串将一并返回:

>>> assert len(unique_charactors) <= 10
>>> assert len(unique_charactors) >= 10, 'Aha, AssertionError has been catched'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: Aha, AssertionError has been catched

上面产生错误的代码其实与下面这个代码是待价的:

if len(unique_charactors) < 10:
    raise AssertionError('Aha, AssertionError has been catched')

再接下来,就是很有趣的东西了,这是一个排列组合的问题,先获得总计有多少个字母,然后将就可以知道,对多有多少种可能的结果了,因为每一个字母对应一个数字,所以,我们可以把问题科化为0-9十个数字有多少种 N 个值的排列方法,这个N 就是字母的个数,在这里我们使用了一个十分有趣的函数: itertools.permutations()

itertools.permutaions() 函数

先来看一段代码:

>>> for i in itertools.permutations('ABC',2): print(i)
... 
('A', 'B')
('A', 'C')
('B', 'A')
('B', 'C')
('C', 'A')
('C', 'B')
>>> for i in itertools.permutations('ABC',3): print(i)
... 
('A', 'B', 'C')
('A', 'C', 'B')
('B', 'A', 'C')
('B', 'C', 'A')
('C', 'A', 'B')
('C', 'B', 'A')
>>> for i in itertools.permutations('ABC',4): print(i)
... 

从上面的代码中可以看到,该函数接受一个序列(可以是任何迭代器或者列表等数据)以及一个排序的元素数目的参数,比如上例中第一段代码,我们就是将’A’,‘B’,‘C’三个字母进行排列,排列仅仅只有两个元素,而且每一个元素不能相同,permutations 会将鳘次排列返回,这个要让我们自己去写排列的话,还真的不简单,不过 Python 已经帮我们把这些都得很好了。

在本文的代码中,将每一个排列进行测试,如果得到正确的结果,刚停止运行并返回结果。

把所有东西放在一起

总的来说: 这个程序通过暴力解决字母算术谜题, 也就是通过穷举所有可能的解法。为了达到目的,它

  1. 通过re.findall()函数找到谜题中的所有字母
  2. 使用集合和set()函数找到谜题出现的所有不同的字母
  3. 通过assert语句检查是否有超过10个的不同的字母 (意味着谜题无解)
  4. 通过一个生成器对象将字符转换成对应的ASCII码值
  5. 使用itertools.permutations()函数计算所有可能的解法
  6. 使用translate()字符串方法将所有可能的解转换成Python表达式
  7. 使用eval()函数通过求值Python 表达式来检验解法
  8. 返回第一个求值结果为True的解法

Tkinter 练习 :简单的计算器

使用 Python 和 Tkinter 实现的一个简单的计算器:

代码

#!/usr/bin/env python
# encoding: utf-8
"""
File: simple_calculator.py
Created by Pan Tao on 2011-09-24.
Copyright (c) 2011 CosTony.Com. All rights reserved.
"""
from Tkinter import *
def frame(root, side):
    w = Frame(root)
    w.pack(side = side, expand = YES, fill = BOTH)
    return w
def button(root, side, text, command = None):
    w = Button(root, text = text, command = command)
    w.pack(side = side, expand = YES, fill = BOTH)
    return w
class Calculator(Frame):
    def __init__(self):
        Frame.__init__(self)
        self.pack(expand = YES, fill = BOTH)
        self.master.title('Simple Calculator')
        self.master.iconname('calculator')
        display = StringVar()
        Entry(self, relief = SUNKEN, textvariable = display).pack(side = TOP, expand = YES, fill = BOTH)
        for key in ('123', '456', '789', '-0.'):
            keyF = frame(self, TOP)
            for char in key:
                button(keyF, LEFT, char, lambda w=display, s = ' {} '.format(char): w.set(w.get() + s))
        opsF = frame(self, TOP)
        for char in '+-*/=':
            if char is '=':
                btn = button(opsF, LEFT, char)
                btn.bind('<ButtonRelease-1>', lambda e,s = self, w = display: s.calc(w), '+')
            else:
                btn = button(opsF, LEFT, char, lambda w = display, c = char: w.set(w.get() + ' ' + c + ' '))
        clearF = frame(self, BOTTOM)
        button(clearF, LEFT, 'Clear', lambda w = display: w.set(''))
    def calc(self, display):
        try:
            display.set(`eval(display.get())`)
        except ValueError:
            display.set('ERROR')
if __name__ == '__main__':
    Calculator().mainloop()

运行效果图

Simple Tkinter Calculator

详细的步骤

  1. 整个程序从两个函数开始, frame()button() ,这两个函数可以让我们更回方便的创建小工具,而且是整个程序更加紧凑,我使用的是 pack 布局管理器(在程序开发中,经常会将一些最常用的函数或方法写为公共函数,这样可以使得程序更加紧凑,可读性也更好,更易维护)。
  2. 我使用 Frame 构造器构造了最顶级的封闭框架,接着设置了它的名称与图标。
  3. 接着,创建了该计算器最上部的显示条(当我们使用该计算器时,输入与结果都会在这里面显示),同时还定义了一个Tkinter变量,它可以访问Tkinter小工具的内容。
  4. 在 Python 中,任何一个字符串都是一个元素为字符的序列,所以,它们都是可以被迭代的。
  5. 我使用了 button() 函数来创建了多个按钮,并且将其添加到它们的上一级框架中。
  6. 我们为

Python 编写 Server 端的步骤

第一步:创建 socket 对象

调用 socket 构造函数,如:

socket = socket.socket( family, type )
  • family 参数代表地址家族,可以为 AF_INET 或者 AF_UNIX ,AF_INET 家族包括 Internet 地址,AF_UNIX 家族则用于同一台机器上的进程间通信
  • type 参数代表套接字类型,可以为 SOCK_STREAM (流套接字) 或者 SOCK_DGRAM (数据报套接字)。

第二步:将 socket 绑定到指定地址

这是通过 socket 对象的 bind 方法来实现的:

socket.bind( address )

由 AF_INET 创建的套接字, address 必须是一个双元素无组,格式是 ( host, port ),host 代表主机, port 代表端口号,如果端与正在使用,主机名不正确或者商品端口已被保留, bind 方法将引发 socket.error 异常。

第三步:使用 listen 套接字的 listen 方法接收连接请求

socket.listen( backlog )

backlog 指定最多允许多少个客户连接到服务器,它的值至少为 1,收到连接请求后,这些请求需要排队,如果队列已满,就拒绝请求。

第四步:服务器套接字通过 socket 的 accept() 方法等待客户请求一个连接

connection, address = socket.accept()

调用 accept 方法时, socket 会进入 “waiting” 状态,客户请求连接时,方法建立连接并返回服务器, accept 方法返回一个含有两个元素的元组(connection, address),第一个元素 connection 是新的 socket 对象,服务器必须通过它与客户通信;第二个元素 address 是客户的 Internet 地址。

第五步:处理

服务器和客户端通过 send 和 recv 方法通信(传输数据)。服务器调用 send ,并采用字符串形式向客户发送信息,send 方法返回已发送的字符个数,服务器使用 recv 方法从客户端接收信息,调用 recv时,服务器必须指定一个整数,它对应于可通过本次方法调用来接收的最大数据量。 recv 方法在会进入“blocked”状态,最后返回一个字符串,用它表示接收到的数据,如果发送的数据量超过了 recv 所允许的数据会被截取,多余的数据将缓冲于接收端,以后调用 recv 时,多余的数据会从缓冲区删除(以及上次调用 recv 以来,客户可能发送的其它任何数据)。

第六步:传输结束

服务器调用 socket 的 close 方法关闭连接

Python 编写客户端的步骤

第一步:创建一个 socket 以连接服务器

socket = socket.socket( family, type)

第二步:使用 socket 的 connect 方法连接服务器

对于 AF_INET 家族,连接格式如下:

socket.connect(( host, port ))

host 代表服务器的主机名或者 IP, port 代表服务器进程所绑定的端口号,如果连接成功,客户端就可以与服务器进行通信了,如果连接失败,会引发 socket.error异常

第三步:处理阶段

客户和服务器将通过 send 方法和 recv 方法通信

第四步:传输结果

客房端通过 socket 的 close 方法关闭连接

整个示例的完整代码

server.py 文件

#!/usr/bin/env python
# encoding: utf-8
"""
File: server.py
Created by Pan Tao on 2011-09-20.
Copyright (c) 2011 CosTony.Com. All rights reserved.
"""
def main():
    import socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost', 8000))
    sock.listen(5)
    while True:
        connection, address = sock.accept()
        try:
            connection.settimeout(5)
            buf = connection.recv(1024)
            if buf == '1':
                print 'Client {} connected to server!'.format(address)
                connection.send('Welcome to server!')
            else:
                connection.send('Please go out!')
        except socket.timeout:
            print 'Time out!'
        connection.close()
if __name__ == '__main__':
    main()

client.py 文件

#!/usr/bin/env python
# encoding: utf-8
"""
File: client.py
Created by Pan Tao on 2011-09-20.
Copyright (c) 2011 CosTony.Com. All rights reserved.
"""
def main():
    import socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('localhost', 8000))
    import time
    time.sleep(2)
    sock.send('1')
    print sock.recv(1024)
    sock.close()
if __name__ == '__main__':
    main()

测试运行

我们先运行 server.py 以开启服务器

python server.py

然后运行 client.py 连接服务器

python client.py

运行结果:

Server 端将打印出:

Client ('127.0.0.1', 56933) connected to server!

客户端将打印出:

Welcome to server!

基于 Bottle 的文件上传脚本

该脚本实在是简陋哈,只能上传文件和下载文件,显示当前服务器上的文件列表,没有其它的功能,只是自己学习 Bottle 的练习了:

#!/usr/bin/env python
# encoding: utf-8
"""
File: com.costony.fileuploader.py
Created by Pan Tao on 2011-09-20.
Copyright (c) 2011 CosTony.Com. All rights reserved.
"""
from bottle import route, template, run, request, redirect, static_file
import os
index_tpl = '''
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
	<title>COS TONY FILE UPLOADER</title>
	<style type="text/css">
	body {font-family: arial; font-size: 80%;line-height:2em;}
	a {color: #393; font-size:1.5em; text-decoration:none}
	h1 { font-size:2em; padding-bottom: .3em;border-bottom: 1px solid #999;}
	</style>
</head>
<body>
<h1>Upload File</h2>
<form action = "/upload" method = "POST" enctype="multipart/form-data">
    <input type="file" name="data" />
    <input type="submit" value="Upload" />
</form>
<h1>Files List</h1>
<ul>
%for file in files:
<li><a href="/download/{{file}}">{{file}}</a></li>
%end
</ul>
</body>
</html>
'''
@route('/')
def index():
    return template(index_tpl, files = [f for f in os.listdir('./files') if not f.startswith('.')])
@route('/upload', method = 'POST')
def upload():
    data = request.files.get('data')
    save_file = open('./files/{}'.format(data.filename), 'wb')
    if data.file:
        try:
            buf = data.file.read(data.bufsize)
            while buf:
                save_file.write(buf)
                buf = data.file.read(data.bufsize)
            save_file.close()
            return redirect('/')
        except Exception, e:
            return redirect('/')
@route('/download/:filename')
def download(filename):
    return static_file(filename, root='./files')
if __name__ == '__main__':
	run(reloader = True)

- Older Posts »