python小技巧与坑

python小技巧与坑

这几天打算用python写个小爬虫,就找出先前写python时候整理的一份小文档,顺便把它贴到博客上面来,以后如果还有其他心得,也一并记录在这里好了。

1. 时间处理

import time
print time.strftime( '%Y-%m-%d %X', time.localtime())

python中有四种时间表示方式: 字符串,时间戳,time.struct_time类型,datetime.datetime类型
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import datetime


def main():
    # python中的时间有四种表示方式

    # 1.字符串表示
    time_string = "20170918123059"
    print type(time_string), time_string

    # 2.时间戳,一个浮点数,表示从基准时间(1970.01.01 00:00:00)到该时间点所经过的秒数,小数点后面表示毫秒数
    time_stamp = time.time()
    print type(time_stamp), time_stamp

    # 3.time.struct_time类型,该类型其实是一个命名元组(named tuple,类似C语言的结构体)
    time_struct = time.localtime()
    print type(time_struct), time_struct

    # 4. datetime.datetime类型 (还有datetime.date类型,只表示日期,没有时间。)
    dt = datetime.datetime.now()
    print type(dt), dt

    print "================ 互相转换 ==================="

    # 时间戳 => struct_time, 使用time.localtime()
    st = time.localtime(time_stamp)
    print st

    # struct_time => 时间戳, 使用time.mktime()
    ts = time.mktime(time_struct)
    print ts

    # struct_time => 字符串, 使用time.strftime(format, t)
    str = time.strftime("%Y-%m-%d %H:%M:%S", time_struct)
    print str

    # 字符串 => struct_time, 使用time.strptime(string, format)
    st = time.strptime(time_string, "%Y%m%d%H%M%S")
    print st

    # datetime => 字符串, 使用datetime.strftime(format)
    str = dt.strftime("%Y-%m-%d %H:%M:%S")
    print str

    # 字符串 => datetime, 使用datetime.strptime(date_string, format)
    dt = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%S")
    print dt

    # datetime => struct_time, 使用datetime.timetuple()
    print dt.timetuple()

    # datetime => timestamp, 使用datetime.timetuple()得到struct_time类型再转行成timestamp
    print time.mktime(dt.timetuple())

    # timestamp => datetime, 使用datetime.fromtimestamp()
    print datetime.datetime.fromtimestamp(time_stamp)

    print "================ 时间差 ==================="

    # 计算两个时间的差,最好用时间戳相减,结果即为相差的秒数(小数点后为毫秒)

    # 计算上个月的最后一天,可以使用datetime.timedetla类型
    now = datetime.datetime.now()
    print "当前时间:",  now
    print "三小时前:", now - datetime.timedelta(hours=3)
    print "三天前:", now + datetime.timedelta(days=-3)
    print "上个月最后一天:", datetime.datetime(day=1, month=now.month, year=now.year) + datetime.timedelta(days=-1)

    pass

if __name__ == "__main__":
    main()

屏幕快照 2017-09-19 上午12.13.14

2. 过程计时

import timeit
StartTime_timeit = timeit.default_timer()
#procdure
EndTime_timeit = timeit.default_timer()
print "Procedure elapsed ", EndTime_timeit - StartTime_timeit, " seconds"

不要用python 2.7中time模块的time.time()或者time.clock()函数,这两个函数都是平台相关的。在python3.3+版本,time.clock()也已经被取消。

3. 输出错误信息

# -*- coding: utf-8 -*-
import sys 
import traceback
import logging

def divisionzero():
    return 1/0
try:
    divisionzero()
except Exception, e:
    #打印错误信息
    print sys.exc_info()[:2]

    #打印traceback信息
    print traceback.format_exc()
    
    #打印错误信息
    #如果用logger就是logger.error()
    logging.error("错误信息: %s", e)
    
    #打印traceback信息
    #logging.execption其实是logging.error的升级版,多加了traceback信息
    #注意这个方法只能用在except下面
    logging.exception("错误信息traceback:")

注意,第一行# -*- coding: utf-8 -*-是为了让python识别文件中的中文。

 

4. 线程不安全

python有不少模块是线程不安全的。

mysql.connector 的cursor、connector变量也是线程不安全的,千万不要在线程里面共享。

logging模块则是线程安全的。

5. 多线程中让主线程等待子线程完成后退出

def main():
    threads = []		#the arry of thread object
    thread_1 = Thread()		#build one thread object
    thread_2 = Thread()		#build another thread object
    threads.append(thread_1)
    threads.append(thread_2)
	
    for t in threads:
        t.start()		#start running a thread in background
    for t in threads:
        t.join()		#block main thread until t thread end

注意:由于全局解释器锁(Global Interpreter Lock,GIL)的作用,python的多线程只会在一个CPU核上运行。所以对于CPU密集型的程序,python的多线程其实根本没有帮助;对于IO密集型的程序的话,还是可以用用的,比如爬虫。

如果想要利用CPU的多个核,最好使用python的多进程模块multiprocessing。参考使用 Python 实现多进程

PS:解释型语言天生都有多线程短板。python如此;javascript根本就没有多线程的概念,不过他有反人类的异步回调机制;php是后来出了pthreads扩展才支持的多线程。

6. 关于urllib2的坑

(1) urllib与urllib2有个缺陷,就是他们是不可重用链接的,即使你在request的hearder中添加connection:keep-alive信息,但在urllib与urllib2的open函数底层,他会自动覆盖这个header为connection:close。即每次发生一个请求后,都会关闭连接,所以每次response响应都返回connection:close的头。下次请求时就重新建立链接。。。 =。=#

要想可重用链接,可以使用urllib3

(2) 如果在多线程中使用不同代理的话,就不能使用urllib2.urlopen()了,因为urllib2.install_opener() 安装的是一个全局变量。而是应该在线程里直接用opener.open()

proxy_ip = random.choice(ip_list)
proxy_support = urllib2.ProxyHandler({'http':ip, 'https':ip})
opener = urllib2.build_opener(proxy_support)
req = urllib2.Request(url)
response = opener.open(req, timeout=3)
html = response.read()

7. 程序入口 if __name__ == '__main__':

python的一个脚本文件,既可以独立执行,又可以当做一个模块被引入另一个文件。

def test1():
    print "i'm in A.test1 function"
    return

def test2():
    print "i'm in A.test2 function"
    return

if __name__ == '__main__':
    test1()

以上面这段代码为例,A.py可以作为一个模块被引用,这时它有两个函数A.test1()跟A.test2();如果单独执行python A.py,那么if __name__ == '__main__':就相当于是该脚本的程序入口,将会执行test1()函数。

这在自定义模块时候非常好用,通常我们会在__main__入口下面放些测试代码,用来单独测试模块的可用性。我觉得这是一个好的python编程习惯。

8. with ... as ...

推荐使用with ... as ...来打开文件

with open("hello.txt") as hello_file:
    for line in hello_file:
        print line

该用法相当于如下这段try ... except ... finally
try:
    open_file = open('hello.txt')
except:
    print 'no such file!'
else:
    try:
        for line in open_file:
            print line
    except:
        print 'read error!'
    finally:
        open_file.close()

9. python的类

9.1 class virables与instance virables

class virables有点像类静态变量的意思,所有对象共享该变量(实际上这样讲不严谨,因为对象有可能覆盖一个class virable为instance virable),

instance virables就是普通的变量,每个对象有一份。

参考https://docs.python.org/2/tutorial/classes.html#class-and-instance-variables

9.2 私有变量、私有方法、类方法、静态方法

# -*- coding: utf-8 -*-

class MyClass(object):
    # class virables
    classVirable = "类变量"

    def __init__(self):
        super(MyClass, self).__init__()

        # instance virables
        # 成员变量必须定义在__init__中,如果暂时无值可以用self.variable=None表示
        self.variable = "成员变量"
        self.__privateVariable = "私有成员变量"
        pass

    # 双下划线开头为私有
    def __privateFunc(self):
        print "这个是私有方法"
        pass

    # python在调用普通实例方法时候,会默认传入实例本身作为第一个实参
    # 因此定义类的实例方法,必须定义第一个参数为self
    def normalFunc(self):
        print "这个是实例方法"
        print "可以直接调用私有变量与私有方法:", self.__privateVariable
        print self
        pass

    # python在调用类方法时候,会默认传入类类型作为第一个实参
    # 因此定义类的类方法,必须定义第一个参数为clazz
    @classmethod
    def classFunc(clazz):
        print "这个是类方法"
        print clazz
        print clazz.classVirable
        pass

    # python在调用类静态方法时候,不会修改要传递的参数
    # 因此定义类的静态方法,无需额外增加self或者clazz参数
    @staticmethod
    def staticFunc():
        print "这个是静态方法"
        pass


def main():
    c = MyClass()

    # 类变量,可以通过类名或实例调用,在__init__之前被赋值给实例
    # 通过实例修改类变量的话,其实是新建了一个同名成员变量,并不会修改到类变量
    print MyClass.classVirable, c.classVirable
    MyClass.classVirable = "通过类名修改类变量"
    print MyClass.classVirable, c.classVirable
    c.classVirable = "通过实例修改类变量"
    print MyClass.classVirable, c.classVirable

    # 成员变量,不能通过类名调用,只能通过实例调用
    print c.variable

    # 直接调用私有变量或者私有方法会报错 >> AttributeError: 'MyClass' object has no attribute '__privateVariable'
    # print c.__privateVariable
    # c.__privateFunc()

    # 私有变量与私有方法,在python中其实并不真的私有!而是被改名了
    print c._MyClass__privateVariable
    c._MyClass__privateFunc()

    # 使用实例调用实例方法,python会自动将实例作为第一个参数传入
    c.normalFunc()
    # 使用类名调用实例方法,必须传递一个实例作为参数
    MyClass.normalFunc(c)

    # 使用实例调用类方法,python会自动将类类型作为第一个参数传入
    c.classFunc()
    # 使用类名调用类方法,python会自动将类类型作为第一个参数传入
    MyClass.classFunc()

    # 使用实例调用静态方法,怎么定义的就怎么调用
    c.staticFunc()
    # 使用类名调用静态方法
    MyClass.staticFunc()
    pass


if __name__ == '__main__':
    main()

 

10. 迭代器与生成器(yield)

http://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/

 

11. 尽量使用logger = logging.getLogger(name)而不是logging

python的logger是有层次关系的,直接使用logging.info()、logging.error()相当于调用的是root logger。logging.getLogger(name)则是从root logger下派生一个名字为name的子logger,一般使用类名进行命名。logging.getLogger()不加参数默认获取root logger。

import logging

LOGGING_FORMAT = "%(asctime)s - %(levelname)5s - %(name)s: %(message)s"

class AAA(object):
    def __init__(self):
        super(AAA, self).__init__()
        self.logger = logging.getLogger(self.__class__.__name__)

class BBB(object):
    def __init__(self):
        super(BBB, self).__init__()
        self.logger = logging.getLogger(self.__class__.__name__)
        self.logger.setLevel("WARNING")
        
if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG, format=LOGGING_FORMAT)
    logging.info("main func starting")

    a = AAA()
    b = BBB()

    a.logger.info("A INFO")
    b.logger.info("B INFO")
    b.logger.warn("B WARNING")

输出:屏幕快照 2016-04-08 下午10.27.02

 

另外,上面我们在连个类中获取了自己的logger,却没有配loghandler,也依然能够输出日志。是因为默认情况下,logger的所有日志都会上传给其父亲logger,父logger再调用自己的loghandler。所以上面的例子中,负责输出日志的其实都是root logger的默认handler。

我们可以给子logger添加一个自己的loghandler。这时候,日志将会通过子logger的loghandler输出一次,同时也会通过父logger的loghandler输出一次。可以通过logger.propagate=False把日志“传播”给关掉,不上传给父logger。

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

import logging
import logging.config

class myHandler(logging.StreamHandler):
	"""docstring for myHandler"""
	def __init__(self):
		super(myHandler, self).__init__()
		self.setFormatter(logging.Formatter('%(levelname)5s %(asctime)s %(name)s.%(funcName)s >> %(message)s'))
		self.setLevel(logging.WARN)

def main():
	logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)5s %(name)s.%(funcName)s - %(message)s')
	logging.info('main start')

	logger1 = logging.getLogger('[logger 1]')
	# 实际上是回传给root logger的默认handler来输出
	logger1.info('111 test')

	logger2 = logging.getLogger('[logger 2]')
	# 默认情况下,propagate=True。所有日志都会上传给父logger再处理
	# 所以这里如果不关闭propagate,会发现控制台输出了两次日志
	logger2.propagate = False

	# loghandler的日志等级(如果有设置的话)会自动覆盖logger的日志等级
	logger2.addHandler(myHandler())
	logger2.setLevel(logging.INFO)

	logger2.info('222 info')
	logger2.error('222 error')
	pass

if __name__ == '__main__':
	main()

 

12. logging.config读取log配置文件

用过java的log4j,觉得java的log配置特别方便,那么python的logging.config模块其实也可以做到。

新建一个log.config文件

[loggers]
keys = root

[handlers]
keys = consoleHandler, fileHandler, timedRotatingFileHandler

[formatters]
keys = commonFormatter

############################

[logger_root]
level = NOTSET
handlers = consoleHandler, fileHandler, timedRotatingFileHandler

[handler_consoleHandler]
class = logging.StreamHandler
level = NOTSET
formatter = commonFormatter
args=(sys.stdout,)

[handler_fileHandler]
class = logging.FileHandler
level = NOTSET
formatter = commonFormatter
args=("log/file.log", "a")

[handler_timedRotatingFileHandler]
class = logging.handlers.TimedRotatingFileHandler
level = NOTSET
formatter = commonFormatter
args=("log/rotate.log", 'M', 1)

[formatter_commonFormatter]
format = %(asctime)s %(levelname)5s %(name)s.%(funcName)s - %(message)s
datefmt = %Y-%m-%d %H:%M:%S 
# datefmt可以不设置,按asctime的默认格式输出。

然后修改测试代码如下:
import logging
import logging.config

class AAA(object):
    def __init__(self):
        super(AAA, self).__init__()
        self.logger = logging.getLogger(self.__class__.__name__)
 
class BBB(object):
    def __init__(self):
        super(BBB, self).__init__()
        self.logger = logging.getLogger(self.__class__.__name__)
        self.logger.setLevel("WARNING")
        
if __name__ == '__main__':
    logging.config.fileConfig("log.config")
    logging.info("main func starting")
 
    a = AAA()
    b = BBB()
 
    a.logger.info("A INFO")
    b.logger.info("B INFO")
    b.logger.warn("B WARNING")

运行该代码,就会发现总共有三处日志输入,分别是控制台、file.log日志文件、rotate.log按时间分割的日志文件。

注意,关于logging handler有一些需要注意的地方:

首先,StreamHandler、FileHandler在logging模块下,但TimedRotatingFileHandler是在logging.handlers模块下。

又注,自带的XXXRotatingFileHandler其实都是针对单进程且常驻内存的进程,对于多进程同时运行,或者单进程瞬时运行(每次只运行一小段时间,但频繁启动)的时候,就不适用了。首先对于多进程的切分,会导致日志丢失的情况,这时候可以考虑用WatchedFileHandler,然后自己写crontab进行日志切分。然后对于单进程瞬时运行的情况,我试过按小时切分的测试脚本,1:30运行的一次测试,理论上2:00再运行一次的时候,日志文件应该被切分的,但它不,因为对于新运行的进程来说,他是去判断文件修改时间的,还不足1小时我天。。。。

另外, 在linux下尽量用WatchedFileHandler(windows不适用),因为当日志文件被移动或删除后:

  1. FileHandler会继续将日志输出至原有的文件描述符, 从而导致日志切分后日志丢失;
  2. WatchedFileHandler会检测文件是否被移动或删除, 如果有, 会新建日志文件, 并输出日志到新建的文件。

13. 编码规范

详细参考:http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/contents/

13.1 命名规范

类名使用CapWords的方式,全局变量名使用全大写辅以下划线的方式,模块名、包名、函数名、局部变量名等都使用全小写辅以下划线的方式:

module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_VAR_NAME, instance_var_name, function_parameter_name, local_var_name.

ps:我以前的代码都是按照c++、java的驼峰方式来命名变量,函数。然后每次PyCharm都有一大堆波浪线提示命名不标准,神烦。=。=#

13.2 引号的使用规范

自然语言使用双引号,比如输出:print u"hello, 哈呜"

机器标识使用单引号,比如键名:print people['name']

还是放弃这种用法吧,这样单双引号切来切去太麻烦了。google编码规范现在也建议统一用一种就好了,单双任选。

Be consistent with your choice of string quote character within a file. Pick ' or " and stick with it. It is okay to use the other quote character on a string to avoid the need to \\ escape within the string.

我觉得以后还是统一选择单引号吧!因为不用按shift键。

多行字符串以及文档字符串用三重双引号"""... ..."""而非三重单引号'''... ...'''

13.3 空格

在二元操作符两边都加上一个空格, 比如赋值(=), 比较(==, <, >, !=, <>, <=, >=, in, not in, is, is not), 布尔(and, or, not). 至于算术操作符两边的空格该如何使用, 需要你自己好好判断. 不过两侧务必要保持一致.

Yes: x == 1

No:  x<1

当’=’用于指示关键字参数或默认参数值时, 不要在其两侧使用空格.
Yes: def complex(real, imag=0.0): return magic(r=real, i=imag)

No:  def complex(real, imag = 0.0): return magic(r = real, i = imag)

14. 配置文件解析器ConfigParser

之前我还傻逼逼的自己写配置文件解析。(´・_・`) 后来才发现原来python标准库里面就有。参考文章http://www.cnblogs.com/huey/p/4334152.html

新建配置文件myproject.config

# 配置文件由多个section组成
# 中括号代表一个section
# section下面跟着它的options
[db]
host = 127.0.0.1
port = 3306
user = root
pass = root

[ssh]
host = 192.168.1.101
user = huey
pass = huey

火狐截图_2017-01-17T15-52-55.635Z

读取配置文件时候,推荐使用config.readfp(codecs.open(config_file, 'r', 'utf8'))来代替config.read(config_file),前者读入的字符串将会是unicode类型,后者则是str类型。

15. 装饰器

python装饰器的语法糖看起来就像java的注解,作用就像面向切面编程。

# -*- coding: utf-8 -*-
from functools import wraps


def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwds):
        print "装饰器 start"
        result = func(*args, **kwds)
        print "装饰器 end"
        return result

    return wrapper


@my_decorator
def main():
    """装饰器测试函数"""
    print "main process start"
    print "main process end"
    pass


if __name__ == "__main__":
    main()
    print "=========================="
    # 如果不在上面的装饰器前加@wraps,那么main方法的名字会变成"wrapper",而且无法找到原本的文档字符串
    print "main函数名字:", main.__name__
    print "main函数文档字符串:", main.__doc__

ps:《用装饰器写一个single instance》

16. python的字符编码(the fucking UnicodeEncodeError)

16.1 字符集(charset)与字符编码(encoding)

字符集:即可使用的字符的集合,同时规定了每个字符对应的二进制码。常见的有ascii、gb2312、unicode。

屏幕快照 2017-06-18 14.59.20

字符编码:规定字符二进制码的存储方式。常见的有ascii编码、gb2312编码、utf-8编码。

ps 1:通常,字符集与字符编码成一一对应关系。比如ascii字符集与字符编码,gb2312字符集与字符编码,但是偏偏unicode字符集却又许多种编码方式,utf-8、utf-16、utf-32都针对unicode字符集的字符编码。

ps 2:字符经过编码后存在内存中的二进制值未必会等值于其在字符集中所规定的二进制值,因为编码可能会对二进制码进行相应的转换。

比如ascii编码规定直接按字符的ascii二进制码存储,占用一个字节。

utf-8编码会对字符的unicode二进制码进行转换后再存储,占用字节数从1~6个字节不等。

utf-16编码同样也会对unicode二进制码进行转换后再存储,占用2或4个字节,同时utf-16编码在存储时候还分大尾序和小尾序。

屏幕快照 2017-06-18 15.17.45

16.2 <type 'str'>与<type 'unicode'>,decode与encode

python2有两种字符串类型,一种是str字符串(byte string),一种是unicode字符串(text string)。

(python3的字符串类型不一样了,变成了str类型与bytes类型。str类型即是unicode字符串,bytes类型则表示二进制字符串)

str字符串可以看做是一个字节数组,即将字符串经过字符编码后得到的二进制字节串。(字符编码可以通过encode()方法指定)

unicode字符串则是一unicode字符数组,每个单元就是一个unicode字符。

我们以 a = "a你好" , b = u"b你好" 这两个字符串做个测试。

屏幕快照 2017-06-18 22.07.37

我们可以看到,type(a)是str字符串,由于我的测试环境是mac,系统默认的中文编码是utf-8,对于字符串长度,其中英文a占一个字节,你好各占3个字节,所以总长度为7。

然后再看字符串b,在python中,字符串前面的u表示该字符串为unicode字符串。所以type(b)得到的就是unicode类型。然后长度就是3个unicode字符。

python有一对专门的函数来进行byte string与unicode string之间的相互转换:encode()与decode()。

decode()将byte string(str字符串)解码成text string(unicode字符串),二进制码串 >> 解码 >> 字符串
encode()将text string(unicode字符串)编码成byte string(str字符串),字符串 >> 编码 >> 二进制码串
所以,调用decode的主体,必须是str字符串;调用encode的主体必须是unicode字符串。

# -*- coding: utf-8 -*-
print "a你好".decode('utf-8')
print u"a你好".encode('utf-8')

print type("a你好".decode('utf-8'))
print type(u"a你好".encode('utf-8'))

print len("a你好".decode('utf-8'))
print len(u"a你好".encode('utf-8'))

 

16.3 # -*- coding: utf-8 -*-与sys.setdefaultencoding('utf-8')

通常,我们需要在python脚本的开头写上一行关于utf-8的注释,如下所示。

# -*- coding: utf-8 -*-
或者
# coding=utf-8

这是因为python解释器默认使用ascii编码来读取脚本文件(它认为文件是以ascii编码存储的),如果文件中出现非ascii字符,就会报错。所以必须在文件开头加上这一行,从一开始就告诉python解释器使用utf-8编码来读取脚本,这样才能在脚本中正常使用中文等非ascii字符。

注意:这一行不是代码,不会执行的,它只是影响Python解释器读取脚本文件时候使用的编码。

然后,对于sys.setdefaultencoding('utf-8'),与上面那行注释的作用不同,它用来指定python在运行时所用的默认字符编码。Python2.x为了兼容处理Unicode字符串(unicode)和str字符串,某些时候会自动做两者的转换,但默认使用的是ASCII字符编码,如果有utf-8字符在里面就会出错。这个hack是修改这个默认特性。非常不建议这么做,你应该永远分清str(bytes)和unicode,然后自己调用encode、decode来转换。Python3.x下这两个类型已经不兼容了。

 

17. 浮点数转换的坑

int()函数将浮点数转换成整数时,是直接截取整数位,而不考虑小数位是否需要四舍五入的!所以浮点数转换成整数需要先手工四舍五入下再转int。否则遇到99.9999999这种直接int就会变成99而不是100了。

if __name__ == '__main__':
    f = [89.99991, 91.00001, 97.00002, 95.99998]
    for i in f:
        print i, int(i), round(i), int(round(i))
        pass

18. 包、模块与类

这三者的关系可以简单的理解为:包是目录,模块是目录下的xxx.py文件,类是xxx.py文件定义的Class。

模块module,是一个py文件。它在第一次导入程序的时候被执行(引入模块文件中定义的类、函数,或直接模块文件中的语句)

包package,是一个目录,目录中必须包含一个__init__.py文件。另外还可以有多个模块文件与“子包”。__init__.py文件可以看作是包的默认模块,该文件可以为空,也可以在其中定义变量、函数、类,或者执行import。

18.1 导入模块文件

以如下目录结构为例,主文件main.py以及模块文件common.py:


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

import common

def main():
    c = common.Common()
    c.say_hello()
    pass


if __name__ == '__main__':
    main()

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

print "导入本模块的话,这句print会被直接执行"

class Common(object):
    """docstring for Common"""
    def __init__(self, arg=None):
        super(Common, self).__init__()
        self.arg = arg

    def say_hello(self):
        print "hello, it's common.py Common.say_hello()"
        pass

这个主程序的执行结果是

18.2 import module与from ... import ...的区别

import common
# 表示导入common模块,
# 这时候想要调用Common类的话,必须加上前缀
# c = common.Common()

from common import Common
# 表示从common模块中导入Common类
# 这时候想要调用Common类的话,只需要
# c = Common()

import关键字后面跟的都是module。即使有时候看起来是包名,其实import package等价于import package.__init__,而__init__是这个包的默认模块。

from ... import ...的用法可以是:

from module import Class/func/variable

from package import module

18.3 导入包中的模块

以如下文件结构为例:


PI = 3.1415926

class BasicColor(object):
    def __init__(self, rgb=(0,0,0)):
        super(BasicColor, self).__init__()
        self.rgb = rgb

class BasicShape(object):
    def __init__(self, arg):
        super(BasicShape, self).__init__()
        self.type = arg

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

import draw
from draw import colors
from draw.shapes import BasicShape

def main():
    print draw.PI

    red = colors.BasicColor((1,2,3))
    print red.rgb
    
    triangle = BasicShape('triangle')
    print triangle.type

    pass


if __name__ == '__main__':
    main()

我们来看一下main.py文件中的几个import语句的区别:

import draw 实际上只是导入了draw包目录下的__init__.py文件。所以可以通过draw.PI来引用draw包的默认模块(__init__.py)中定义的PI变量。但是这句并不会导入除__init__之外的其他模块:colors与shapes模块!!!

from draw import colors 从draw包中导入colors模块。所以可以通过colors.BasicColor来引用colors模块中的类。

from draw.shapes import BasicShape 从shapes模块中直接导入BasicShape类。所以可以直接调用BasicShape类了。

 

18.4 __init__.py的用法

可能我们并不想用from draw.shapes import BasicShape这么长的路径来引入一个类(这还不算长的,如果考虑多级包目录的话)。那么,我们可以在draw目录的__init__.py文件中加上一行:

from .shapes import BasicShape
# 表示从当前包的shapes模块中引入BasicShape类

那么,在main.py中,就可以通过import draw来使用draw.BsasicShape了。或者通过from draw import BasicShape来直接使用BasicShape了。

19. string.format与数据库操作时候的参数

执行数据库语句的时候,通常需要传递变量给要执行的语句。这时候有两种方法。一种是用string.format()来格式化要执行的语句,一种的使用数据库cursor.execute('sql_string', argv)的argv参数来传递变量值。

19.1 string.format()用法

sql_query = "select * from {table_name} where id={id}".format(table_name='user', id=1)
cursor.execute(sql_query)
row = cursor.fetchone()

19.2 cursor.execute()用法

通常的数据库驱动实现都支持带参数的cursor.execute()方法,并且都支持%s与%(name)s这两种参数定位。

# 使用%s占位符
cur.execute("""
    INSERT INTO some_table (an_int, a_date, a_string)
    VALUES (%s, %s, %s);
    """,
    (10, datetime.date(2005, 11, 18), "O'Reilly"))

# 使用带名字的%(name)s占位符
cur.execute("""
    INSERT INTO some_table (an_int, a_date, another_date, a_string)
    VALUES (%(int)s, %(date)s, %(date)s, %(str)s);
    """,
    {'int': 10, 'str': "O'Reilly", 'date': datetime.date(2005, 11, 18)})

 

20. 动态定义类成员变量

有时候需要动态定义(调用)变量名字,或者说用字符串定义(调用)变量名。

如果是在函数中定义局部变量,可以使用exec('%s=%s'%('var_name', value))。但这个我试了只在python 2有效,python 3就无效了。

如果是要在类中动态定义成员变量,可以使用类的__dict__特殊变量来定义。举个例子:

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

class UserModel(object):
    fields = ('id', 'name', 'age', 'gender')

    def __init__(self, row):
        super(UserModel, self).__init__()
        
        assert len(row) == len(self.__class__.fields), '字段数量不一致'

        # 通过__dict__动态定义成员变量并赋值
        # 等价于定义了 self.id=row[0], self.name=row[1], self.age=row[2]
        for i in range(len(row)):
            field = self.__class__.fields[i]
            self.__dict__[field] = row[i]


def main():
    user = UserModel((1, 'funway', 18, 'male'))
    print(user.name)
    pass

if __name__ == '__main__':
    main()

21. ftplib的编码问题

使用python自带的ftplib,在处理中文路径时都会遇到这样一个异常:UnicodeEncodeError: 'latin-1' codec can't encode characters...

这是由于ftplib.py默认使用latin-1编码,而且还硬编码在FTP类的类变量中,简直脑残,设计构造函数可传递的成员变量都好呀。凸=。=

这个情况,有两种方式应对:

1、新建FTP实例后,立即赋值一个encoding成员变量覆盖之(ftplib.py中用到encoding的地方都是用self.encoding来调用,而不是FTP.encoding来调用,所以该方法是有效的)

def ftp_store():
    ftp = FTP('127.0.0.1', 'username', 'password')
    ftp.encoding = 'utf-8'
    file_name = '中文名.txt'
    file_data = 'hello,world'
    if type(file_data) == str:
        file_data = file_data.encode()
    ftp.storbinary('STOR %s' % file_name, io.BytesIO(file_data))
    pass

2、每次遇到中文字符串,都主动改成latin-1编码
def ftp_store():
    ftp = FTP('127.0.0.1', 'username', 'password')
    file_name = '中文名.txt'
    file_data = 'hello,world.'
    if type(file_data) == str:
        file_data = file_data.encode()
    ftp.storbinary('STOR %s' % file_name.encode('utf-8').decode('latin-1'), io.BytesIO(file_data))
    pass

3、网上居然有人说直接修改ftplib.py源码。。。简直👎

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注