Python中常见的陷阱

Anu*_*yal 75 python

可能重复:
Python 2.x陷阱和地雷

今天,多年后我被可变的默认参数再次咬了.除非需要,我通常不会使用可变的默认参数,但我认为随着时间的推移我忘了这一点.今天在应用程序中,我在PDF生成函数的参数列表中添加了tocElements = [],现在每次调用"generate pdf"后,"目录"变得越来越长.:)

我还应该在我的列表中添加什么才能避免?

  • 始终以相同的方式导入模块,例如,from y import x并将import x视为不同的模块.

  • 不要使用范围代替列表因为range()无论如何都将成为迭代器,以下将失败:

    myIndexList = [0, 1, 3]
    isListSorted = myIndexList == range(3)  # will fail in 3.0
    isListSorted = myIndexList == list(range(3))  # will not
    
    Run Code Online (Sandbox Code Playgroud)

    使用xrange可能会错误地做同样的事情:

    myIndexList == xrange(3)
    
    Run Code Online (Sandbox Code Playgroud)
  • 小心捕获多种异常类型:

    try:
        raise KeyError("hmm bug")
    except KeyError, TypeError:
        print TypeError
    
    Run Code Online (Sandbox Code Playgroud)

    这打印出"嗯bug",虽然它不是一个bug; 看起来我们正在捕获这两种类型的异常,但我们只将KeyError作为变量TypeError捕获,而是使用它:

    try:
        raise KeyError("hmm bug")
    except (KeyError, TypeError):
        print TypeError
    
    Run Code Online (Sandbox Code Playgroud)

e-s*_*tis 72

不要使用索引循环序列

别 :

for i in range(len(tab)) :
    print tab[i]
Run Code Online (Sandbox Code Playgroud)

做:

for elem in tab :
    print elem
Run Code Online (Sandbox Code Playgroud)

For将为您自动执行大多数迭代操作.

使用enumerate如果你真的需要这两个指标和元素.

for i, elem in enumerate(tab):
     print i, elem
Run Code Online (Sandbox Code Playgroud)

使用"=="检查TrueFalse时要小心

if (var == True) :
    # this will execute if var is True or 1, 1.0, 1L

if (var != True) :
    # this will execute if var is neither True nor 1

if (var == False) :
    # this will execute if var is False or 0 (or 0.0, 0L, 0j)

if (var == None) :
    # only execute if var is None

if var :
    # execute if var is a non-empty string/list/dictionary/tuple, non-0, etc

if not var :
    # execute if var is "", {}, [], (), 0, None, etc.

if var is True :
    # only execute if var is boolean True, not 1

if var is False :
    # only execute if var is boolean False, not 0

if var is None :
    # same as var == None
Run Code Online (Sandbox Code Playgroud)

不要检查是否可以,只是这样做并处理错误

Pythonistas通常会说"请求宽恕比允许更容易".

别 :

if os.path.isfile(file_path) :
    file = open(file_path)
else :
    # do something
Run Code Online (Sandbox Code Playgroud)

做:

try :
    file =  open(file_path)
except OSError as e:
    # do something
Run Code Online (Sandbox Code Playgroud)

或者甚至更好的python 2.6+/3:

with open(file_path) as file :
Run Code Online (Sandbox Code Playgroud)

它更好,因为它更通用.您可以对几乎任何事情应用"尝试/除外".您无需关心如何防止它,只需要关注您所面临的错误.

不要检查类型

Python是动态类型的,因此检查类型会使您失去灵活性.相反,通过检查行为来使用duck typing.EG,你期望一个函数中的字符串,然后使用str()来转换字符串中的任何对象.您期望一个列表,使用list()来转换列表中的任何可迭代.

别 :

def foo(name) :
    if isinstance(name, str) :
        print name.lower()

def bar(listing) :
    if isinstance(listing, list) :
        listing.extend((1, 2, 3))
        return ", ".join(listing)
Run Code Online (Sandbox Code Playgroud)

做:

def foo(name) :
    print str(name).lower()

def bar(listing) :
    l = list(listing)
    l.extend((1, 2, 3))
    return ", ".join(l)
Run Code Online (Sandbox Code Playgroud)

使用最后一种方式,foo将接受任何对象.Bar会接受字符串,元组,集合,列表等等.廉价干:-)

不要混合空格和制表符

只是不要.你会哭的.

使用object作为第一个父对象

这很棘手,但随着程序的增长,它会咬你.Python 2.x中有新旧类.旧的,旧的.它们缺少一些功能,并且可能具有继承的笨拙行为.为了可以使用,你的任何一个班级必须是"新风格".为此,请将其从"对象"继承:

别 :

class Father :
    pass

class Child(Father) :
    pass
Run Code Online (Sandbox Code Playgroud)

做:

class Father(object) :
    pass


class Child(Father) :
    pass
Run Code Online (Sandbox Code Playgroud)

在Python 3.x中,所有类都是新样式,因此您可以声明class Father:很好.

不要在__init__方法之外初始化类属性

来自其他语言的人发现它很诱人,因为你用Java或PHP做的工作.编写类名,然后列出属性并为其指定默认值.它似乎在Python中工作,然而,这并不像你想象的那样工作.

这样做会设置类属性(静态属性),然后当你尝试获取对象属性时,它将为你提供它的值,除非它是空的.在这种情况下,它将返回类属性.

它意味着两大危害:

  • 如果更改了class属性,则更改初始值.
  • 如果将可变对象设置为默认值,则将获得跨实例共享的相同对象.

不要(除非你想要静态):

class Car(object):
    color = "red"
    wheels = [wheel(), Wheel(), Wheel(), Wheel()]
Run Code Online (Sandbox Code Playgroud)

做:

class Car(object):
    def __init__(self):
        self.color = "red"
        self.wheels = [wheel(), Wheel(), Wheel(), Wheel()]
Run Code Online (Sandbox Code Playgroud)

  • 在你的"只做它并纠正错误"节中,你有一个except:block这是evilbadscary.总是捕获最小的错误子集,以避免在较大的程序中进行错误的调试陷阱,因此在您的示例中替换除以下:除了IOError作为E :(总是值得抓住异常实例进行检查;)) (6认同)

Jor*_*lob 35

当你需要一组数组时,你可能会想要输入这样的东西:

>>> a=[[1,2,3,4,5]]*4
Run Code Online (Sandbox Code Playgroud)

当然,当你看到它时,它会给你你所期望的东西

>>> from pprint import pprint
>>> pprint(a)

[[1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5]]
Run Code Online (Sandbox Code Playgroud)

但是不要指望人口中的元素是单独的对象:

>>> a[0][0] = 2
>>> pprint(a)

[[2, 2, 3, 4, 5],
 [2, 2, 3, 4, 5],
 [2, 2, 3, 4, 5],
 [2, 2, 3, 4, 5]]
Run Code Online (Sandbox Code Playgroud)

除非这是你需要的......

值得一提的是一个解决方法:

a = [[1,2,3,4,5] for _ in range(4)]
Run Code Online (Sandbox Code Playgroud)

  • 我不会在表达式中使用_.首先,它对交互式解释器具有特殊意义.其次,你的意思是忽略循环变量并不是很明显.如何将其更改为"忽略"或某些此类? (6认同)
  • _对翻译来说意味着什么? (3认同)

S.L*_*ott 28

Python语言陷阱 - 以非常模糊的方式失败的东西

  • 使用可变的默认参数.

  • 前导零表示八进制. 09是Python 2.x中一个非常模糊的语法错误

  • 在超类或子类中拼写错误的方法名称.超类拼写错误更糟糕,因为没有一个子类正确地覆盖它.

Python设计陷阱

  • 花时间进行内省(例如尝试自动确定类型或超类身份或其他内容).首先,从阅读来源可以看出这一点.更重要的是,花在奇怪的Python内省上的时间通常表明了掌握多态性的根本失败.在SO上80%的Python内省问题都未能获得多态性.

  • 花时间在代码高尔夫上.仅仅因为你的应用程序的心理模型是四个关键词("do","what","I","mean"),并不意味着你应该建立一个超复杂的内省装饰者驱动的框架来做到这一点.Python允许您将DRY带到愚蠢的水平.关于SO的其余Python内省问题试图减少复杂问题以编码高尔夫练习.

  • 的Monkeypatching.

  • 未能真正读取标准库,并重新发明轮子.

  • 使用适当的程序将交互式类型与Python相关联.当您以交互方式键入内容时,可能会丢失对变量的跟踪并且必须使用globals().此外,在您打字时,几乎所有内容都是全球性的.在适当的程序中,你永远不会"失去"一个变量,没有什么是全局的.

  • 这是python:你有源.如果第三方lib有错误,您可以修复它. (8认同)

Wil*_*ris 26

改变默认参数:

def foo(bar=[]):
    bar.append('baz')
    return bar
Run Code Online (Sandbox Code Playgroud)

默认值仅计算一次,而不是每次调用该函数.重复调用foo()将返回['baz'],['baz', 'baz'],['baz', 'baz', 'baz'],...

如果你想变异吧做这样的事情:

def foo(bar=None):
    if bar is None:
        bar = []

    bar.append('baz')
    return bar
Run Code Online (Sandbox Code Playgroud)

或者,如果你想参数是最终的:

def foo(bar=[]):
    not_bar = bar[:]

    not_bar.append('baz')
    return not_bar
Run Code Online (Sandbox Code Playgroud)


小智 21

我不知道这是否是一个常见的错误,但是虽然Python没有增量和减量运算符,所以允许使用双重符号

++i
Run Code Online (Sandbox Code Playgroud)

--i
Run Code Online (Sandbox Code Playgroud)

语法正确的代码,但没有做任何"有用"或你可能期望的事情.

  • 应该注意到python递增/递减是用i + = 1,i - = 1完成的.(代替++ i,== i或i ++,i--). (5认同)

Wil*_*ris 16

在查看标准库之前滚动自己的代码.例如,写这个:

def repeat_list(items):
    while True:
        for item in items:
            yield item
Run Code Online (Sandbox Code Playgroud)

当你可以使用这个时:

from itertools import cycle
Run Code Online (Sandbox Code Playgroud)

经常被忽视的模块(除此之外itertools)的例子包括:

  • optparse 用于创建命令行解析器
  • ConfigParser 用于以标准方式读取配置文件
  • tempfile 用于创建和管理临时文件
  • shelve 用于将Python对象存储到磁盘,在完整的数据库过度使用时很方便


uol*_*lot 14

避免使用关键字作为自己的标识符.

而且,不使用总是好的from somemodule import *.


Ste*_*ini 13

很惊讶没人说这个:

缩进时混合制表符和空格.

真的,这是一个杀手.相信我.特别是,如果它运行.

  • 实际上有人这样做了. (5认同)
  • @ e-satisf:如果你按时间顺序阅读答案,那就不行了. (3认同)

rof*_*kel 13

如果您来自C++,请认识到在类定义中声明的变量是静态的.您可以在init方法中初始化非静态成员.

例:

class MyClass:
  static_member = 1

  def __init__(self):
    self.non_static_member = random()
Run Code Online (Sandbox Code Playgroud)


Ima*_*ist 12

不使用功能工具.从风格的角度来看,这不仅仅是一个错误,从速度的角度来看,这是一个错误,因为很多功能工具都在C语言中进行了优化.

这是最常见的例子:

temporary = []
for item in itemlist:
    temporary.append(somefunction(item))
itemlist = temporary
Run Code Online (Sandbox Code Playgroud)

正确的方法:

itemlist = map(somefunction, itemlist)
Run Code Online (Sandbox Code Playgroud)

这是正确的方法:

itemlist = [somefunction(x) for x in itemlist]
Run Code Online (Sandbox Code Playgroud)

如果您一次只需要一个可用的处理项目,而不是一次只需要一次,您可以通过使用可迭代的等价物来节省内存并提高速度

# itertools-based iterator
itemiter = itertools.imap(somefunction, itemlist)
# generator expression-based iterator
itemiter = (somefunction(x) for x in itemlist)
Run Code Online (Sandbox Code Playgroud)


van*_*van 10

代码像Pythonista:惯用语Python

  • 不要这样做. (3认同)

ste*_*anv 9

正常复制(分配)是通过引用完成的,因此通过调整相同的对象并插入来填充容器,最后得到一个容器,其中包含对最后添加的对象的引用.

copy.deepcopy改用.


ire*_*ses 8

re当为每个常见操作存在完美的字符串方法时(例如,大写,简单匹配/搜索),导入并使用完整正则表达式方法进行字符串匹配/转换.


zer*_*ble 7

最后一个链接是原始链接,这个SO问题是重复的.


Dav*_*ver 7

%s在错误消息中使用formatter.在几乎所有情况下,%r都应该使用.

例如,想象代码如下:

try:
    get_person(person)
except NoSuchPerson:
    logger.error("Person %s not found." %(person))
Run Code Online (Sandbox Code Playgroud)

打印此错误:

ERROR: Person wolever not found.

无法判断person变量是字符串"wolever",unicode字符串u"wolever"还是Person类的实例(已__str__定义为def __str__(self): return self.name).然而,如果%r使用了,则会有三种不同的错误消息:

...
logger.error("Person %r not found." %(person))
Run Code Online (Sandbox Code Playgroud)

会产生更多有用的错误:

ERROR: Person 'wolever' not found.
ERROR: Person u'wolever' not found.
ERROR: Person  not found.

另一个很好的理由是路径复制/粘贴更容易.想像:

try:
    stuff = open(path).read()
except IOError:
    logger.error("Could not open %s" %(path))
Run Code Online (Sandbox Code Playgroud)

如果pathsome path/with 'strange' "characters",错误消息将是:

ERROR: Could not open some path/with 'strange' "characters"

这在视觉上难以解析并且难以复制/粘贴到shell中.

然而,如果%r使用,则错误将是:

ERROR: Could not open 'some path/with \'strange\' "characters"'

易于视觉分析,易于复制粘贴,更好.


sca*_*ble 6

我会停止在2.6中使用已弃用的方法,以便您的应用程序或脚本可以准备好并更容易转换为Python 3.


Wog*_*gan 6

我不得不训练自己的坏习惯是使用X and Y or Z内联逻辑.

除非你能100%始终保证这Y将是一个真正的价值,即使你的代码在18个月的时间内发生变化,你也会为自己设置一些意想不到的行为.

值得庆幸的是,在以后的版本中你可以使用Y if X else Z.


mak*_*puf 5

一些个人意见,但我发现最好不要

  • 使用已弃用的模块(对它们使用警告)

  • 过度使用类和继承(可能是静态语言遗留的典型)

  • 显式使用声明性算法(作为迭代与for使用 itertools

  • 重新实现标准库中的函数,“因为我不需要所有这些功能”

  • 为了功能而使用功能(降低与旧版 Python 版本的兼容性)

  • 当你确实不需要的时候使用元类,更普遍的是让事情变得太“神奇”

  • 避免使用发电机

  • (更个人化)尝试在低级基础上对 CPython 代码进行微优化。最好花时间在算法上,然后通过创建一个由 调用的小型 C 共享库来进行优化ctypes(在内循环上获得 5 倍性能提升非常容易)

  • 当迭代器就足够时使用不必要的列表

  • 在您需要的库全部可用之前,直接为 3.x 编写一个项目(这一点现在可能有点争议!)


Anu*_*yal 5

++n--n通过人C或Java背景的预期可能无法正常工作。

++n是一个正数的正数,即n

--n是负数的负数,即n


Dro*_*per 5

我也已经开始学习Python,而我犯的最大错误之一就是不断使用索引为“ for”的C ++ / C#循环。Python具有for(i; i <length; i ++)类型的循环,这是有充分的理由的-大多数时候,有更好的方法可以执行相同的操作。

示例:我有一个遍历列表并返回所选项目索引的方法:

for i in range(len(myList)):
    if myList[i].selected:
        retVal.append(i)
Run Code Online (Sandbox Code Playgroud)

取而代之的是,Python具有列表理解功能,该列表理解功能以更优雅,更易读的方式解决了相同的问题:

retVal = [index for index, item in enumerate(myList) if item.selected]
Run Code Online (Sandbox Code Playgroud)