Python中pass语句的作用是什么?
pass语句不会执行任何操作,一般作为占位符或者创建占位程序
Python是如何进行类型转换的?
Python提供了将变量或值从一种类型转换成另一种类型的内置函数。比如int函数能够将符合数学格式数字型字符串转换成整数。否则,返回错误信息。
Python是如何进行内存管理的?
Python引用了一个内存池(memory pool)机制,即Pymalloc机制(malloc:n.分配内存),用于管理对小块内存的申请和释放。
is和==有什么区别?
is是对比地址,==是对比值
is:通过对比两个对象的内存地址,判断是否为同一个实例对象(id():获取对象的内存地址)。
== : 比较的两个对象的内容/值是否相等,默认会调用对象的eq()方法
is是用来判断两个变量引用的对象是否为同一个,==用于判断引用对象的值是否相等。可以通过id()函数查看引用对象的地址。
dict 的 items() 方法与 iteritems() 方法的不同?
items以列表形式返回可遍历的(键, 值) 元组数组;iteritems返回值不是列表,而是一个迭代器
read,readline和readlines
python中内置的数据结构有几种?
可变类型和不可变类型
Python中类实例方法、类方法、静态方法有何区别?
实例方法: 是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身;
类方法: 是类对象的方法,在定义时需要在上方使用 @classmethod 进行修饰,形参为cls,通过类名. 调用类方法,在方法内部,可以通过 cls.
访问类的属性和方法
静态方法: 任意函数上方使用 @staticmethod进行装饰。既不需要访问实例属性或者调用实例方法,也不需要访问类属性或者调用类方法,可以把这个方法封装成一个静态方法;通过类名.调用静态方法
简述__new__和__init__的区别
__new__
方法会返回一个创建的实例,而__init__
什么都不返回__new__
返回一个cls的实例时后面的__init__
才能被调用__new__
必须以cls作为第一个参数,__init__
以self作为其第一个参数总结:__new__方法在__init__方法之前被调用,并且__new__方法的返回值将传递给__init__方法作为第一个参数,最后__init__给这个实例设置一些参数。
鸭子类型
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为鸭的对象,并调用它的走和叫方法。在使用鸭子类型的语言中,这样的一个函数可以接受任何拥有这样的正确的走和叫方法的对象,并调用它的走和叫方法。
如list.extend()方法中,我们并不关心它的参数是不是list,只要它是可迭代的我们都可以使用(list/tuple/dict/字符串/生成器等)
什么是匿名函数?它有什么好处?
匿名函数(lambda函数)一般只有一行,而不是一个代码块,函数体比def简单很多。仅仅能在lambda表达式中封装有限的逻辑进去。
lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。
定义lambda函数的形式如下:labmda 参数:表达式
lambda函数默认返回表达式的值。你也可以将其赋值给一个变量。lambda函数可以接受任意个参数,包括可选参数,但是表达式只有一个。
如何捕获异常,常用的异常机制有哪些?
如果我们没有对异常进行任何预防,那么在程序执行的过程中发生异常,就会中断程序,调用python默认的异常处理器,并在终端输出异常信息。
try…except…finally语句:当try语句执行时发生异常,回到try语句层,寻找后面是否有except语句。找到except语句后,会调用这个自定义的异常处理器。except将异常处理完毕后,程序继续往下执行。finally语句表示,无论异常发生与否,finally中的语句都要执行。
assert语句:判断assert后面紧跟的语句是True还是False,如果是True则继续执行print,如果是False则中断程序,调用默认的异常处理器,同时输出assert语句逗号后面的提示信息。
with语句:使用with语句,文件会在with代码块结束后自动关闭;如果with语句或语句块中发生异常,会调用默认的异常处理器处理,但文件还是会正常关闭。
try:
可能出现异常的语句
except:
当异常出现时如何处理
finally:
无论异常是否发生都必须执行的语句
有用过with statement吗?它的好处是什么?具体如何实现?
with语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。
简述Python的作用域以及Python搜索变量的顺序
Python作用域简单说就是一个变量的命名空间。代码中变量被赋值的位置,就决定了哪些范围的对象可以访问这个变量,这个范围就是变量的作用域。在Python中,只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域。
Python的变量名解析机制也称为 LEGB 法则:
L: local 函数内部作用域
E: enclosing 函数内部与内嵌函数之间
Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。
当 Python 遇到一个变量的话他会按照这样的顺序进行搜索:
本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局/模块作用域(Global)→内置作用域(Built-in)
新式类和旧式类的区别,如何确保使用的类是新式类
为了统一类(class)和类型(type),python在2.2版本引进来新式类。在2.1版本中,类和类型是不同的。
为了确保使用的是新式类,有以下方法:
放在类模块代码的最前面 __metaclass__ = type
从内建类object直接或者间接地继承
a. 在python里凡是继承了object的类,都是新式类
b. 在python3中,默认所有的类都是新式类。
c. Python2里面继承object的是新式类,没有写父类的是经典类
d. 经典类目前在Python里基本没有应用
新式类很早在2.2就出现了,所以旧式类完全是兼容的问题,Python3里的类全部都是新式类.这里有一个MRO问题可以了解下(新式类是广度优先,旧式类是深度优先),
一个旧式类的深度优先的例子
class A():
def foo1(self):
print "A"
class B(A):
def foo2(self):
pass
class C(A):
def foo1(self):
print "C"
class D(B, C):
pass
d = D()
d.foo1()
# A
按照经典类的查找顺序从左到右深度优先
的规则,在访问d.foo1()
的时候,D这个类是没有的..那么往上查找,先找到B,里面没有,深度优先,访问A,找到了foo1(),所以这时候调用的是A的foo1(),从而导致C重写的foo1()被绕过
Python其实有3个方法,即静态方法(staticmethod),类方法(classmethod)和实例方法,如下:
def foo(x):
print "executing foo(%s)"%(x)
class A(object):
def foo(self,x):
print "executing foo(%s,%s)"%(self,x)
@classmethod
def class_foo(cls,x):
print "executing class_foo(%s,%s)"%(cls,x)
@staticmethod
def static_foo(x):
print "executing static_foo(%s)"%x
a=A()
这里先理解下函数参数里面的self和cls.这个self和cls是对类或者实例的绑定,对于一般的函数来说我们可以这么调用foo(x)
,这个函数就是最常用的,它的工作跟任何东西(类,实例)无关.对于实例方法,我们知道在类里每次定义方法的时候都需要绑定这个实例,就是foo(self, x)
,为什么要这么做呢?因为实例方法的调用离不开实例,我们需要把实例自己传给函数,调用的时候是这样的a.foo(x)
(其实是foo(a, x)
).类方法一样,只不过它传递的是类而不是实例,A.class_foo(x)
.注意这里的self和cls可以替换别的参数,但是python的约定是这俩,还是不要改的好.
对于静态方法其实和普通的方法一样,不需要对谁进行绑定,唯一的区别是调用的时候需要使用a.static_foo(x)
或者A.static_foo(x)
来调用.
\ | 实例方法 | 类方法 | 静态方法 |
---|---|---|---|
a = A() | a.foo(x) | a.class_foo(x) | a.static_foo(x) |
A | 不可用 | A.class_foo(x) | A.static_foo(x) |
类变量:
是可在类的所有实例之间共享的值(也就是说,它们不是单独分配给每个实例的)。例如下例中,num_of_instance 就是类变量,用于跟踪存在着多少个Test 的实例。
实例变量:
实例化之后,每个实例单独拥有的变量。
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
补充的例子
class Person:
name="aaa"
p1=Person()
p2=Person()
p1.name="bbb"
print p1.name # bbb
print p2.name # aaa
print Person.name # aaa
这里p1.name="bbb"
是实例调用了类变量,这其实和上面第一个问题一样,就是函数传参的问题,p1.name
一开始是指向的类变量name="aaa"
,但是在实例的作用域里把类变量的引用改变了,就变成了一个实例变量,self.name不再引用Person的类变量name了.
可以看看下面的例子:
class Person:
name=[]
p1=Person()
p2=Person()
p1.name.append(1)
print p1.name # [1]
print p2.name # [1]
print Person.name # [1]
参考:http://stackoverflow.com/questions/6470428/catch-multiple-exceptions-in-one-line-except-block
这个也是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) # True
说一说Python自省。
自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型。简单一句话就是运行时能够获得对象的类型。比如:type()、dir()、getattr()、hasattr()、isinstance()
想要完整的理解Python自省,请点击:Python自省(反射)指南
可能你见过列表推导时,却没有见过字典推导式,在2.7中才加入的:
d = {key: value for (key, value) in iterable}
>>> class MyClass():
... def __init__(self):
... self.__superprivate = "Hello"
... self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
File "", line 1, in
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}
__foo__
:一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突,就是例如__init__()
,__del__()
,__call__()
这些特殊方法
_foo
:一种约定,用来指定变量私有.程序员用来指定私有变量的一种方式.不能用from module import * 导入,其他方面和公有一样访问;
__foo
:这个有真正的意义:解析器用_classname__foo
来代替这个名字,以区别和其他类相同的命名,它无法直接像公有成员一样随便访问,通过对象名._类名__xxx这样的方式可以访问.
或者: http://www.zhihu.com/question/19754941
.format在许多方面看起来更便利.对于%
最烦人的是它无法同时传递一个变量和元组.你可能会想下面的代码不会有什么问题:
"hi there %s" % name
但是,如果name恰好是(1,2,3),它将会抛出一个TypeError异常.为了保证它总是正确的,你必须这样做:
"hi there %s" % (name,) # 提供一个单元素的数组而不是一个参数
但是有点丑..format就没有这些问题.你给的第二个问题也是这样,.format好看多了.
你为什么不用它?
不知道它(在读这个之前)
为了和Python2.5兼容(譬如logging库建议使用%
(issue #4))
http://stackoverflow.com/questions/5082452/python-string-formatting-vs-format
答:如果我们不确定往一个函数中传入多少参数,或者我们希望以元组(tuple)或者列表(list)的形式传参数的时候,我们可以使用*args(单星号)。如果我们不知道往函数中传递多少个关键词参数或者想传入字典的值作为关键词参数的时候我们可以使用**kwargs(双星号),args、kwargs两个标识符是约定俗成的用法。
另一种答法:当函数的参数前面有一个星号*号的时候表示这是一个可变的位置参数,两个星号**表示这个是一个可变的关键词参数。星号*把序列或者集合解包(unpack)成位置参数,两个星号**把字典解包成关键词参数。
代码辅助理解:
*args and **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')
cabbage = vegetable
apple = fruit
你也可以混着用.命名参数首先获得参数值然后所有的其他参数都传递给*args
和**kwargs
.命名参数在列表的最前端.例如:
def table_things(titlestring, **kwargs)
*args
和**kwargs
可以同时在函数的定义中,但是*args
必须在**kwargs
前面.
当调用函数时你也可以用*
和**
语法.例如:
>>> 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
就像你看到的一样,它可以传递列表(或者元组)的每一项并把它们解包.注意必须与它们在函数里的参数相吻合.当然,你也可以在函数定义或者函数调用时用*.
re模块中match和search方法的不同?
match() 函数只检查 RE 是否在字符串开始处匹配,而search() 则是扫描整个字符串。
简述函数式编程
在函数式编程中,函数是基本单位,变量只是一个名称,而不是一个存储单元。除了匿名函数外,Python还使用fliter(),map(),reduce(),apply()函数来支持函数式编程。
python中函数式编程支持:
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
本篇文章我们来介绍下Python函数式编程的知识。最主要的一点,Python中的函数是对象,可以复制给变量!好了,我们来介绍几个Python函数式编程中的要点,包括高阶函数、匿名函数、装饰器、偏函数等等。精彩内容,不容错过!
函数本身也可以赋值给变量,即:变量可以指向函数。如果一个变量指向了一个函数,那么,可以通过该变量来调用这个函数。
f = abs
f(-10) # 10
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
def add_abs(x,y,f):
return f(x) + f(y)
f = abs
add_abs(-5,6,f) # 11
接下来,我们介绍几个重点的高阶函数。
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。我们之前介绍过了,Iterator是惰性序列,需要通过list()函数让它把返回结果变为list。
def f(x):
return x * x
r = list(map(f,[1,2,3,4,5,6,7,8,9,10]))
r
输出结果为:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算:
def f(x,y):
return x + y
reduce(f,[1,2,3,4,5]) # 15
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。filter()函数返回的是一个Iterator,也就是一个惰性序列,同样需要通过list()函数让它把返回结果变为list。
def is_odd(n):
return n%2==1
list(filter(is_odd,[1,2,3,4,5,6,7,8,9]))
结果为:
[1, 3, 5, 7, 9]
sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序或者按照小写字母顺序进行排序:
print(sorted([36,5,-12,9,-21],key=abs))
print(sorted(['bob', 'about', 'Zoo', 'Credit'],key=str.lower))
结果为:
[5, 9, -12, -21, 36]
['about', 'bob', 'Credit', 'Zoo']
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
我们来实现一个可变参数的求和,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:
def lazy_sum(*args):
def sum():
res = 0
for n in args:
res += n
return res
return sum
f = lazy_sum(1,3,5,7,9)
f()
在这个例子中,我们在函数lazy_sum中又定义了函数sum,
并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,
当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,
这种称为“闭包(Closure)”的程序结构拥有极大的威力。
请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
f1 = lazy_sum(1,3,5,7,9)
f2 = lazy_sum(1,3,5,7,9)
f1 == f2 # False
另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:
def count():
fs = []
for i in range(1,4):
def f():
return i * i
fs.append(f)
return fs
f1,f2,f3 = count()
print(f1())
print(f2())
print(f3())
输出结果为:
9
9
9
全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
def count():
fs = []
def sub(j):
def f():
return j * j
return f
for i in range(1,4):
fs.append(sub(i))
return fs
f1,f2,f3 = count()
print(f1())
print(f2())
print(f3()
结果为:
1
4
9
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。相信大家对于匿名函数一定不陌生,其实就是我们常说的lambda函数:
list(map(lambda x:x * x,[1,2,3,4,5,6,7,8,9]))
def build(x,y):
return lambda:x * x + y * y
reduce(lambda x,y:x + y,[1,2,3,4,5,6,7,8,9])
一般的函数有许多需要定义的参数,假设我们想要固定其中的某些参数,返回一些新的函数,我们就可以使用functools.partial帮助我们创建一个偏函数,从而使得调用变得简单
import functools
int2 = functools.partial(int, base=2)
int2('10010') # 18
当然我们也可以穿入一个函数字典:
kw = { 'base': 2 }
int('10010', **kw)
当传入的参数没有对应的key时,它默认时作为*args的一部分自动加到左边,因此下面的函数相当于比较max(10,5,6,7),返回10:
max2 = functools.partial(max, 10)
max2(5, 6, 7)
引用和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']]
copy()与deepcopy()的区别
copy是浅拷贝,只拷贝可变对象的父级元素。 deepcopy是深拷贝,递归拷贝可变对象的所有元素。
Python里面如何拷贝一个对象?
Python中对象之间的赋值是按引用传递的,如果要拷贝对象需要使用标准模板中的copy
copy.copy:浅拷贝,只拷贝父对象,不拷贝父对象的子对象。
copy.deepcopy:深拷贝,拷贝父对象和子对象。
简要描述Python的垃圾回收机制(garbage collection)
Python中的垃圾回收是以引用计数为主,标记-清除和分代收集为辅。
主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。
引用计数:Python在内存中存储每个对象的引用计数,对象有新的引用时,计数加1;引用它的对象被删除时,计数减1;如果计数变成0,该对象就会消失,分配给该对象的内存就会释放出来。优点:简单,实时性;缺点:维护引用计数消耗资源,循环引用
标记-清除:一些容器对象,比如list、dict、tuple,instance等可能会出现引用循环,对于这些循环,垃圾回收器会定时回收这些循环(对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边)。
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
分代收集:Python把内存根据对象存活时间划分为三代,对象创建之后,垃圾回收器会分配它们所属的代。每个对象都会被分配一个代,而被分配更年轻的代是被优先处理的,因此越晚创建的对象越容易被回收。
分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
Python中的@property有什么作用?如何实现成员变量的只读属性?
@property装饰器就是负责把一个方法变成属性调用,通常用在属性的get方法和set方法,通过设置@property可以实现实例成员变量的直接访问,又保留了参数的检查。另外通过设置get方法而不定义set方法可以实现成员变量的只读属性。
对装饰器的理解,你能写出一个计时器装饰器,它能记录函数的执行时间吗?
装饰器本质上是一个callable object,它可以在让其他函数在不需要做任何代码的变动的前提下增加额外的功能。装饰器的返回值也是一个函数的对象,它经常用于有切面需求的场景。比如:插入日志,性能测试,事务处理,缓存。权限的校验等场景,有了装饰器就可以抽离出大量的与函数功能本身无关的雷同代码并发并继续使用。 详细参考:https://manjusaka.itscoder.com/2018/02/23/something-about-decorator/
装饰器的作用和功能:
import time
def timeit(func):
def wrapper():
start = time.clock()
func()
end = time.clock()
print('used:',end-start)
return wrapper
@timeit
def foo():
print('in foo()'foo())
有关于具体的装饰器的用法看这里:装饰器 - 廖雪峰的官方网站
进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而多个线程共享内存可以极大地提高了程序的运行效率。协程: 是一种用户态的轻量级线程,协程的调度完全由用户控制,协程拥有自己的寄存器上下文和栈。协程调度时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操中栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
这个问题被问的概念相当之大, 进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。
线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。
协程: 是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操中栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
Python并不支持真正意义上的多线程,Python提供了多线程包。Python中有一个叫Global Interpreter Lock(GIL)的东西,它能确保你的代码中永远只有一个线程在执行。经过GIL的处理,会增加执行的开销。这就意味着如果你先要提高代码执行效率,使用threading不是一个明智的选择,当然如果你的代码是IO密集型,多线程可以明显提高效率,相反如果你的代码是CPU密集型的这种情况下多线程大部分是鸡肋。
想要深入详细了解多线程,点击这里:详解Python中的多线程编程_python
想了解一下IO密集和CPU密集可以点击这里:CPU-bound(计算密集型) 和I/O bound(I/O密集型)
设计模式是为我们经常会碰到的一些编程问题构建的可重用解决方案,它是总结性和经优化的。一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码,反之,设计模式更为高级,它是一种在特定情形下实现的方法模板。常见的是工厂模式和单例模式。
单例模式应用的场景一般发现在以下条件下: 资源共享的情况下,避免由于资源操作时导致的性能或损耗等,如日志文件,应用配置。控制资源的情况下,方便资源之间的互相通信。
引自知乎:http://www.zhihu.com/question/20053359
函数重载主要是为了解决两个问题。
可变参数类型。
可变参数个数。
另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。
好吧,那么对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。
那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。
好了,鉴于情况 1 跟 情况 2 都有了解决方案,python 自然就不需要函数重载了。
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
__new__()
在__init__()
之前被调用,用于生成实例对象。利用这个方法和类的属性的特点可以实现设计模式的单例模式。单例模式是指创建唯一对象,单例模式设计的类只能实例这个绝对常考啊.绝对要记住1~2个方法,当时面试官是让手写的.
使用基类 New 是真正创建实例对象的方法,所以重写基类的new 方法,以此保证创建对象的时候只生成一个实例
class Singleton(object):
def __new__(cls,*args,**kwargs):
if not hasattr(cls,'_instance'):
cls._instance = super(Singleton,cls).__new__(cls,*args,**kwargs)
return cls._instance
class Foo(Singleton):
pass
foo1 = Foo()
foo2 = Foo()
print foo1 is foo2 #True
创建实例时把所有实例的__dict__
指向同一个字典,这样它们具有相同的属性和方法.
class Borg(object):
_state = {}
def __new__(cls, *args, **kw):
ob = super(Borg, cls).__new__(cls, *args, **kw)
ob.__dict__ = cls._state
return ob
class MyClass2(Borg):
a = 1
def singleton(cls):
instances = {}
def getinstance(*args, **kw):
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return getinstance
@singleton
class MyClass:
...
作为python的模块是天然的单例模式
# mysingleton.py
class My_Singleton(object):
def foo(self):
pass
my_singleton = My_Singleton()
# to use
from mysingleton import my_singleton
my_singleton.foo()
单例模式伯乐在线详细解释
简单谈下GIL:
Global Interpreter Lock(全局解释器锁)
Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
1. 设置GIL
2. 切换到一个线程去运行
3. 运行:
a. 指定数量的字节码指令,或者
b. 线程主动让出控制(可以调用time.sleep(0))
4. 把线程设置为睡眠状态
5. 解锁GIL
6. 再次重复以上所有步骤
在调用外部代码(如C/C++扩展函数)的时候,GIL 将会被锁定,直到这个函数结束为止(由于在这期间没有Python 的字节码被运行,所以不会做线程切换)。
GIL 是python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行。
多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大
线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程.对于io密集型任务,python的多线程起到作用,但对于cpu密集型任务,python的多线程几乎占不到任何优势,还有可能因为争夺资源而变慢。
见Python 最难的问题
解决办法就是多进程和下面的协程(协程也只是单CPU,但是能减小切换代价提升性能).
知乎被问到了,呵呵哒,跪了
简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态.
Python里最常见的yield就是协程的思想!可以查看第九个问题.
作者:有点苦
链接:https://www.nowcoder.com/discuss/117365
来源:牛客网
Python面试相关问题
说明os,sys模块不同,并列举常用的模块方法?
官方文档:
os模板提供了一种方便的使用操作系统函数的方法
sys模板可供访问由解释器使用或维护的变量和与解释器交互的函数
另一种回答:
os模块负责程序与操作系统的交互,提供了访问操作系统底层的接口。sys模块负责程序与Python解释器的交互,提供了一系列的函数和变量用户操作Python运行时的环境。
os.remove()删除文件 os.rename()重命名文件 os.walk()生成目录树下的所有文件名 os.chdir()改变目录 os.mkdir/makedirs创建目录/多层目录 os.rmdir/removedirs删除目录/多层目录 os.listdir()列出指定目录的文件 os.getcwd()取得当前工作目录 os.chmod()改变目录权限 os.path.basename()去掉目录路径,返回文件名 os.path.dirname()去掉文件名,返回目录路径 os.path.join()将分离的各部分组合成一个路径名 os.path.split()返回(dirname(),basename())元组 os.path.splitext()(返回filename,extension)元组 os.path.getatime\ctime\mtime分别返回最近访问、创建、修改时间 os.path.getsize()返回文件大小 os.path.exists()是否存在 os.path.isabs()是否为绝对路径 os.path.isdir()是否为目录 os.path.isfile()是否为文件 |
sys.argv 命令行参数List,第一个元素是程序本身路径
sys.modules.keys() 返回所有已经导入的模块列表
sys.exc_info() 获取当前正在处理的异常类,exc_type、exc_value、exc_traceback当前处理的异常详细信息
sys.exit(n) 退出程序,正常退出时exit(0)
sys.hexversion 获取Python解释程序的版本值,16进制格式如:0x020403F0
sys.version 获取Python解释程序的版本信息
sys.maxint 最大的Int值
sys.maxunicode 最大的Unicode值
sys.modules 返回系统导入的模块字段,key是模块名,value是模块
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
sys.stdout 标准输出
sys.stdin 标准输入
sys.stderr 错误输出
sys.exc_clear() 用来清除当前线程所出现的当前的或最近的错误信息
sys.exec_prefix 返回平台独立的python文件安装的位置
sys.byteorder 本地字节规则的指示器,big-endian平台的值是'big',little-endian平台的值是'little'
sys.copyright 记录python版权相关的东西
sys.api_version 解释器的C的API版本
sys.version_info
一些常用的用法示例:
想要了解更详细的使用请访问:os和sys模块 - 君醉
range和xrange之间有什么区别?
在大多数情况下,xrange和range在功能方面完全相同,它们都提供了一种生成整数列表供您使用的方法。唯一的区别是range返回一个Python列表对象,xrange返回一个xrange对象。
这意味着xrange实际上并不像run-time那样生成静态列表。它使用称为yielding的特殊技术根据需要创建值。该技术与一种称为生成器的对象一起使用。这意味着,如果你有一个非常巨大的范围,你想生成一个列表,比如10亿,xrange就是要使用的功能。
如果你有一个真正的内存敏感系统,例如你正在使用的手机,尤其如此,因为range将使用尽可能多的内存来创建整数数组,这可能导致内存错误并导致崩溃程序。
"""
封装、继承、多态
1. 谈谈你对面向对象的理解?
2. Python面向对象中的继承有什么特点?
3. 面向对象深度优先和广度优先是什么?
4. 面向对象中super的作用?
5. 列举面向对象中特殊成员(带双下划线的特殊方法,如:__new__、__init__、__call__等)
6. 静态方法和类方法区别?
"""
#谈谈你对面向对象的理解?
#面向对象,首先我们要理解对象,什么是对象?对象是特征与技能的结合体。特征即变量,技能即函数,对象就是变量和函数的结合体,在python中既有面向对象编程,
也有面向过程编程,在所做项目中需要用到哪种更加便捷就用那种编程方式,学面向对象的人,正常最常听到的一句话就是“万物皆对象”,我们编程就是将现实中的事物,
抽象成程序中的对象,用特征和技能去表现出对象。
# Python面向对象中的继承有什么特点?
# 减少代码重用,可以多继承,缺点就是把子类与父类强耦合到一起
# 面向对象深度优先和广度优先是什么?
#深度优先主要体现在经典类在菱形继承的背景下,查找属性的方式
#广度优先主要体现在新式类在菱形继承的背景下,查找属性的方式,
经典类只在python2中,由于的python2不支持服务的倒计时,推荐用python3,python3中的所有类都是新式类。
# 面向对象中super的作用?
super在子类派生出新的方法中重用父类的功能
注意:super()的返回值是一个特殊的随想,该对象专用用来调用父类的属性
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return "呵呵"
class Male(Person):
def __init__(self, name, age):
super().__init__(name, age)
# 列举面向对象中特殊成员(带双下划线的特殊方法,如:__new__、__init__、__call__等)
# __new__ 可以调用其他类的构造方法或者直接返回别的对象来作为本类的实例。
# __init__ 负责将类的实例化
# __call__ 对象后面加括号,触发执行
# __str__ print打印一个对象时 __unicode__
# __repr__ 和__str__
# __doc__ 类的注释,改属性是无法继承的
# __getattr__ 在使用调用属性(方式.属性)不存在的时候触发
# __setattr__ 添加/修改属性会触发它的执行
#__delattr__ 删除属性的时候会触发
# __delete__ 采用del删除属性时,触发
# 静态方法和类方法区别?
# 1. 静态方法: 相当于普通函数 staticmethod
# 2. 类方法: 通过类调用,第一个参数默认是类本身 classmethod
面向过程和面向对象http://www.cnblogs.com/ManyQian/p/8797097.html
反射
通过字符串来操作类与对象的属性,这种操作称为反射
hasattr(obj,某种属性)
不知道有没有某种方法时,进行的判断
getattr(obj,某种属性,None)
查看某种属性,没有属性,可以设置为NONE,有就返回值
setattr(obj,某种属性,属性对应的值)
添加obj对象的某种属性,及其值
delattr(obj,某种属性,属性对应的值)
删除obj对象的某种属性,及其值
与其他语言不同,Python是利用缩进来组织代码的,而不是使用大括号;Python可以不使用分号结尾,也可以使用分号,在一行内将多条语句之间进行分割,如:a = 5; b = 6; c = 7;
2.4创建数值列表
range函数返回一个迭代器,该迭代器生成一个等差整数序列,其可以传入三个参数:起始,终止(不包括),步长(可以为负)
range()可以生成一系列的数字,只传入一个参数N时,默认产生的数字从0到N-1;传入两个参数N,M时,产生从N到M的数,包括N,但不包括M(步长为1,前闭后开);传入三个参数时,第三个参数为步长,可以指定步长的方向,N,M,-,2,即产生从N到M的步长为-2的数(8,6,4,2)。
注意:在Python中对一个变量赋值时,你就创建了一个指向等号右边对象的引用。new_list = my_list这种方法并不能复制列表,因为这只是相当于给my_list起了个新名字叫new_list,两个变量其实指的是同一列表,并没有创建新的列表。
Python 字典(Dictionary) get() 函数返回指定键的值,如果值不在字典中返回默认值。
get()方法语法:
dict.get(key, default=None)
以下实例展示了 get()函数的使用方法:
dict = {'Name': 'Zara', 'Age': 27}
print "Value : %s" % dict.get('Age')
print "Value : %s" % dict.get('Sex', "Never")
以上实例输出结果为:
Value : 27
Value : Never
字典是一种可变容器模型,且可存储任意类型对象。
字典的每个键值 key:value 对用冒号 : 分割,每个键值对之间用逗号 , 分割,整个字典包括在花括号 {} 中 ,格式如下所示:
d = {key1 : value1, key2 : value2 }
键一般是唯一的,如果重复最后的一个键值对会替换前面的,值不需要唯一。
>>>dict = {'a': 1, 'b': 2, 'b': '3'}
>>> dict['b']
>>>'3'
>>> dict
>>>{'a': 1, 'b': '3'}
值可以取任何数据类型,但键必须是不可变的,如字符串,数字或元组。
一个简单的字典实例:
dict = {'Alice': '2341', 'Beth': '9102', 'Cecil': '3258'}
也可如此创建字典:
dict1 = { 'abc': 456 }
dict2 = { 'abc': 123, 98.6: 37 }
把相应的键放入熟悉的方括弧,如下实例:
dict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}
print ("dict['Name']: ", dict['Name'])
print( "dict['Age']: ", dict['Age'])
以上实例输出结果:
dict['Name']: Zara
dict['Age']: 7
向字典添加新内容的方法是增加新的键/值对,修改或删除已有键/值对如下实例:
dict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}
dict['Age'] = 8
dict['School'] = "DPS School"
print ("dict['Age']: ", dict['Age'])
print("dict['School']: ", dict['School'])
以上实例输出结果:
dict['Age']: 8
dict['School']: DPS School
能删单一的元素也能清空字典,清空只需一项操作。
显示删除一个字典用del命令,如下实例:
dict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}; del dict['Name'] # 删除键是'Name'的条目 dict.clear(); # 清空词典所有条目
del dict # 删除词典 print "dict['Age']: ", dict['Age']; print "dict['School']: ", dict['School']
但这会引发一个异常,因为用del后字典不再存在:
dict['Age']:
Traceback (most recent call last):
File "test.py", line 8, in
print "dict['Age']: ", dict['Age'];
TypeError: 'type' object is unsubscriptable
注:del()方法后面也会讨论。
字典值可以没有限制地取任何python对象,既可以是标准的对象,也可以是用户定义的,但键不行。
两个重要的点需要记住:
1)不允许同一个键出现两次。创建时如果同一个键被赋值两次,后一个值会被记住,如下实例:
#!/usr/bin/python dict = {'Name': 'Zara', 'Age': 7, 'Name': 'Manni'}; print "dict['Name']: ", dict['Name'];
以上实例输出结果:
dict['Name']: Manni
2)键必须不可变,所以可以用数字,字符串或元组充当,所以用列表就不行,如下实例:
#!/usr/bin/python dict = {['Name']: 'Zara', 'Age': 7}; print "dict['Name']: ", dict['Name'];
以上实例输出结果:
Traceback (most recent call last):
File "test.py", line 3, in
dict = {['Name']: 'Zara', 'Age': 7};
TypeError: list objects are unhashable
Python字典包含了以下内置函数:
序号 | 函数及描述 |
---|---|
1 | cmp(dict1, dict2) 比较两个字典元素。 |
2 | len(dict) 计算字典元素个数,即键的总数。 |
3 | str(dict) 输出字典可打印的字符串表示。 |
4 | type(variable) 返回输入的变量类型,如果变量是字典就返回字典类型。 |
Python字典包含了以下内置方法:
序号 | 函数及描述 |
---|---|
1 | dict.clear() 删除字典内所有元素 |
2 | dict.copy() 返回一个字典的浅复制 |
3 | dict.fromkeys(seq[, val]) 创建一个新字典,以序列 seq 中元素做字典的键,val 为字典所有键对应的初始值 |
4 | dict.get(key, default=None) 返回指定键的值,如果值不在字典中返回default值 |
5 | dict.has_key(key) 如果键在字典dict里返回true,否则返回false |
6 | dict.items() 以列表返回可遍历的(键, 值) 元组数组 |
7 | dict.keys() 以列表返回一个字典所有的键 |
8 | dict.setdefault(key, default=None) 和get()类似, 但如果键不存在于字典中,将会添加键并将值设为default |
9 | dict.update(dict2) 把字典dict2的键/值对更新到dict里 |
10 | dict.values() 以列表返回字典中的所有值 |
11 | pop(key[,default]) 删除字典给定键 key 所对应的值,返回值为被删除的值。key值必须给出。 否则,返回default值。 |
12 | popitem() 随机返回并删除字典中的一对键和值。 |
集合(set)是一个无序的不重复元素序列。
可以使用大括号 { } 或者 set() 函数创建集合,注意:创建一个空集合必须用 set() 而不是 { },因为 { } 是用来创建一个空字典。
创建格式:
parame = {value01,value02,...}
或者
set(value)
>>>basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'} >>> print(basket) # 这里演示的是去重功能 {'orange', 'banana', 'pear', 'apple'} >>> 'orange' in basket # 快速判断元素是否在集合内 True >>> 'crabgrass' in basket False >>> # 下面展示两个集合间的运算. ... >>> a = set('abracadabra') >>> b = set('alacazam') >>> a {'a', 'r', 'b', 'c', 'd'} >>> a - b # 集合a中包含元素 {'r', 'd', 'b'} >>> a | b # 集合a或b中包含的所有元素 {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'} >>> a & b # 集合a和b中都包含了的元素 {'a', 'c'} >>> a ^ b # 不同时包含于a和b的元素 {'r', 'd', 'b', 'm', 'z', 'l'}
类似列表推导式,同样集合支持集合推导式(Set comprehension):
>>>a = {x for x in 'abracadabra' if x not in 'abc'} >>> a {'r', 'd'}
语法格式如下:
s.add( x )
将元素 x 添加到集合 s 中,如果元素已存在,则不进行任何操作。
>>>thisset = set(("Google", "Runoob", "Taobao")) >>> thisset.add("Facebook") >>> print(thisset) {'Taobao', 'Facebook', 'Google', 'Runoob'}
还有一个方法,也可以添加元素,且参数可以是列表,元组,字典等,语法格式如下:
s.update( x )
x 可以有多个,用逗号分开。
>>>thisset = set(("Google", "Runoob", "Taobao")) >>> thisset.update({1,3}) >>> print(thisset) {1, 3, 'Google', 'Taobao', 'Runoob'} >>> thisset.update([1,4],[5,6]) >>> print(thisset) {1, 3, 4, 5, 6, 'Google', 'Taobao', 'Runoob'} >>>
语法格式如下:
s.remove( x )
将元素 x 从集合 s 中移除,如果元素不存在,则会发生错误。
>>>thisset = set(("Google", "Runoob", "Taobao")) >>> thisset.remove("Taobao") >>> print(thisset) {'Google', 'Runoob'} >>> thisset.remove("Facebook") # 不存在会发生错误 Traceback (most recent call last): File "
此外还有一个方法也是移除集合中的元素,且如果元素不存在,不会发生错误。格式如下所示:
s.discard( x )
>>>thisset = set(("Google", "Runoob", "Taobao")) >>> thisset.discard("Facebook") # 不存在不会发生错误 >>> print(thisset) {'Taobao', 'Google', 'Runoob'}
我们也可以设置随机删除集合中的一个元素,语法格式如下:
s.pop()
thisset = set(("Google", "Runoob", "Taobao", "Facebook")) x = thisset.pop() print(x)
输出结果:
$ python3 test.py
Runoob
多次执行测试结果都不一样。
然而在交互模式,pop 是删除集合的第一个元素(排序后的集合的第一个元素)。
>>>thisset = set(("Google", "Runoob", "Taobao", "Facebook")) >>> thisset.pop() 'Facebook' >>> print(thisset) {'Google', 'Taobao', 'Runoob'} >>>
语法格式如下:
len(s)
计算集合 s 元素个数。
>>>thisset = set(("Google", "Runoob", "Taobao")) >>> len(thisset) 3
语法格式如下:
s.clear()
清空集合 s。
>>>thisset = set(("Google", "Runoob", "Taobao")) >>> thisset.clear() >>> print(thisset) set()
语法格式如下:
x in s
判断元素 s 是否在集合 x 中,存在返回 True,不存在返回 False。
>>>thisset = set(("Google", "Runoob", "Taobao")) >>> "Runoob" in thisset True >>> "Facebook" in thisset False >>>
方法 | 描述 |
---|---|
add() | 为集合添加元素 |
clear() | 移除集合中的所有元素 |
copy() | 拷贝一个集合 |
difference() | 返回多个集合的差集 |
difference_update() | 移除集合中的元素,该元素在指定的集合也存在。 |
discard() | 删除集合中指定的元素 |
intersection() | 返回集合的交集 |
intersection_update() | 删除集合中的元素,该元素在指定的集合中不存在。 |
isdisjoint() | 判断两个集合是否包含相同的元素,如果没有返回 True,否则返回 False。 |
issubset() | 判断指定集合是否为该方法参数集合的子集。 |
issuperset() | 判断该方法的参数集合是否为指定集合的子集 |
pop() | 随机移除元素 |
remove() | 移除指定元素 |
symmetric_difference() | 返回两个集合中不重复的元素集合。 |
symmetric_difference_update() | 移除当前集合中在另外一个指定集合相同的元素,并将另外一个指定集合中不同的元素插入到当前集合中。 |
union() | 返回两个集合的并集 |
update() | 给集合添加元素 |
使用continue关键字可以跳过continue后面的代码进入下一次循环
终止循环用break,多个循环嵌套时,break关键字只结束最内层的循环,外层的循环会继续运行;
pass是Python中“什么都不做”的语句,或者是作为还没有实现的代码占位符;
for 变量 in range(begin, end) : 前闭后开
for 变量 in my_list:
while 条件:
一个if语句可以接多个elif代码块和一个else代码块,如果if与所有的elif条件均为False,则执行else代码块,如果某个条件为True,则后面的elif和else代码块则不会执行。
类是用来描述具有相同属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法。对象是类的实例,类是可实例化出对象的模具。
面向对象:程序员反复修改优化类,类实例化出对象,对象调用类里的函数执行具体的操作。
使用isinstance函数来检查一个对象是否是特定类型的实例:a = 5; isinstance(a, int); # 返回 True
isinstance接受一个包含类型的元组,可以检查对象的类型是否在元组中的类型中:a = 5;isinstance(a, (int, float)); # 返回 True
import pickle
开:文件变量 = open('文件路径文件名', 'wb')
存:pickle.dump(待写入的变量, 文件变量)
关:文件变量.close()
import pickle
开:文件变量 = open('文件路径文件名', 'rb')
存:pickle.load(待写入的变量, 文件变量)
关:文件变量.close()
文件的作用
将数据长期保存下来,在需要的时候使用
CPU | 内存 | 硬盘 |
---|---|---|
 |  |  |
文本文件和二进制文件
文本文件
二进制文件
在 计算机 中要操作文件的套路非常固定,一共包含三个步骤:
Python
中要操作文件需要记住 1 个函数和 3 个方法序号 | 函数/方法 | 说明 |
---|---|---|
01 | open | 打开文件,并且返回文件操作对象 |
02 | read | 将文件内容读取到内存 |
03 | write | 将指定内容写入文件 |
04 | close | 关闭文件 |
open
函数负责打开文件,并且返回文件对象read
/write
/close
三个方法都需要通过 文件对象 来调用open
函数的第一个参数是要打开的文件名(文件名区分大小写)
read
方法可以一次性 读入 并 返回 文件的 所有内容close
方法负责 关闭文件
read
方法执行后,会把 文件指针 移动到 文件的末尾# 1. 打开 - 文件名需要注意大小写
file = open("README")
# 2. 读取
text = file.read()
print(text)
# 3. 关闭
file.close()
提示
文件指针(知道)
read
方法后,文件指针 会移动到 读取内容的末尾
思考
read
方法,读取了所有内容,那么再次调用 read
方法,还能够获得到内容吗?答案
open
函数默认以 只读方式 打开文件,并且返回文件对象语法如下:
f = open("文件名", "访问方式")
访问方式 | 说明 |
---|---|
r | 以只读方式打开文件。文件的指针将会放在文件的开头,这是默认模式。如果文件不存在,抛出异常 |
w | 以只写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件 |
a | 以追加方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入 |
r+ | 以读写方式打开文件。文件的指针将会放在文件的开头。如果文件不存在,抛出异常 |
w+ | 以读写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件 |
a+ | 以读写方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入 |
提示
写入文件示例
# 打开文件
f = open("README", "w")
f.write("hello python!\n")
f.write("今天天气真好")
# 关闭文件
f.close()
read
方法默认会把文件的 所有内容 一次性读取到内存readline
方法
readline
方法可以一次读取一行内容读取大文件的正确姿势
# 打开文件
file = open("README")
while True:
# 读取一行内容
text = file.readline()
# 判断是否读到内容
if not text:
break
# 每读取一行的末尾已经有了一个 `\n`
print(text, end="")
# 关闭文件
file.close()
目标
用代码的方式,来实现文件复制过程
小文件复制
# 1. 打开文件
file_read = open("README")
file_write = open("README[复件]", "w")
# 2. 读取并写入文件
text = file_read.read()
file_write.write(text)
# 3. 关闭文件
file_read.close()
file_write.close()
大文件复制
# 1. 打开文件
file_read = open("README")
file_write = open("README[复件]", "w")
# 2. 读取并写入文件
while True:
# 每次读取一行
text = file_read.readline()
# 判断是否读取到内容
if not text:
break
file_write.write(text)
# 3. 关闭文件
file_read.close()
file_write.close()
Python
中,如果希望通过程序实现上述功能,需要导入 os
模块序号 | 方法名 | 说明 | 示例 |
---|---|---|---|
01 | rename | 重命名文件 | os.rename(源文件名, 目标文件名) |
02 | remove | 删除文件 | os.remove(文件名) |
序号 | 方法名 | 说明 | 示例 |
---|---|---|---|
01 | listdir | 目录列表 | os.listdir(目录名) |
02 | mkdir | 创建目录 | os.mkdir(目录名) |
03 | rmdir | 删除目录 | os.rmdir(目录名) |
04 | getcwd | 获取当前目录 | os.getcwd() |
05 | chdir | 修改工作目录 | os.chdir(目标目录) |
06 | path.isdir | 判断是否是文件 | os.path.isdir(文件路径) |
提示:文件或者目录操作都支持 相对路径 和 绝对路径
ASCII
编码,UNICODE
编码等Python 2.x 默认使用
ASCII
编码格式
Python 3.x 默认使用UTF-8
编码格式
ASCII
编码
256
个 ASCII
字符ASCII
在内存中占用 1 个字节 的空间
8
个 0/1
的排列组合方式一共有 256
种,也就是 2 ** 8

UTF-8
编码格式
UTF-8
字符,涵盖了 地球上几乎所有地区的文字UTF-8
是 UNICODE
编码的一种编码格式Python 2.x 默认使用
ASCII
编码格式
Python 3.x 默认使用UTF-8
编码格式
utf-8
编码来处理 python 文件# *-* coding:utf8 *-*
这方式是官方推荐使用的!
# coding=utf8
unicode 字符串
Python 2.x
中,即使指定了文件使用 UTF-8
的编码格式,但是在遍历字符串时,仍然会 以字节为单位遍历 字符串u
,告诉解释器这是一个 unicode
字符串(使用 UTF-8
编码格式的字符串)# *-* coding:utf8 *-*
# 在字符串前,增加一个 `u` 表示这个字符串是一个 utf8 字符串
hello_str = u"你好世界"
print(hello_str)
for c in hello_str:
print(c)
所有可用的二元运算符:
检查两个引用是否指向同一个对象可以使用is/is not关键字
请参考《利用Python进行数据分析》第2.3.2.7小节
value = a if condition else b 如果满足condition条件则将a赋值给value,否则将b赋值给value
enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
enumerate(sequence, [start=0])
返回 enumerate(枚举) 对象。
以下展示了使用 enumerate() 方法的实例:
>>>seasons = ['Spring', 'Summer', 'Fall', 'Winter']
>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>> list(enumerate(seasons, start=1)) # 下标从 1 开始
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
>>>seq = ['one', 'two', 'three']
>>> for i, element in enumerate(seq):
... print i, element
0 one
1 two
2 three
eval
函数eval()
函数十分强大 —— 将字符串 当成 有效的表达式 来求值 并 返回计算结果
# 基本的数学计算
In [1]: eval("1 + 1")
Out[1]: 2
# 字符串重复
In [2]: eval("'*' * 10")
Out[2]: '**********'
# 将字符串转换成列表
In [3]: type(eval("[1, 2, 3, 4, 5]"))
Out[3]: list
# 将字符串转换成字典
In [4]: type(eval("{'name': 'xiaoming', 'age': 18}"))
Out[4]: dict
需求
input_str = input("请输入一个算术题:")
print(eval(input_str))
eval
在开发时千万不要使用
eval
直接转换input
的结果
__import__('os').system('ls')
import os
os.system("终端命令")
注意:Python中虽然没有访问控制的关键字,例如private、protected等等。但是,在Python编码中,有一些约定来进行访问控制
单下划线、双下划线、头尾双下划线说明:
_foo:单下划线开头表示的是protected类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import *
__foo:双下划线表示的是私有类型(private)的变量,只能是允许这个类本身进行访问了
__foo__:头尾双下划线定义的是特列方法,类似_init_()之类的
通俗的理解__name__ == '__main__'
:假如你叫小明.py,在朋友眼中,你是小明(__name__ == '小明')
;在你自己眼中,你是你自己(__name__ == '__main__')
。
if __name__ == '__main__':
的意思是:当.py文件被直接运行时,if __name__ == '__main__'
之下的代码块将被运行;当.py文件以模块形式被导入时,if __name__ == '__main__'
之下的代码块不被运行。