python语言特性(数据结构、语法结构)
1. range和xrange
python2.x中,range和xrange有什么区别?
for i in range(0, 20)
for i in xrange(0, 20)
答:
两者都用于for循环
range一开始就生成完整的list;xrange返回一个xrange object,且这个对象是iterable,要想返回list,需要类型转换list(xrange(1, 4))
由于xrange object是按需生成单个元素,而不是像range那样创建整个list。实际上,xrange由于是在循环中被调用时才会生成元素,因此无论循环多少次,只有当前一个元素占用了内存空间,且每次循环占用的都是相同的单个元素空间。因此,我们可以粗略认为,相同n个元素,range占用的空间是xrange的n倍。因此,在循环很大的情况下,xrange的高效率和快速将表现的很明显:占用的内存空间将更小,速度更快,内存性能更好。测试一下range和xrange的执行时间。
# python2环境下执行
import timeit
print(timeit.timeit('for i in range(10000000): pass',number=1))
# 0.49290895462036133
print(timeit.timeit('for i in xrange(10000000): pass',number=1))
# 0.2595210075378418
在大量循环的条件下,可以看到xrange的高效率是很明显的。
- 在python3中,移除了range的实现,保留了xrange的实现,并将xrange重新命名成range
dir(range(1, 4))
list(range(1, 4)) # range对象类型转换成list
range object在Python3里增加了新的attributes,’count’, ‘index’, ‘start’, ‘step’, ‘stop’,且能支持slicing。Python 3的range()在xrange()的基础上变得更强大了。
2. 迭代器和生成器
2.1 定义
2.2 生成器的创建
问: 将列表生成式中[]改成()之后,数据结构是否改变?
答:是,从列表变为生成器
L = [x*x for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
g = (x*x for x in range(10))
g
# <generator object <genexpr> at 0x11073bfc0>
通过列表生成式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含百万元素的列表,不仅占用很大的内存空间,如果我们只需要访问前面的几个元素,后面大部分元素所占的空间都是浪费的。因此,我们没必要创建完整的列表(节省大量内存空间)。在Python中,我们可以采用生成器generator,边循环边计算的机制。
3. 协程
简单点说,协程是进程和线程的升级版。进程和线程都面临着内核态和用户态切换的问题,而耗费许多切换时间;而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态。
Python里最常见的yield就是协程的思想,可以查看生成器-yield那一节。
4. lambda表达式
lambda一个匿名函数,与后面的函数式编程联系紧密。知乎-lambda
5. 函数式编程(python内置高阶函数reduce、map、filter)
- filter函数的功能相当于过滤器。调用一个布尔函数bool_func来遍历seq中的每个元素,返回一个使bool_seq返回值为true的元素序列。
a = [1,2,3,4,5,6,7]
b = filter(lambda x: x > 5, a)
print(b)
# [6,7]
- map是对一个序列的每个项依次执行函数,下面是对一个序列每个项都乘以2:
a = map(lambda x:x*2, [1,2,3])
list(a)
# [2, 4, 6]
- reduce是对一个序列的相邻项两两调用函数。下面是求3的阶乘:
reduce(lambda x,y:x*y, range(1,4))
# 6
6. Python里的浅拷贝和深拷贝
赋值(引用),copy(),deepcopy()的区别
import copy
a = [1, 2, 3, 4, ['a', 'b']] # 创建原始对象
b = a # 赋值,对内存中对象的引用
c = copy.copy(a) # 对象拷贝,浅拷贝
d = copy.deepcopy(a) # 对象拷贝,深拷贝
a.append(5) # 修改对象a
a[4].append('c') # 修改对象a中的['a', 'b']数组对象
print('a = ', a)
print('b = ', b)
print('c = ', c)
print('d = ', d)
'''
输出结果:
a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c = [1, 2, 3, 4, ['a', 'b', 'c']]
d = [1, 2, 3, 4, ['a', 'b']]
'''
7. Python2和Python3的区别
8. is,is not, ==, =
is、is not,对比内存地址(id)。
==,对比值,包括数值和布尔值。
=,赋值。
9. 闭包
闭包(closure)是函数式编程重要的语法结构。闭包是一种代码的组织结构,它同样提高了代码的复用性。
内层函数引用了其外部作用域的变量(参数),然后返回内层函数的情况,称为闭包(Closure)。创建一个闭包必须满足以下几点:
- 必须有一个内嵌函数
- 内嵌函数必须引用外部函数中的变量,外层空间中被引用的变量叫做内层函数的环境变量
- 外部函数的返回值必须是内嵌函数
- 环境变量和内层非全局函数一起构成了闭包
函数运行后并不会被撤销:当函数运行完后,并不被销毁,而是继续留在内存空间里。这个功能类似类里的类变量,只不过迁移到了函数上。
# 简单理解闭包:内层函数会记住其外层作用域的事实,即内层函数会记住外层函数的变量
nums_in_global=[15,2,3,9,3.2] # 声明一个全局变量
def foo1(nums_in_function): # 设计一个外层函数
print('nums_in_function此时在foo1中,可以被访问:', nums_in_function)
def foo2():
return max(nums_in_function)
#return max(nums_in_global)
return foo2
foo1([5,3,8])()
# print(nums_in_function)
闭包就像个空心球一样,你知道外面和里面,但你不知道中间是什么样。
10. Python的各种推导式(列表推导式、字典推导式、集合推导式)
http://blog.youkuaiyun.com/yjk13703623757/article/details/79490476
11. Python自省
自省就是面向对象的语言所写的程序在运行时,就能知道对象的类型。也就是程序运行时能够获得对象的类型。比如type(),dir(),getattr(),hasattr(),isinstance()。
a = [1, 2, 3]
b = {'a':1, 'b':2, 'c':3}
c = True
print(type(a), type(b), type(c))
print(isinstance(a, list))
# <class 'list'> <class 'dict'> <class 'bool'>
# True
12. 字符串格式化:%和.format
对于%,最烦人的是它无法同时传递一个变量和元组。你可能会想下面的代码不会有什么问题:
name = 123
print("hi there %s" %name)
但是,如果name恰好是元祖(1,2,3),它将会抛出一个TypeError异常。
name = (1,2,3)
print("hi there %s" %name)
# TypeError: not all arguments converted during string formatting
为了保证它总是正确的,你必须如下做,但是有点丑。
name = (1,2,3)
print("hi there %s" %(name, )) # 提供一个单元素的元组而不是一个参数
.format在许多方面看起来更便利,它没有这些问题,format好看多了。
name = (1,2,3)
print("hi there {}".format(name))
format的具体用法:python之字符串格式化format
13. GIL全局解释器锁
线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取独立线程运行的限制。即在同一时间,一个CPU核只能运行一个线程。对于IO密集型任务,python的多线程起作用;但对于CPU密集型任务,python的多线程几乎占不到任何优势,还有可能因为互相争夺资源而变慢。见Python最难的问题。
解决办法:多进程和协程(协程也只是单核CPU,但是能减小切换代价提升性能)。
14. Python中的作用域
在Python中,一个变量的作用域总是由代码中被赋值的地方所决定的。
当遇到一个变量,Python会按照下面的顺序进行搜索:本地作用域(Local)→ 当前作用域被嵌入的本地作用域(Enclosing locals)→ 全局/模块作用域(Global)→ 内置作用域(Built-in)
15. Python中read,readline,readlines的区别
- read,读取整个文件
- readline,读取下一行,使用生成器方法
- readlines,读取整个文件到一个迭代器以供我们遍历
详见:http://blog.youkuaiyun.com/yjk13703623757/article/details/79502998
16. Python中单下划线和双下划线的区别
class MyClass():
def __init__(self):
self.__superprivate = "Hello"
self._semiprivate = ", world!"
mc = MyClass()
print(mc.__dict__)
# {'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}
print(mc._semiprivate)
# , world!
#print(mc.__superprivate)
# AttributeError: 'MyClass' object has no attribute '__superprivate'
print(mc._MyClass__superprivate)
# Hello
__foo__
:一种约定。Python内部的名字,用来区别用户自定义的其他命名,以防冲突。例如__init__()
,__del__()
,__call__()
这些特殊方法。_foo
:一种约定。程序员用来指定私有变量的一种方式,不能用from module import *
导入,其他方面和公有变量的一样。__foo
:这个有真正的意义。python解释器用_classname__foo
来代替这个名字,以区别和其他类相同的命名。它无法直接像公有成员一样随便访问,而是通过对象名._类名__方法
这样的方式访问。
详见:
17. *args,**kwargs参数是什么?
使用用*args
和**kwargs
是为了方便,而不是必须要使用它们。
- 当不确定你的函数里将要传递多少参数时,你可以使用位置参数包裹
*args
,它可以传递任意数量的位置参数
def print_everything(*args):
for count, thing in enumerate(args):
print('{0}. {1}'.format(count, thing))
print_everything('apple', 'banana', 'cabbage')
'''
0. apple
1. banana
2. cabbage
'''
- 同理,关键字参数包裹
**kwargs
允许你使用没有事先定义的关键字参数
def table_things(**kwargs):
for name, value in kwargs.items():
print('{0} = {1}'.format(name, value))
table_things(apple = 'fruit', cabbage = 'vegetable')
'''
apple = fruit
cabbage = vegetable
'''
- 位置参数、关键字参数、位置参数包裹、关键字参数包裹的混合使用
位置参数首先获得参数值,然后其他所有的参数都传递给*args
和**kwargs
。位置参数在参数的最前端,*args
和**kwargs
可以同时在传参时使用,但是*args
必须在**kwargs
前面。
func(positional_args, keyword_args, *tuple_nonkw_args, **dict_kw_args)
'''
positional_args:位置参数
keyword_args:关键字参数
*tuple_nonkw_args:非关键字不定长参数
**dict_kw_args:关键字不定长参数
'''
- 调用函数时,我们也可以使用
*
和**
语法
def print_three_things(a, b, c):
print('a = {0}, b = {1}, c = {2}'.format(a, b, c))
mylist = ['aardvark', 'baboon', 'cat']
print_three_things(*mylist)
# a = aardvark, b = baboon, c = cat
*
可以传递列表(或者元组)的每一项并把它们解包,这必须与它们在函数里的参数相吻合。
stackoverflow上的解释:https://stackoverflow.com/questions/3394835/args-and-kwargs
18. Python中类变量和实例变量的区别
- 类变量:在类的所有实例之间共享的变量值(也就是说,类变量不是单独分配给1个实例的)。
class Test(object):
num_of_instance = 0
def __init__(self, name):
self.name = name
Test.num_of_instance += 1
if __name__ == '__main__':
print(Test.num_of_instance) # 类变量0
t1 = Test('jack')
print(Test.num_of_instance) # 1
t2 = Test('lucy')
print(t1.name, t1.num_of_instance) # jack 2
print(t2.name, t2.num_of_instance) # lucy 2
# num_of_instance就是类变量,用于跟踪存在着多少个Test的实例。
- 实例变量:类实例化之后,实例单独拥有的变量。
class Person():
name="aaa"
p1=Person()
p1.name="bbb"
print(p1.name) # 修改实例p1的变量name值,更新为bbb
print(Person.name) # 类变量name的值为aaa
p1.name="bbb"
是实例调用了类变量,也就是函数传参的问题,p1.name
一开始是指向的类变量name="aaa"
,后来改变了实例作用域里的类变量引用,p1.name
就变成了一个实例变量,self.name
不再引用Person的类变量name了。
下面是类变量引用实例作用域里的实例变量。
class Person:
name=[]
p1=Person()
p2=Person()
print(p1.name)
p1.name.append(1)
print(p1.name) # [1]
print(p2.name) # [1]
print(Person.name) # [1]
p1.name.append(2)
print(p1.name) # [1, 2]
print(p2.name) # [1, 2]
print(Person.name) # [1, 2]