Python学习难点和易错点

 

这是我的第一个博客,是对之前学习的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)

输出结果:

Python学习难点和易错点_第1张图片

注意:

如果异常或错误类型为exceptions模块定义的,在except子句中不用写出exceptions,如上例,直接写except ZeroDivisionError即可,如果写成except exceptions.ZeroDivisionError,此时又还没导入exceptions模块,则会报语法错误,如下:

Python学习难点和易错点_第2张图片

 

25、Python多线程同步问题:

线程同步有多种方法,其中一种是所有线程共享同样的全局变量,同样Python也是可以通过这种方式实现线程间同步,但是全局变量应为可变类型,否则子线程无法跟踪该全局变量的变化。

 

26、Python导入相关:import,reload,__import__在python中的区别

 

27、在Python 2.x中,bytes只是str的一个别名,参考源码:

Python学习难点和易错点_第3张图片

 

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

输出结果:

Python学习难点和易错点_第4张图片

补充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,))

更多内容后续更新。

 

你可能感兴趣的:(学习笔记,个人总结)