January Star
  • Home
  • Categories
  • Tags
  • Archives

那些年我们踩过的跟Python有关的坑(持续更新)

Contents

  • list.sort, dict.update 返回啥?
  • tuple 的创建,不是少个逗号么
  • 装饰过后了,我的 func_name 呢?
  • bool(gevent.spawn(lambda: True)): True? or False?
  • 自定义异常无法正常 Pickle 反序列化
  • json.loads(json.dumps({1: 2})) == {1: 2}?
  • str.encode([encoding[, errors]]) 中的关键字参数

list.sort, dict.update 返回啥?

1
2
3
4
5
6
7
def aa():
    a = [2, 8, 3, 1, 6, 4]
    return a.sort()

def bb():
    b = {1: 2, 3: 4, 5: 6}
    return b.update({7: 8})

好吧,虽说我用 Python 也不少年头了,但是最近还是犯上面例子中的错误。

虽然我知道他们会修改值本身的内容,而我却想当然以为它们会将修改后的值返回出来,但现实是很残酷的,它们都返回 None。

1
2
3
4
5
In [10]: print [4, 2, 3].sort()
None

In [11]: print {"a": 1, "b": 2}.update({"c": 3})
None

tuple 的创建,不是少个逗号么

1
2
3
4
5
6
7
8
In [36]: a = (); type(a)
Out[36]: tuple

In [37]: a = (1); type(a)
Out[37]: int

In [38]: a = (1,); type(a)
Out[38]: tuple

在 Python 中,括号的功能主要是来进行组合的,而不是用来创建元组的。比如:

1
2
In [47]: a=("first;" "second;"); a
Out[47]: 'first;second;'

装饰过后了,我的 func_name 呢?

Python 中不仅可以使用自带的 @staticmethod 等来进行装饰,还可以自己写装饰器来进行装饰。

但是装饰完的函数还是原来的函数么?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def wrapper(func):
    def _wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return _wrapper

def aa():
    return "aa"

@wrapper
def bb():
    return "bb"
1
2
3
4
5
In [11]: aa.func_name
Out[11]: 'aa'

In [12]: bb.func_name
Out[12]: '_wrapper'

经过装饰的函数,它的 func_name 已经发生了变化,说明它已经不是原来的函数定义了。

那么如何解决经过装饰的函数还拥有原来的一此属性呢?

使用 functools.wraps
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import functools

def wrapper(func):
    @functools.wraps(func)
    def _wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return _wrapper

def aa():
    return "aa"

@wrapper
def bb():
    return "bb"
1
2
3
4
5
In [11]: aa.func_name
Out[11]: 'aa'

In [12]: bb.func_name
Out[12]: 'bb'

Tip

如果某个函数之间已经装饰过了,然后改变装饰器,则原来的函数仍然是使用改变之前的装饰器。

bool(gevent.spawn(lambda: True)): True? or False?

1
2
3
4
5
6
def aa():
    return "aa"

aa_spawn = None
aa_spawn = gevent.spawn(aa)
print bool(aa_spawn) == True

上面的代码最后结果是 True 么?

是

好,咱们接着看。

1
2
3
4
5
6
7
def aa():
    return "aa"

aa_spawn = None
aa_spawn = gevent.spawn(aa)
aa_spawn.join()
print bool(aa_spawn) == True

上面的代码最后结果是 True 么?

难道不是么,我 X,还真不是。

怎么回事啊?

这个就要看 bool 函数的内部判断逻辑了。

以下情况会被认为是 False,其他的情况是 True。

  • None
  • False
  • zero of any numeric type, for example, 0, 0L, 0.0, 0j.
  • any empty sequence, for example, '', (), [].
  • any empty mapping, for example, {}.
  • instances of user-defined classes, if the class defines a __nonzero__() or __len__() method, when that method returns the integer zero or bool value False.

这时候咱们再看一下 aa_spawn 这个对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
In [25]: dir(aa_spawn)
Out[25]:
['GreenletExit',
 '__class__',
 ... ...
 '__module__',
 '__new__',
 '__nonzero__',
 '__reduce__',
 ... ...]

哦,我看到 __nonzero__ 这个属性了。当 gevent.greenlet 对象还没运行时,该属性为 True ,但当它执行完了,该属性就会变成 False 了。

SO,判断一个对象是否为真,千万要小心使用诸如以下形式的自动判断啊:

1
2
3
4
5
6
if some_instance:
    do_something()

some_instance and do_something()

some_instance or do_something()

自定义异常无法正常 Pickle 反序列化

我们先来定义一个自定义异常 MyError

1
2
3
4
5
6
7
8
class MyError(Exception):

    def __init__(self, desc):
        super(MyError, self).__init__()
        self.__desc = desc

    def __str__(self):
        return "MyError: {}".format(self.__desc)

接下来咱们序列化一下:

1
2
3
4
5
6
In [10]: import pickle

In [11]: p = pickle.loads(e)

In [12]: p
Out[12]: "c__main__\nMyError\np0\n(tRp1\n(dp2\nS'_MyError__desc'\np3\nS'saifsdf'\np4\nsb."

貌似序列化成功了,接下咱们再反序列化一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
In [16]: pickle.loads(p)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-18-6aaee2b1d950> in <module>()
----> 1 pickle.loads(p)

/usr/lib/python2.7/pickle.pyc in loads(str)
   1380 def loads(str):
   1381     file = StringIO(str)
-> 1382     return Unpickler(file).load()
   1383
   1384 # Doctest

/usr/lib/python2.7/pickle.pyc in load(self)
    856             while 1:
    857                 key = read(1)
--> 858                 dispatch[key](self)
    859         except _Stop, stopinst:
    860             return stopinst.value

/usr/lib/python2.7/pickle.pyc in load_reduce(self)
   1131         args = stack.pop()
   1132         func = stack[-1]
-> 1133         value = func(*args)
   1134         stack[-1] = value
   1135     dispatch[REDUCE] = load_reduce

TypeError: __init__() takes exactly 2 arguments (1 given)

好家伙,出现这么信息,竟然异常了。

好吧,有问题,查看官方网站 pickle 文档 。

pickle 只能序列化和反序列化以下类型:

  1. None, True, and False
  2. integers, long integers, floating point numbers, complex numbers
  3. normal and Unicode strings
  4. tuples, lists, sets, and dictionaries containing only picklable objects
  5. functions defined at the top level of a module
  6. built-in functions defined at the top level of a module
  7. classes that are defined at the top level of a module
  8. instances of such classes whose __dict__ or the result of calling __getstate__() is picklable(see section The pickle protocol for details).

按照以上定义没有问题啊, MyError 符合其中第 7 条的要求。事实也是可以序列化,但不能反序列化。好吧,继续看官方文档。

Pickling and unpickling extension types

  object.__reduce__()

    When the Pickler encounters an object of a type it knows nothing about
    — such as an extension type
    — it looks in two places for a hint of how to pickle it.
    One alternative is for the object to implement a __reduce__() method.
    If provided, at pickling time __reduce__() will be called with no arguments,
    and it must return either a string or a tuple.

原来 pickle 对自定义异常 一无所知 。所以咱们需要在某个地方告诉它应该怎么序列化和反序列化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class MyError(Exception):

    def __init__(self, desc):
        super(MyError, self).__init__()
        self.__desc = desc

    def __str__(self):
        return "MyError: {}".format(self.__desc)

    def __reduce__(self):
        return (MyError, (self.__desc,))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
In [10]: import pickle

In [20]: e = MyError("error")

In [21]: p = pickle.dumps(e)

In [24]: p
Out[24]: "c__main__\nMyError\np0\n(S'error'\np1\ntp2\nRp3\n."

In [28]: pickle.loads(p)
Out[28]: __main__.MyError()

In [29]: c = pickle.loads(p)

In [30]: str(c)
Out[30]: 'MyError: error'

json.loads(json.dumps({1: 2})) == {1: 2}?

1
2
3
4
5
6
In [4]: import json

In [5]: a = {1: 2}

In [7]: json.loads(json.dumps(a)) == a
Out[7]: False

奇怪,序列化 + 反序列化前后不应该一样么?

好吧,我们看一下,JSON 序列化再反序列化的数据是什么样本?

1
2
In [6]: json.loads(json.dumps(a))
Out[6]: {u'1': 2}

字典的 Key 值由原来的数值类型变成了字符串类型。

JSON 为什么会有这种奇怪的行为?

我们来看一下 JSON 中文网 的 JSON 格式说明。

原来 JSON 语法规定:一个键值对的集合(Python 叫字典),其键必须为 string(字符串)。(JSON 作为一种数据交换格式,可能需要考虑兼容性, 不是所有语言都支持键为数值类型)

好吧,这种情况我只能 Orz...

str.encode([encoding[, errors]]) 中的关键字参数

在 Python2.6 上面,它的执行结果如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
In [10]: s = u"abc"

In [11]: s.encode("utf-8", errors="ignore")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-11-a702e9e976da> in <module>()
----> 1 s.encode("utf-8", errors="ignore")

TypeError: encode() takes no keyword arguments

In [12]: s.encode("utf-8", "ignore")
Out[12]: 'abc'

在 Python2.7 上面,它的执行结果如下:

1
2
3
4
In [9]: s = u"abc"

In [10]: s.encode("utf-8", errors="ignore")
Out[10]: 'abc'

Python2.7 才支持关键字参数。具体可参见 str.encode ,str.decode 也是一样的。

我在 Google 搜索了一下,看来被这个坑的,还不止我一个,哈哈。

1 Comment

Published

Oct 9, 2014

Category

python

Tags

  • 坑 1
  • python 23

Contact

  • Powered by Pelican. Theme: Elegant by Talha Mansoor