美文网首页
关于Python的一些(trick?坑?)

关于Python的一些(trick?坑?)

作者: Teci | 来源:发表于2019-01-06 16:59 被阅读750次

1. 可变类型和不可变类型

可变类型: 列表和字典, 可变集合(set)

不可变类型: 数字, 字符串, 元组, 不可变集合(frozenset)

9A563A4CDB576F2DFA586B8C54F2E9EB.png

凡是不可变类型都可以作为字典的key

a=('s','t')
b=frozenset('cheeseshop')
c=0.582592
d=True
print({a:2,b:3,c:4,d:5})

set无序排序且不重复,是可变的,有add(),remove()等方法。既然是可变的,所以它不存在哈希值。基本功能包括关系测试和消除重复元素. 集合对象还支持union(联合), intersection(交集), difference(差集)和sysmmetric difference(对称差集)等数学运算.

sets 支持 x in set, len(set),和 for x in set。作为一个无序的集合,sets不记录元素位置或者插入点。因此,sets不支持 indexing, 或其它类序列的操作。
frozenset是冻结的集合,它是不可变的,存在哈希值,好处是它可以作为字典的key,也可以作为其它集合的元素。缺点是一旦创建便不能更改,没有add,remove方法。

如果包含的元素都是相同的, 那么set就等于frozenset, 如下代码答案就是 true

s=set('cheeseshop')
t=frozenset('cheeseshop')
print(s==t)

注意其中可变类型的赋值涉及到浅拷贝和深拷贝, 而不可变类型不涉及, 因为是不可变的所以每次是重新创建的地址, 而不是单纯的索引

同样的, 这在全局变量中有体现, 看下面的代码

def test():
    a=0
    ccc={1:2,2:3}
    def test2():
        a=a-2
        ccc[2]=5
    test1()
    test2()
    print(ccc)
test()

此时会报错local variable ‘a' referenced before assignment

原因是对于不可变类型, 在python的函数中和全局同名的变量,如果你有修改变量的值就会变成局部变量,在修改之前对该变量的引用自然就会出现没定义这样的错误了,如果确定要引用全局变量,并且要对它修改,必须加上global关键字。

但是注意到ccc并不会报错, 因为ccc提供的是索引, 是可变类型的

要是想在函数中使用全局变量, 参考如下代码:

def test():
    global a
    a=0
    ccc={1:2,2:3}
    def test2():
        global a
        a=a-2
        ccc[2]=5
        print(a)

    test1()
    test2()
    print(ccc)
test()

同样, 传入函数的参数如果是list的话在函数中修改这个list, 那么原来的list也会改变

def chage_list(arr):
  arr[0]=5

def test():
  test_arr=[1,2,3,4,5]
  chage_list(test_arr)
  print(test_arr)

test()

#结果是:
>>> [5, 2, 3, 4, 5]

2. 关于collections中defaultdict

在用collecitons.defaultdict时, 它会将你访问过的字典中本不存在的值在访问后帮你存下来, 如下:

from collections import defaultdict
dd = defaultdict(lambda: 'N/A')
print(dd['ac'])
print(dd)

结果是:

N/A

defaultdict( at 0x104550b18>, {'ac': 'N/A'})

defaultdict没有相应的参数可以使其不存下来, 之所以defaultdict在query不存在的key时不抛keyerror是因为其先自动创建了一个key:defaultvalue的键值对, 然后再将其返回。之所以这样是可以确保当默认值是可变类型时, 后续对可变类型的操作可以保存下来

譬如如果继承dict并自己写一个subclass为了实现不保存query的key时:

class DefaultDict(dict):
    def __init__(self, default_factory, **kwargs):
        super().__init__(**kwargs)

        self.default_factory = default_factory

    def __getitem__(self, key):
        try:
            return super().__getitem__(key)
        except KeyError:
            return self.default_factory()

d = DefaultDict(int)
res = d[5]
d[8] = 1 
d[3] += 1
print(d)

d = DefaultDict(list)
d[5].append('foobar')
print(d)

结果是:

{8: 1, 3: 1}

{}

很显然对不可变类型时没问题的, 但对可变类型, 其返回结果是{}, 很明显有错误

另一个方法是写一个defaultdict的subclass, 如下:

from collections import defaultdict
class DefaultDict(defaultdict):
    def get_and_forget(self, key):
        _sentinel = object()
        value = self.get(key, _sentinel)
        if value is _sentinel:
            return self.default_factory()
        return value

a=DefaultDict(lambda : 'NA')
print(a.get_and_forget(3))
a[3]=2
print(a.get_and_forget(3))

结果是:

NA

2

其中_sentinel = object()指代的是当前对象, 如果将其print出来则是: <object object at 0x10d6be0b0>, 如果value等于_sentinel也就意味着没有get到值, 字典中没有这个元素, 返回了object的实例。但如果字典中有当前query的key的话, 那么value就等于这个key所对应的value。

2. python中字符串操作

2.1 计算一个字符串中某个substring的数量

string本身有str.count()函数, 如下:

'abababa'.count('ab')
'abababa'.count('aba')

#结果是3和2

很显然, string自带的count不会计算overlap的部分, 如果会计算的话第二行代码的结果就是3而不是2了

那么如果要得到overlap的数量的话需要用正则表达式

import re
print(len(re.findall('(?=aba)','abababa')))

#结果是3

2.2 字符串中split, rsplit和splitlines

split()从左向右寻找,以某个元素为中心将左右分割成两个元素并放入列表中

rsplit()从右向左寻找,以某个元素为中心将左右分割成两个元素并放入列表中

splitlines()根据换行符(\n)分割并将元素放入列表中

举个例子:

1 a = "dlrblist"
2 a1 = a.split("l", 1) 3 print(a1)

输出结果:

['d', 'rblist']

从左向右寻找,以寻找到的第一个"l"为中心将左右分割成两个元素并放入列表中

1 b = "dlrblist"
2 b1 = b.rsplit("l", 1) 3 print(b1)

输出结果:

['dlrb', 'ist']

从右向左寻找,以寻找到的第一个"l"为中心将左右分割成两个元素并放入列表中

1 c = "hello\\nworld\\ndlrb"
2 c1 = c.splitlines() 3 print(c1)

输出结果:

['hello', 'world', 'dlrb']

2.3 字符串的startwith

str.startswith(str, beg=0,end=len(string)); 其中beg和end是指定检查的范围

str = "this is string example....wow!!!";
print str.startswith( 'this' );
print str.startswith( 'is', 2, 4 );
print str.startswith( 'this', 2, 4 );

3. python中字典操作

3.1 字典中设定默认值

python中设定默认值除了collections中的defaultdict外还有如下两种方法, 需要注意的是哪怕其不需要输出默认值时, 两个方法都会执行默认值里的函数

aaa={1:2,3:5,5:6}
print(aaa.setdefault(3,aaa.update({5:8})))
print(aaa)
#5
#{1: 2, 3: 5, 5: 8}

aaa={1:2,3:5,5:6}
print(aaa.get(6,aaa.update({6:8})))
print(aaa)
#8
#{1: 2, 3: 5, 5: 6, 6: 8}

aaa={1:2,3:5,5:6}
print(aaa.get(6,aaa.update({5:10})))
print(aaa)
#None
#{1: 2, 3: 5, 5: 10}

其中get函数虽然是如果没有6这个键值对可以返回后面的default value, 但哪怕aaa里边存在5这个键值对, 其依然会执行后续的update操作, 如果用setdefault进行字典更新时, 有:

dic = {'runoob': 'cais', 'google': 'Google sear'}
'''
setdefault给Taobao这个key设定了一个空的{}的初始值,setdefault返回这个空的{},因为{}是可变类型,
所以返回其reference, 后面的update直接针对这个空的{}进行更新, 而因为返回的是reference, 所以原来
字典中的空{}也会随着更新
'''

dic.setdefault('Taobao', {}).update({'s':'yes'})
print(dic)
print(dic.setdefault('Taobao', {}))

3.2 用函数作为python字典的值

下面3种方法由结果看来很显然都是错误的, 为何会返回gold而不是red呢, 因为在将lambda函数赋值给每个key时, 并没有调用这个函数, python只是记住了lambda返回的是当前val这个参数, 但没有记下这个参数具体的值, 而迭代到最后, 当调用ccc0时, 此时lambda函数会从python的变量管理处寻找val这个变量, 但整个过程中val的值一直在变, 变到最后的’gold’就停止变化了, 所以此时val的值是’gold’, 然后lambda函数找到val后, 就将val此时的值’gold'返回

#方法1
lsystem_colours=["red", "green", "blue",\
                "yellow", "cyan", "magenta",\
                "white", "black", "gray","gold"]

ccc=dict()
for ind,val in enumerate(lsystem_colours):
    ccc[ind]=(lambda:val)
print(ccc[0]())

>>> 'gold'

#方法2
ccc.update({ind:lambda:val for ind,val in enumerate(lsystem_colours)})
print(ccc[0]())

>>> 'gold'

#方法3
ccc.update(dict((ind,lambda:val) for ind,val in enumerate(lsystem_colours)))
print(ccc[0]())

>>> 'gold'

4. python中列表操作

4.1 一些基本的容易忽略的操作

4.1.1 insert函数

向列表中指定位置插入元素, 将列表指定位置的元素右移

temp=[1,3,4,5,6]
temp.insert(1,2)
print(temp)

#结果是
>>> [1, 2, 3, 4, 5, 6]

4.1.2 range操作

print(range(10,0,-1))
print(range(10,-1,-1))
print(range(10,-2,-1))

#结果为:
>>> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
    [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1]

4.1.3 列表切片

s='ABCDEFG'
print([s[:i+1] for i in range(len(s)-1)])
print([s[:i] for i in range(len(s))])

#结果是:
>>> ['A', 'AB', 'ABC', 'ABCD', 'ABCDE', 'ABCDEF']
    ['', 'A', 'AB', 'ABC', 'ABCD', 'ABCDE', 'ABCDEF']

因为此处range函数取不到最大值, 所以最大值较小了1, 然后列表切片s[:]又取不到最大值, 所以最大值又减小了1, 所以取不到G

4.1.4 倒序遍历一个数组

a = ["foo", "bar", "baz"]
for i in reversed(a):
    print i
for i, e in reversed(list(enumerate(a))):
    print(i,e)
print(list(enumerate(a)))

#结果是:
>>> baz
    bar
    foo
    (2, 'baz')
    (1, 'bar')
    (0, 'foo')
    [(0, 'foo'), (1, 'bar'), (2, 'baz')]

4.1.5 二分查找一个倒序数组

bisect模块是只支持正序数组的查找的, 如果要查找倒序数组可以用如下方法

import bisect
a=[6,4,3,2,1]
b=5
print(len(a)-bisect.bisect(a[::-1],b))

4.1.6 字符串求最大值

字符串求最大值的时候一定要用key=lambda… 不然结果会不一样

aaa=['ab']
print(max(aaa,key=lambda x:len(x)))
print(max(aaa,lambda x:len(x)))

#结果是
>>> ab
    ['ab']

4.1.7 通过+=给列表赋值

一般来说, 如果让字符串直接与int相加, 会报错TypeError: can only concatenate list (not "int") to list报, 但如果通过+=号和逗号(逗号可以用来构成元组), 即可一次赋多个值, 其底层实现应该是将判断+=号后如果是元组的话将元组中的元素依次输入到左边的list中

#首先我们来看看用逗号构成元组
a=5
b=5,
print(a,b)
>>> 5 (5,)

#但是如下就不会构成元组
a,b=5,5,
print(a,b)
>>> 5 5

#接下来我们看一下单纯的加号的示例
l1=[1,2,3]
b=5,
l1=l1+b

>>> Traceback (most recent call last):
      File "<pyshell#43>", line 1, in <module>
        l1=l1+b
    TypeError: can only concatenate list (not "tuple") to list

#如果换成+=号呢, 如下可见, 其+=号实现的方法是通过迭代元组, 然后append到list中, 因为底下报的错是not iterable
l2=[1,2,3]
l2+=5,
print(l2)
l2+=5

>>> [1, 2, 3, 5]
    Traceback (most recent call last):
      File "/Users/shenyi/Documents/Untitled.py", line 4, in <module>
        l2+=5
    TypeError: 'int' object is not iterable
  
#当然还可以有更多的尝试:
l3=[1,2,3]
l3+=(4,5,6),7,8,9,[10,11,12]
print(l3)

>>> [1, 2, 3, (4, 5, 6), 7, 8, 9, [10, 11, 12]]

4.1.8 列表删除某个元素

python列表删除某个元素的时候会通过执行一个循环将所有在删除元素另一边的元素转移到它的左边, 为了填补上删除元素所造成的洞, 所以这样一来从中间删除元素就非常低效, 而pop(0)是所有操作中最低效的操作, 时间为O(n)

4.1.9 复制列表

如果想复制一个列表, 通常要通过深拷贝才能只拷贝值而不是列表的reference, 除此之外, 通过切片可以拷贝一维列表的值, 但是无法拷贝二维列表的值

#一维列表
c=[1,2,3,4,5]
a=c[:]
c[2]=10
print(a)

#结果是:
>>> [1, 2, 3, 4, 5]


#二维列表
c=[1,2,[10,11],4,5]
a=c[:]
c[2].append(12)
print(a)

#结果是:
>>> [1, 2, [10, 11, 12], 4, 5]



#二维列表直接去掉列表, 不会对复制的列表有影响
c=[1,2,[10,11],4,5]
a=c[:]
c[2]=10
print(a)

#结果是
>>> [1, 2, [10, 11], 4, 5]

5. python中局部变量和全局变量

5.1 一些基本的容易混淆的例子

5.1.1 函数内函数对全局变量的调用

def aaa():
    def bbb():
        print(b)
    b=[0]
    for i in range(5):
        b[0]=i
        bbb()
aaa()

#结果是:
>>> [0]
    [1]
    [2]
    [3]
    [4]

6. 关于python类操作

6.1 万物皆对象, 函数内调用类

def wtf():
    class test_class():
        def __init__(self,a):
            self.a=a
        def get_a(self):
            return self.a

    aaa=test_class(5)
    print(aaa.get_a())
wtf()

#结果是:
>>> 5

6.2 类内import无效

class abc:
    import numpy as np
    def bcd(self):
        np.array([0,1,2,3])

a=abc()
a.bcd()

#结果是:
>>> Traceback (most recent call last):
    File "<string>", line 919, in <module>
    File "<string>", line 916, in bcd
  NameError: global name 'np' is not defined
  
class abc:
    def bcd(self):
        import numpy as np
        np.array([0,1,2,3])

a=abc()
a.bcd()

#结果正常


import numpy as np
class abc:
    def bcd(self):
        np.array([0,1,2,3])

a=abc()
a.bcd()

#结果正常

7. python中的数字

7.1 is和==的区别

It will return True if two variables point to the same object, == if the objects referred to by the variables are equal.

print(1000 is 10**3)
>>> False

print(1000 is 1000)
>>> True

print(1000==10**3)
>>> True

Python会储存那些小的integer, 对于大的不会储存:

a=b=0
while a is b:
  a+=1
  b+=1
print(a)

#结果是
>>> 257

也就是说python的cache只会储存到256, 之后的is判断就失效了

8. python中的逻辑运算

当连续的or或者and时, 如果最后结果是true的话, 返回最后一个为true的值, 而且如果碰到 a or b, 如果a不等于false, 那么b就不会被执行, 但如果是and则会接着执行下去

print( 0 or 2 and 3)
>>> 3

print( 1 or 2 and 3)
>>> 1

print( 1 and 2 and 3)
>>> 3

print( 0 or None and 3)
>>> None

9. python中typehint

当我们在团队开发代码时, 想让别人快速读懂代码, 可以用typehint来告诉对方输入函数的参数的类型和返回的类型, 如下代码只支持python3h

#函数参数及返回类型
# 指定参数的类型, 函数的返回类型, 以及局部变量的类型
def foo(a: str, b: str) -> str:
    c = None  # type:str
    return 'hi'

#普通变量
x = None # type: str
x= '' # 其实IDE已经能知道是str类型了


#类的成员字段
class Student:
    def __init__(self):
        self.name=None # type: str

#集合泛型
my_list=[] # type: list[Student]
my_map={} # type: dict[int,str]
my_list_2=None # type: Tuple[int,Tuple[int]]

#用typing来对list等进行提示类型
from typing import List, Dict, Tuple, List
Vector = List[float]
ConnectionOptions = Dict[str, str]
Address = Tuple[str, int]
Server = Tuple[Address, ConnectionOptions]

10. 嵌套类和嵌套函数

#如果在一个类中嵌套类, 嵌套类中仅仅能访问到全局变量, 而不能访问外部类中的变量, 而且, 嵌套类时自动调用的, 即不需要class A中显式调用B(), B类就自动调用了
c=7
class A:
    a=5
    print(a)
    class B:
        print(c)
        print(a)

A()
#结果是: 5 7 NameError: name 'a' is not defined

#如果在一个函数中嵌套类, 嵌套类中也能访问到函数的局部变量以及全局变量, 同样, 嵌套类也是自动调用的
c=7
def A():
    a=5
    print(a)
    class B:
        print(c)
        print(a)
A()
#结果是: 5 7 5

#如果在一个函数中嵌套函数, 嵌套函数即可访问到局部变量也可访问到全局变量, 不过需要注意的是, 此时函数不是自动调用的, 需要显式调用!!!!!
c=7
def A():
    a=5
    print(a)
    def B():
        print(c)
        print(a)
A()
#结果是5

#除此之外, 嵌套类或函数在当前外部类或函数外是不可见的, 类和函数都是这样
c=7
def C():
    def A():
        a=5
        print(a)
        def B():
            print(c)
            print(a)
    B()
C()
#结果是: NameError: global name 'B' is not defined


#补充一点, 在定义类的时候, 如下两种定义方式都不会报错
class A():
    pass

class B:
    pass

A()
B()

11. for和else连用

#for和else连用时, 当执行完for循环后会执行else中的语句, 如果从for循环中break出来了, 则不会执行else中的语句
for i in range(10):
    if i == 5:
        print 'found it! i = %s' % i
else:
    print 'not found it ...'

12. itertools

#itertools中的groupby
from itertools import groupby
a=[1,2,2,2,2,1,1,1,3,3,3,3,3,3,4,4,4,4,1,3,5,5,5,5,5,5,5,5,5,5,5,5,5]
for k,v in groupby(a):
    print(str(k)+":"+str(len(list(v))))

#结果是:
1:1
2:4
1:3
3:6
4:4
1:1
3:1
5:13



13. collections

#Counter
from collections import Counter
c=Counter()
c['a']+=1
c['b']=-1
c['c']=0
print(c)
del c['c']
print(c)

#很显然, 就算count降到0了, c中存在的元素也不会被消除, 要想消除元素只能del掉
Counter({'a': 1, 'c': 0, 'b': -1})
Counter({'a': 1, 'b': -1})

14. return中用and和or

#在return中, 如果是and, 那么假如第一个值为假, 返回第一个值; 否则无论第二个值是真是假都返回第二个值
def test():
    return ['a'] and []
print(test()) #结果是[]

def test():
    return [] and ['a']
print(test()) #结果是[]

def test():
    return ['c'] and ['a']
print(test()) #结果是['a']



##在return中, 如果是or, 那么假如第一个值为假, 返回第二个值; 否则无论第二个值是真是假都返回第一个值
def test():
    return ['c'] or ['a']
print(test()) #结果是['c']

def test():
    return [] or ['a']
print(test()) #结果是['a']

def test():
    return ['a'] or []
print(test()) #结果是['a']

相关文章

网友评论

      本文标题:关于Python的一些(trick?坑?)

      本文链接:https://www.haomeiwen.com/subject/pbisrqtx.html