这是我的第一个博客,是对之前学习的Python进行进一步的深入理解的过程中遇到的一些比较有意思的地方。
注:本文基于python 2.x
1、__new__()和__init__():
__new__()可看做是构造函数,正常情况需返回类实例(一般是通过super(currentClass, cls).__new__(cls)调用返回类实例),如果其不返回类实例,则__init__()不会被调用;而当__new__()返回类实例,__init__()则作为初始化函数,对类实例属性进行设置等,其接收的参数与__new__()一致,也即__new__()和__init__()的函数定义的参数必须一致,否则会报错;
具体可参考:官方文档对__new__和__init__的描述,Python 之 __new__() 方法与实例化
2、Python的函数参数传递是将引用对象地址的一份复制赋值给形参,若在函数内改变形参的指向(即对形参重新赋值),则不会影响原指向内存的内容。
相关参考:https://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference
例如:
def try_to_change_list_reference(the_list):
print 'got', the_list
the_list = ['and', 'we', 'can', 'not', 'lie']
print 'set to', the_list
outer_list = ['we', 'like', 'proper', 'English']
print 'before, outer_list = ', outer_list
try_to_change_list_reference(outer_list)
print 'after, outer_list = ', outer_list
运行结果为:
before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']
4、对于metaclass的理解:
(1)类用于创建对象,而metaclass用于创建类;
(2)在python2中,使用__metaclass__指明一个类的metaclass,而python3中则在类定义时使用metaclass指出,形如
“class Foo(object, metaclass=something):”;
(3)python解释器根据以下顺序确定一个类的metaclass:
1)、在类中对__metaclass__赋值;
2)、如果该类有至少一个父类,则使用父类的metaclass(即首先寻找父类的__class__,如果没找到,则使用父类的类型)
3)、如果该类没有父类,则在全局变量中寻找__metaclass__;
4)、如果全局变量没有__metaclass__,则使用types.ClassType
(4)__metaclass__必须为callable,并且其必须能创建类(即其需要调用type()或者继承type类,type类是一个非常特殊的类,其metaclass为自身);
(5)如果一个类的metaclass也是一个类,则metaclass.__new__()需调用type.__new__()去创建一个类。
具体可以参考:对metaclass的完美讲解,官方文档对于metaclass的解释
5、classmethod和staticmethod相关内容:https://stackoverflow.com/questions/136097/what-is-the-difference-between-staticmethod-and-classmethod-in-python
6、对于@staticmethod装饰的静态方法,对于调用它的类或者实例一无所知,也即该静态方法内无法访问它所在的类的类变量或者实例变量。
7、类变量VS实例变量:每个实例将获得类变量的一份拷贝,即变量内容相同而地址不同,如果对某个实例的类变量重新赋值,不会影响到该类其他实例的这个类变量值。
例如:
class Person:
name="aaa"
p1=Person()
p2=Person()
p1.name="bbb"
print p1.name # bbb
print p2.name # aaa
print Person.name # aaa
8、字典推导式:d = {key: value for (key, value) in iterable}
e.g:myList = [('a',1), ('b',2), ('c',3)]
d = {key: value for (key, value) in myList}
对比列表推导式:[x**2 for x in range(10) if x % 2 == 0]
9、单下划线和双下划线:
a、在一个模块中,单下划线开头的变量和函数被默认当中内部私有成员,当使用from a_module import *时,这些变量和函数不会被导入,但如果使用import a_module导入时,仍然可以用a_module._var访问;
b、如果类成员使用双下划线开头命名,则Python会自动将其名称改为_classname__var,这是为了避免该成员的名称与子类中的名称冲突;
c、而类似于__init__为Python具有特别含义的标识符,Python 官方推荐永远不要将这样的命名方式应用于自己的变量或函数
具体可以参考:Python 的类的下划线命名有什么不同
10、字符串格式化:%和.format:
易错:对于"hi there %s" % name,当name为(1,2,3)时会抛出TypeError,此时要修改为 "hi there %s" % (name, )才能正常打印。
11、迭代器和生成器:
(1)迭代器是一个有next(Python 2.x)或者__next__(Python 3.x)方法的对象;
(2)生成器也是迭代器,是只能迭代遍历一次的可迭代对象;
(3)创建生成器的两种常用方法:
a、将列表生成式的'[]'改为'()',
b、在函数中使用yield关键字,则调用该函数时返回一个生成器对象;
生成器具体可参考:yield关键字的使用,也可以参考廖雪峰的python教程
12、可变参数*args和关键字参数**kwargs:http://stackoverflow.com/questions/3394835/args-and-kwargs
13、装饰器的作用与使用:
(1)装饰器的作用就是对被装饰的函数进行功能扩展,其本质就是一个语法糖,即下图的最后一行:
def d(func):
def wrapper(*args, **kwargs):
pre_handle()
func(*args, **kwargs)
post_handle()
return wrapper
@d
def f():
pass
# 上面其实等价于以下代码:
def f():
pass
f = d(f)
(2)python解释器对于这个‘@’语法糖的处理流程:把@符号后面的内容看成一个整体去执行,并且必须保证执行结果是返回一个函数(或者叫做函数的引用,在C里的叫法就是函数地址);因此带有参数的构造器,即在调用构造器时传入参数,形如@d(arg),则构造器的实现必须多加一层内嵌函数,来接纳传入的参数,同时保证执行结果,即d(arg)是返回一个函数,可以参考:
def d(content):
def mydecorator(func):
def wrapper(*args, **kwargs):
print content
func(*args, **kwargs)
return wrapper
return mydecorator
@d('hehe')
def f():
pass
# 上面等价于以下代码:
def f():
pass
f = d('hehe')(f)
(3)对于多个装饰器的堆叠使用(如下图),基于(1)中最后一行,@符号这一行以下的内容视为整体,依次类推到最后一个@符号所在行,因此类似于函数的递归调用,也即从上往下给被装饰函数扩展功能:
def d_1(func):
def wrapper(*args, **kwargs):
print 'before d_1'
func(*args, **kwargs)
print 'after d_1'
return wrapper
def d_2(func):
def wrapper(*args, **kwargs):
print 'before d_2'
func(*args, **kwargs)
print 'after d_2'
return wrapper
@d_1
@d_2
def print_hello():
print '你好'
# 上面代码等价于:
def print_hello():
print '你好'
print_hello = d_1(d_2(print_hello))
运行结果:
面向切面编程AOP和装饰器的解释也可以参考:https://stackoverflow.com/questions/739654/how-to-make-a-chain-of-function-decorators
14、python的单例模式实现
a、使用__new__()方法:
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
orig = super(Singleton, cls)
cls._instance = orig.__new__(cls, *args, **kwargs)
return cls._instance
class MyClass(Singleton):
a = 1
b、使用装饰器:
def singleton(cls):
instances = {}
def _singleton(*args, **kw):
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return _singleton
@singleton
class MyClass(object):
a = 1
def __init__(self, x=0):
self.x = x
c、使用import
# a.py
class Singleton(object):
def foo(self):
pass
singleton = Singleton()
执行from a import singleton,则获得单例类实例。
15、Python中的作用域
Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。
当 Python 遇到一个变量的话他会按照这样的顺序进行搜索:
本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局/模块作用域(Global)→内置作用域(Built-in)
16、threading.Thread类的start()和run()的区别:
start() 方法的作用是启动一个新线程,新线程会执行相应的run()方法,start()不能被重复调用。
而run()方法则只是普通的方法调用,在调用线程中顺序运行而已。
17、对于以下代码:
list = ['a', 'b', 'c']
print list[10:]
运行结果:[]
解释:对一个列表以超出列表长度作为开始索引的切片不会导致IndexError,而是返回一个空列表;但如果是访问列表的元素时,如果索引超出边界,则报IndexError错误。
18、str()默认的编码方式是ASCII;str(s)是s.encode(‘ascii’)的简写,以下是python官方文档“tutorial”的说明:
“The built-in function unicode() provides access to all registered Unicode codecs (COders and DECoders).
Some of the more well known encodings which these codecs can convert are Latin-1, ASCII, UTF-8, and
UTF-16. The latter two are variable-length encodings that store each Unicode character in one or more
bytes. The default encoding is normally set to ASCII, which passes through characters in the range 0 to
127 and rejects any other characters with an error. When a Unicode string is printed, written to a file, or
converted with str(), conversion takes place using this default encoding.”
注意:Python的默认编码是ASCII
str类型的字符其具体的编码格式是UTF-8还是GBK,还是其他格式,根据操作系统相关
有关python编码问题的详细解释:Python 编码为什么那么蛋疼?
19、sorted(),当参数reverse=True时,降序排列,反之为升序。
20、使用MySQLdb的接口对数据库插入记录条目时,容易犯错的地方:
例如员工表(主要字段有name(varchar)和gender(varchar))插入一个条目,用Python有时会写成:
name = 'jyj'
gender = 'male'
sql = "INSERT INTO m_tb_employee(sName, sGender) VALUES ({name}, {gender});".format(name=name, gender=gender)
execute_sql(sql) #execute_sql为自定义的执行sql语句的函数
sql = "INSERT INTO m_tb_employee(sName, sGender) VALUES ({name}, {gender});".format(name=name, gender=gender)
execute_sql(sql) #execute_sql为自定义的执行sql语句的函数
但是执行以上的代码会报错,报错原因是没办法插入该条目,根本原因是format会把字符串的内容替换了{name},也即sql的值为:
"INSERT INTO m_tb_employee(sName, sGender) VALUES (jyj, male);"
但实际我们需要的是:
"INSERT INTO m_tb_employee(sName, sGender) VALUES ('jyj', 'male');"
因此上面的代码第三行应改为:
sql = "INSERT INTO m_tb_employee(sName, sGender) VALUES ('{name}', '{gender}');".format(name=name, gender=gender)
也即sql语句使用format格式化时,位置参数需要加上'',也即'{arg}'
当然format函数也可以用%来替代,也即上述sql语句等价于:
sql = 'INSERT INTO m_tb_employee(sName, sGender) VALUES (%s, %s);' % (name, gender)
21、try...except...finally的使用问题:当try语句中包含return语句时,当return语句抛出异常时,finally语句依然执行。
例程:
def myDiv(a, b):
try:
return a/b
except Exception as e:
print e
finally:
print 'finished!'
if __name__ == '__main__':
print myDiv(1, 0)
运行结果:
22、当调用一个没有返回值的函数时,Python默认该函数返回值为None,参考上面的例程。
23、使用try...except的场景:try...except为Python的异常捕捉,所谓异常其实就是可以预估的导致try语句执行出错的情况,当使用别人写的代码时(尤其是使用socket连接的API)时应当使用try...except语句避免API调用出现异常使得Python进程停止。
24、想获取程序运行抛出的异常或错误类型,可使用except Exception as e,然后print type(e),即可获取如ValueError等异常或错误类型。
例:
try:
a = 1/0
except Exception as e:
print type(e)
输出结果:
注意:
如果异常或错误类型为exceptions模块定义的,在except子句中不用写出exceptions,如上例,直接写except ZeroDivisionError即可,如果写成except exceptions.ZeroDivisionError,此时又还没导入exceptions模块,则会报语法错误,如下:
25、Python多线程同步问题:
线程同步有多种方法,其中一种是所有线程共享同样的全局变量,同样Python也是可以通过这种方式实现线程间同步,但是全局变量应为可变类型,否则子线程无法跟踪该全局变量的变化。
26、Python导入相关:import,reload,__import__在python中的区别
27、在Python 2.x中,bytes只是str的一个别名,参考源码:
28、Python解释器对for...in my_iterator的处理过程:How does Python for loop work?
(1)my_iterator必须是一个可迭代对象(Iterable), 而可迭代对象要么有__iter__方法(可以返回迭代器),要么有一个__getitem__方法(能够从0开始索引该迭代对象的元素,且超过最大索引会抛出IndexError);通过可迭代对象,我们可以得到一个迭代器(Iterator);
(2)迭代器是一个有next(Python 2.x)或者__next__(Python 3.x)方法的对象;
(3)for子句会先对可迭代对象使用iter()函数,也即进行iter(iterable),返回一个迭代器(iterator),然后调用迭代器的next或者__next__方法遍历其所有元素。
具体的也可以参考:What exactly are iterator, iterable, and iteration?,官方文档对迭代器的说明
29、range和xrange的区别(python2):
range返回的是列表,而xrange返回的是xrange类的实例,其有__iter__()方法,会返回一个iterator,在对于大范围的表示,xrange会明显节约内存。
30、字典的遍历:
易错点:使用形如for key, value in my_dict:print key, value进行遍历,运行此代码会报“ValueError: toomany values to unpack”错误;
正确做法:
(1)使用for key, value in my_dict.iteritems():print key, value (注:python 3.x不支持)
(2)使用for key, value in my_dict.items():print key, value
(3)使用for key in my_dict.keys():print key, my_dict[key]
31、负数取余问题:(也即“%”运算符问题)
例子:在Python中,-5 % 4,返回的是3。
理解:按数学的理解,应该返回-1,但是Python对它进行了+4操作,因此返回3。
参考:The modulo operation on negative numbers in Python
32、python中%r和%s的区别:
%r用rper()方法处理对象,而%s用str()方法处理对象。
参考:When to use %r instead of %s in Python?
33、Python中__repr__和__str__区别:
(1)如果一个类只重构了__repr__,则__str__默认等于__repr__,而如果只重构了__str__,则__repr__还是使用Python默认的;
(2)__repr__和__str__需要返回字符串类型,否则会报错;
(3)在交互模式下,输出类实例会使用__repr__,而print类实例则会使用__str__。
参考:Difference between __str__ and __repr__?
34、一个有趣的问题:当使用try...except KeyboardInterrupt来捕捉键盘中断异常时,如果try语句中包含阻塞语句,例如socket_obj.accept()方法,由于该方法默认是阻塞的,所以此时CRTL+C并不能一定导致进程退出(在Windows上基本无法退出,在Linux上可以退出)。
参考代码:
import socket
def bar():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 6666))
s.listen(5)
try:
cli_sock, cli_addr = s.accept()
except KeyboardInterrupt:
print "STOP the server!"
if __name__ == '__main__':
bar()
当运行该脚本以后,按CRTL+C并不能结束该脚本运行,除非有人请求,使得s.accept()执行完成,否则一直会阻塞在该语句,导致不能捕捉KeyboardInterrupt异常,从而无法让该进程结束。
35、python的import机制:(这一块具体可以展开讲很多)
参考链接:Python Import 详解,Python中import机制,python模块的导入以及模块简介
最重要的还是要看官方文档:Modules
36、调试代码时想找出哪一行出错,可以使用traceback.print_exc()函数,例如:
import traceback
try:
1/0
except Exception,e:
traceback.print_exc()
输出结果:
补充trackback的其他使用相关:
traceback.print_exc()跟traceback.format_exc()有什么区别呢?
format_exc()返回字符串,print_exc()则直接给打印出来。
即traceback.print_exc()与print traceback.format_exc()效果是一样的。
print_exc()还可以接受file参数直接写入到一个文件。比如
traceback.print_exc(file=open('tb.txt','w+'))
写入到tb.txt文件去。
37、一段有趣的代码:
a = [3,6,9,12,15]
for i in a:
if i % 3 == 0:
a.remove(i)
# output.
>>> a
[6, 12]
那么为什么上面代码的运行结果不是空列表呢?
原因分析:
python解释器对于for的处理,会使用一个变量记录当前所遍历的位置p,每一轮遍历后会p += 1,当在遍历时改变了遍历对象时,如上面代码对a进行了remove处理,则p会指向改变后的遍历对象的该位置;例如开始p = 0, 此时a[0] = 3,被remove掉,然后p = 1,此时a = [6,9,12,15],则当前i = a[1] = 9,也即6被跳过处理了,最终上面代码的运行结果为[6,12]
如果期望运行结果为空列表,怎么办?
解决方法:
只需将for这行代码修改为:for i in a[:]:
38、正则表达式相关:
请参考:Python正则表达式指南
39、如果需要使用format格式化的字符串中包含'{'或者'}',则需要使用两个花括号表示,如下所示:
>>> x = " {{ Hello }} {0} "
>>> print x.format(42)
' { Hello } 42 '
相关参考:How can I print literal curly-brace characters in python string and also use .format on it?
40、进程内存增长问题, 解决方法和工具
请参考:python 进程内存增长问题, 解决方法和工具
41、当tuple只有一个元素时,记得后面加上逗号,否则当该元素为字符串时,且该tuple作为函数参数时,会把字符串展开为一个个字符传入函数中:
import logging
_s = "hehe"
# 错误实例,会报错:
# TypeError: not all arguments converted during string formatting
# Call stack:
# File "", line 1, in
# Message: 'hhhh %s'
# Arguments: ('h', 'e', 'h', 'e')
logging.error("hhhh %s", *(_s))
# 正确实例
logging.error("hhhh %s", *(_s,))
更多内容后续更新。