Python进阶细节

Python进阶细节

根据慕课网七月老师视频整理html

一切皆对象

对与Python来讲,一切皆对象,包括函数。在其余语言好比c++中,函数只是一段可执行的代码,只要你得到入口地址就能够调用这段代码。可是Python中不同,Python中一切皆对象。Python中的函数,能够做为另外一个函数的参数传入到另一个函数里,也能够看成另一个函数的返回值,甚至能够赋值给一个变量。python

def a():
    pass

print(type(a))


<class 'function'>   # 可见是一个class,是一个类。

闭包

闭包指的是:函数+环境变量(环境变量不能是全局变量)。
python在函数内部还能够定义函数,但该函数做用域只在外部函数内部有效,除非做为外部函数的返回值被返回,在外部函数的外部用一个变量接收后就能够调用它了。c++

def curve_pre():
    a = 25  # 这里定义了环境变量
    def curve(x):
        return a*x*x
    return curve

a = 10   # 在外部定义了a为10
f = curve_pre()
print(f(2))
print(f.__closure__)    # 能够经过这个内置变量来查看闭包里的内容
print(f.__closure__[0].cell_contents)


100
(<cell at 0x0000016832868708: int object at 0x0000000065DCECD0>,)
25

可见返回了一个对象。你再在外面定义了变量,也不会改变闭包内的环境变量。
闭包的意义在于返回了一个现场,若是没有闭包,很容易被外部的变量所影响。算法

1. 闭包的经典误区

闭包返回现场的环境变量,不能在闭包里定义的函数里面再被定义了,并且函数里必需要有调用环境变量的地方,不然就不叫作闭包了。express

def f1():
    a = 20
    def f2():
        a = 10  # 重复定义
        return a
    return f2
f = f1()
print(f.__closure__)

None  # 可见此时返回了None,再也不是闭包了。本质上是认为此时a被认为是一个局部变量,再也不是环境变量了!

--------------------------------------------------------------------------

# 若是想要环境变量在函数里被改变,能够这样:

def f1():
    a = 25
    def f2():
        nonlocal a  # nonlocal关键字强制让a再也不为局部变量,跳到上一级做为了环境变量。
        a = a + 10
        return a
    return f2
f = f1()
print(f.__closure__)
print(f())
print(f())
print(f())

(<cell at 0x000002A5CF348708: int object at 0x0000000065DCECD0>,)
35
45
55
# 能够看到a的值是能够保存的,这是由于闭包的环境变量具备保存现场的功能,记忆住上次调用的状态,因此能够这样作。

---------------------------------------------------------------------------


def f1():
    a = 20
    def f2():
        return 2  # 里面再也不调用a了
    return f2
f = f1()
print(f.__closure__)

None # 可见此时仍然不是闭包

---------------------------------------------------------------------------

def f1():
    a = 20
    def f2():
        s = a+20
        return 2
    return f2
f = f1()
print(f.__closure__)

(<cell at 0x00000294E8568708: int object at 0x0000000065DCEC30>,)
# 可见就算返回值里不包括a,可是只要调用了,就能够是一个闭包。

2. 闭包的优势

从闭包能够看出函数式编程的优势。若是出现须要保存值进行迭代的状况,就不得不定义一个全局变量来保存上一次的值。可是在闭包里不须要使用到全局变量,只须要闭包里定义的环境变量便可记忆上一次的状态,这样就具备了封闭性,不然过多的使用全局变量会使代码变得混乱。这里再注意一个问题:编程

a = 10

def f1(x):
    a_new = a + x
    a = a_new

print(f1(5))

Traceback (most recent call last):
  File "c4.py", line 7, in <module>
    print(f1(5))
  File "c4.py", line 4, in f1
    a_new = a + x
UnboundLocalError: local variable 'a' referenced before assignment

# 看起来美滋滋其实报错了。再Python里,若是再定义了a的话,不管是在哪里,在定义的时候系统会默认a是局部变量再也不是全局变量了。因此在执行代码的时候,就会出现了找不到局部变量a的状况,由于f1中第一段代码中用到了局部变量a,可是此时尚未定义啊。

----------------------------------------------------------------------

# 能够这么解决:
a = 10

def f1(x):
    global a   # 定义global关键字强制认为是全局变量
    a_new = a + x
    a = a_new
    return a

print(f1(5))
print(f1(10))

15
25

# 可见这时候全局变量起到了保存的功能,但相对闭包,就显得很Low了。

3. 闭包的一个经典例子

def testFun():  
    temp = [lambda x : i*x for i in range(4)]
    return temp

for everyLambda in testFun(): 
    print (everyLambda(2))
    
# 运行后结果居然是:    
6
6
6
6

这里testfun()返回一个temp,temp是一个列表,everyLambda每次返回的都是temp里列表的值,参数2赋给x。数据结构

三元表达式

python中的三元表达式和其余语言中的不太同样。
条件为真时返回的结果 if 判断条件 else 条件为假时返回的结果
x if x > y else y
其余不少语言中是这么定义的:
x > y ? x:y闭包

map类

map不是一个函数而是一个类。map和lambda表示结合起来一块儿用会很是好。
map(func, *iterables) --> map object
iterables是可迭代的类型,序列和元组均可以。*号表示是可变参数,能够传入多个不一样值的取值。app

a = [1,2,3,4,5]
def square(x):
    return x*x
r = map(square, a)
print(r)
print(list(r))


<map object at 0x000001DC6AD0B0F0>
[1, 4, 9, 16, 25]

匿名函数(lambda表达式)

lambda表达式也叫作匿名函数。
lambda的定义:lambda parameter_list: expression
expression意思是表达式,因此后面只能是表达式,不能是语句,好比赋值语句等是不能够的。
lambda表达式最后返回的是表达式计算出的值,和return后是同样的。函数式编程

def add(x, y):
    return x+y

lambda x, y : x + y

lambda表达式通常和三元表达式和map链接在一块儿用会更加整洁。

x = 1,2,3,4,5,6
y = 1,2,3,4,5,6
r = map(lambda x,y:x+y, x,y)
print(tuple(r))


(2, 4, 6, 8, 10, 12)

reduce函数

reduce 是一个函数。
def reduce(function, sequence, initial=None)

  • function : 这里须要注意函数必须且只能有两个参数
  • sequence: 序列
  • initial: 初始值
  • reduce的含义是连续调用函数进行连续计算
from functools import reduce # 须要引入这个函数才可使用
list_x = ['1','2','3']
r = reduce(lambda x,y:x+y, list_x, 'a')
print(r)

a123
# 执行状况 :(('a'+'1')+'2')+'3'
# 每次执行完一次函数的结果在下一次调用函数的时候会传入到函数的参数中进行计算。初始值是给出的开始的参数之一。

filter类

class filter(function or None, iterable)
表示过滤不符合条件的值。当函数返回为True时保留,False时剔除。当函数为None时,剔除调iterable中原本就为False的值

# ord()返回ascII码值

list_x = ['a', 'B', 'c', 'D']
r = filter(lambda x: False if ord(x)>64 and ord(x)<91 else True, list_x)
print(list(r))


['a', 'c']

装饰器

编写代码一个原则是:对修改是封闭的,对拓展是开放的。
若是想在不少个函数里,每一个函数都实现相同的功能,用装饰器是最方便的,不用在每一个函数里重复定义,并且调用起来很方便,和“装饰”的意思很像。

import time  

def decorator(func):
    def wrapper(*args, **kw): # *args是可变参数,**kw是可变关键字参数,这样能够接受除了有默认参数类型之外全部类型的函数参数
        print(time.time())
        func(*args, **kw)
    return wrapper
 # 就是一个闭包,传入的函数func是环境变量

@decorator  # @ 是一个语法糖
def f1(func_name):  
    print("this is f1" + func_name)

@decorator
def f2(func_name1, func_name2):
    print("this is f2" + func_name1 + func_name2)

@decorator
def f3(func_name1, func_name2='f3',*args, **kw):
    print("this is f3"+func_name1+func_name2)
    print(kw)
    
f1('f1')   # 可见虽然在定义的时候麻烦了一些,可是调用的时候很方便。
f2('f2','f2')
f3('f3','f3',a='1',b='2',c='3') # 可变关键字参数


1519276076.973657  #时间戳
this is f1f1
1519276076.9746575
this is f2f2f2
1519276076.9746575
this is f3f3f3
{'a': '1', 'b': '2', 'c': '3'}

生成器

经过列表生成式能够直接建立一个列表,可是咱们若是只想访问前面几个元素,不想利用后面的元素,咱们能够定义一个生成器(generator),一边循环一边计算,有一种方法很简单,只要把列表生成式的[]改成()就能够建立一个generator.

L = [x * x for x in range(10)]
L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]


g = (x * x for x in range(10))
g
<generator object <genexpr> at 0x104feab40>

若是须要打印出来元素,能够经过生成器的next()方法。

g.next()
0
g.next()
1
g.next()
4

generator是能够迭代的:

g = (x * x for x in range(10))
for n in g:
...     print n
...
0
1
4
9

能够把一个函数写成生成器,把return改成yield

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
        
fib(6)
<generator object fib at 0x104feaaa0>

for n in fib(6):
...     print n
...
1
1
2
3
5
8

定义成生成器后,generator的执行流程和函数并不同,函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

另一个例子:

def odd():
...     print 'step 1'
...     yield 1
...     print 'step 2'
...     yield 3
...     print 'step 3'
...     yield 5
...
o = odd()
o.next()
step 1
1
o.next()
step 2
3
o.next()
step 3
5
o.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

字典来代替swich

Python中没有swich这种结构,能够用字典来实现。

day = 0

def get_monday():
    return 'Monday'

def get_Tuseday():
    return 'Tuseday'

def get_Wednesday():
    return 'Wednesday'

def default():
    return 'Unknow'

#返回函数能够定义跟多操做,也能够直接返回值
swicher = {
    0: get_monday,
    1: get_Tuseday,
    2: get_Wednesday
}

# 字典的get方法能够获取键对应的值,若是键不存在则返回指定的默认值

day_time = swicher.get(day, default())() # 后面再加一个括号来调用函数

print(day_time)


monday

列表推导式

对于列表推导式,其实不仅是列表,也能够是元组,集合,字典进行推倒

a = [1,2,3,4,5]

b = [i**2 for i in a if i>=3]

print(b)


[9,16,25]
---------------------------------------------------

# 对于字典能够这样:

sdict = {
    'q':'烈焰冲击',
    'w':'天女散花',
    'e':'致命一击'
}

dic = {value:key for key,value in sdict.items()} # 最外面加花括号就是字典或者集合
dic1 = [key for key,value in sdict.items()] 
dic2 = (key for key,value in sdict.items()) 
print(dic)
print(dic1)
print(dic2)

# 若是是元组,元组是不可遍历的对象,因此须要下面这样取出对象
for i in dic2:
    print(i,end='\\ ')


{'烈焰冲击': 'q', '天女散花': 'w', '致命一击': 'e'}
['q', 'w', 'e']
<generator object <genexpr> at 0x0000021A3430E150>
q\ w\ e\

None

None表明空,并非False,也不是[],''

print(type(None))
print(type(False))
print(type([]))
print(type(''))

<class 'NoneType'>
<class 'bool'>
<class 'list'>
<class 'str'>

咱们能够看见,这些在本质上都是不同的,类型都不同,只不过咱们在进行逻辑判断的时候,有时会像下面这样作:

a = None/false/[]/''
if not a:
.....

# 判断的时候会这样作

不建议使用if a is None:这种语句,咱们能够看到类型是不同的,有时会出错。

对象存在不必定是True

在上面对None的分析中,在逻辑判断的时候之因此能够判断None为False,是由于每一个对象和bool类型之间都是有联系的,因此能够进行逻辑判断。可是咱们自定义的对象却不必定了,返回True和False因不一样的对象而不一样。咱们自定义的对象,如类,和咱们的内置方法有关系。
类中有两个内置方法会影响对类的布尔类型的取值:

  • __len__:这个内置方法返回的是类的长度,外部调用len()时会返回该方法的返回值,返回值只有布尔类型或者int类型。
  • __bool__:这个内置方法返回的类的bool类型的取值,当这个方法在类里面定义之后,返回值只看其返回值,__len__的返回值再也不起做用。注意该方法的返回值只能是布尔类型,即True或False。
class Rest():
    def __len__(self):
        return 5
    def __bool__(self):
        return False

print(len(Rest()))
print(bool(Rest()))

5
False

装饰器的反作用

加上装饰器后会改变函数的名字。

def decorator(func):
    def wrapper():
        print('this is decorator')
        func()
    return wrapper
@decorator
def f1():
    print(f1.__name__)  # 打印函数名字,不加装饰器是f1
f1()


this is decorator
wrapper

可见会出错,加上装饰器后,若是不想改变名字,能够这样作:

from functools import wraps


def decorator(func):
    @wraps(func)
    def wrapper():
        print('this is decorator')
        func()
    return wrapper

@decorator
def f1():
    print(f1.__name__)  # 打印函数名字,不加装饰器是f1

f1()



this is decorator
f1

可哈希(hashable)对象和不可变性(immutable)

  • 可哈希(hashable)和不可改变性(immutable)

若是一个对象在本身的生命周期中有一哈希值(hash value)是不可改变的,那么它就是可哈希的(hashable)的,由于这些数据结构内置了哈希值,每一个可哈希的对象都内置了__hash__方法,因此可哈希的对象能够经过哈希值进行对比,也能够做为字典的键值和做为set函数的参数,可是list是unhashable,因此不能够做为字典的键值。全部python中全部不可改变的的对象(imutable objects)都是可哈希的,好比字符串,元组,也就是说可改变的容器如字典,列表不可哈希(unhashable)。咱们用户所定义的类的实例对象默认是可哈希的(hashable),它们都是惟一的,而hash值也就是它们的id()

  • 哈希

它是一个将大致量数据转化为很小数据的过程,甚至能够仅仅是一个数字,以便咱们能够用在固定的时间复杂度下查询它,因此,哈希对高效的算法和数据结构很重要。

  • 不可改变性

它指一些对象在被建立以后不会由于某些方式改变,特别是针对任何能够改变哈希对象的哈希值的方式

  • 联系

由于哈希键必定是不可改变的,因此它们对应的哈希值也不改变。若是容许它们改变,,那么它们在数据结构如哈希表中的存储位置也会改变,所以会与哈希的概念违背,效率会大打折扣

具体的咱们能够参考官方文档和如下博客:
关于python内置__eq__函数的探索
关于可哈希对象的理解

python的深拷贝和浅拷贝

1.赋值方法:

list1 = [1,2,3]
list2 = list1
print(id(list1),id(list2))

2577180416904 2577180416904

可见这和在c语言中不同,两者的id是同样的,换句话说,你改变 list2,同时也会改变 list1
2.浅拷贝:

import copy

list1 = [1,2,3]
list2 = copy.copy(list1)
print(id(list1),id(list2))
list2.append(4)
print(list1,list2)


2522465131400 2522465130824
[1, 2, 3] [1, 2, 3, 4]

可是浅拷贝,对于里面的元素,若是是不可变类型,会直接拷贝,可是对于可变类型只是指向它而已,例如看下面的代码:

import copy

list1 = [1,2,[1,2,3]]
list2 = copy.copy(list1)
print(id(list1),id(list2))

list2[2].append(4)

print(list1,list2)

2710366738760 2710368236680
[1, 2, [1, 2, 3, 4]] [1, 2, [1, 2, 3, 4]] # 都变化了

3.深拷贝

import copy


list1 = [1,2,[1,2,3]]
list2 = copy.deepcopy(list1)
print(id(list1),id(list2))

list2[2].append(4)

print(list1,list2)

1660389185864 1660390683784
[1, 2, [1, 2, 3]] [1, 2, [1, 2, 3, 4]]

可见深拷贝才是真正的拷贝。

相关文章
相关标签/搜索