目录
1- python中list、tuple、dict、set等类型有什么区别?
6- 请简述内置函数zip的用法。迭代器的长度不一致时,是如何处理的,有什么替代方案吗?
7- 高阶函数map/reduce/filter/sorted的用法分别是怎样的?举例说明。
0- 解释型语言与编译型语言的区别
1- python中list、tuple、dict、set等类型有什么区别?
list、set和dict都是可变数据类型,tuple是不可变数据类型。不可变数据类型更改后地址发生改变,可变数据类型更改地址不发生改变。
list:python内置的数据类型,有序集合,随时增删。包含的数据类型可以不同
tuple 元祖:python内置的数据类型,有序列表,一旦初始化,无法修改。tuple不可变,所以代码更安全。包含的数据类型可以不同。
表示:空的tuple1=();一个元素tuple1=(2,)后面跟着一个逗号
虽然tuple不可变,但是它里面的list、dict、set是可以变的
dict 词典:python内置,键值对(key-value)方式存储,查找速度快;dict的key必须是不可变对象(字符串、数字、元祖);value包含的数据类型可以不同
set:无序集合、key不重复
无索引、无切片、作为一个无序的集合,set不记录元素位置或者插入点。因此,set不支持 indexing, slicing, 或其它类序列(sequence-like)的操作
2- 函数传参有哪些形式?分别有什么特点?
fun1(a,b,c)
fun2(a=1,b=2,c=3)
fun3(*args)
fun4(**kargs)
第一种 fun1(a,b,c)
是直接将实参赋予行参,根据位置做匹配,即严格要求实参的数量与行参的数量位置相等,比较一般,大多数语言常用这种方式
第二种 fun2(a=1,b=2,c=3)
根据键值对的形式做实参与行参的匹配,通过这种式就可以忽略了参数的位置关系,直接根据关键字来进行赋值,同时该种传参方式还有个好处就是可以在调用函数的时候作为个别选填项,不要求数量上的相等
第三种 fun3(*args)
,这传参方式是可以传入任意个参数,这些若干参数都被放到了tuple元组中赋值给行参args,之后要在函数中使用这些行参,直接操作args这个tuple元组就可以了,这样的好处是在参数的数量上没有了限制,但是因为是tuple,其本身还是有次序的,这就仍然存在一定的束缚,在对参数操作上也会有一些不便
第四种 fun4(**kargs)
最为灵活,其是以键值对字典的形式向函数传参,含有第二种位置的灵活的同时具有第三种方式的数量上的无限制。此外第三四种函数声明的方式前的'*',与c里面的指针声明一样,这里仅做声明标识之用
def test(x,y=4,*a,**b):
print(x,y,a,b)
在 python 中,类型属于对象,变量是没有类型的
a = [1, 2, 3]
print(type(a))#<class 'list'>
a = 'string'
print(type(a)) # <class 'str'>
以上代码中,[1,2,3] 是 List 类型,"string" 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是指向 List 类型对象,也可以是指向 String 类型对象。
python 函数的参数传递:
-
不可变类型:类似 C++ 的值传递,如整数、字符串、元组。如 fun(a),传递的只是 a 的值,没有影响 a 对象本身。如果在 fun(a) 内部修改 a 的值,则是新生成一个 a 的对象。
-
可变类型:类似 C++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后 fun 外部的 la 也会受影响
python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。
3- 请解释python的默认参数陷阱问题。
当解释器执行def的时候, 默认参数也会被计算, 并存在函数的.func_defaults属性中。 由于Python中函数参数传递的是对象, 可变对象在调用者和被调用者之间共享,再次执行函数时,默认参数不会重新计算,而是继续使用上一次调用后得到的默认参数值。注意,可变对象才会发生这种情况。
不可变的默认参数的多次调用不会造成任何影响,可变默认参数的多次调用的结果不符合预期。那么在使用可变默认参数时,就不能只在函数定义时初始化一次,而应该在每次调用时初始化。
最佳实践是定义函数时指定可变默认参数的值为None,在函数体内部重新绑定默认参数的值
#
def test(item, buf=[]):
buf.append(item)
print(buf)
test('younger')#['younger']
test(10, [])#[10]
test('higher')#['younger', 'higher']
#另一种方法
def test(item, buf = None):
if buf is None:
buf = []
buf.append(item)
print(buf)
4- 请举例说明浅拷贝和深拷贝的区别
-
直接赋值:其实就是对象的引用(别名)。没有新建任何对象,就是原对象的一个别名,对象 id 和对象元素的 id 都是一样的。
-
浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。Python 会分配一块新的内存用于创建新的拷贝对象,但拷贝对象中的元素依旧是原对象(被拷贝对象)中元素,即拷贝对象与原对象的 id 不同,但两者中的元素具有相同的 id。
-
深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。Python会分配一块新的内存用于创建新的拷贝对象,拷贝对象中的元素是通过递归的方式将原对象中的元素一一复制过来的(可变元素除外,可变元素会创建一个新的对象,然后用原来对象的相应元素赋值之后指向新对象的元素),即对象与对象中的元素都是不同的 id,两者完全独立分离。
注意:浅拷贝和深拷贝的不同仅仅是对组合对象来说,所谓的组合对象就是包含了其它对象的对象,如列表,类实例。而对于数字、字符串以及其它“原子”类型,没有拷贝一说,产生的都是原对象的引用。
a = [2, 3, [4, 5, 6]]
b = a
c = a.copy()
d = copy.deepcopy(a)
print(a, b, c, d)
#[2, 3, [4, 5, 6]] [2, 3, [4, 5, 6]] [2, 3, [4, 5, 6]] [2, 3, [4, 5, 6]]
a[1] = '4'
a[2].append('you')
print(a, b, c, d)
#[2, '4', [4, 5, 6, 'you']] [2, '4', [4, 5, 6, 'you']] [2, 3, [4, 5, 6, 'you']] [2, 3, [4, 5, 6]]
import copy
a = [1, 2, 3]
b = copy.deepcopy(a)
print(id(a), id(b))
>>> 60742344 60742600
for x, y in zip(a, b):
print(id(x), id(y))
>>>
8791184094032 8791184094032
8791184094064 8791184094064
8791184094096 8791184094096
为什么使用了深拷贝,a和b中元素的id还是一样呢?
答:这是因为对于不可变对象,当需要一个新的对象时,python可能会返回已经存在的某个类型和值都一致的对象的引用。而且这种机制并不会影响 a 和 b 的相互独立性,因为当两个元素指向同一个不可变对象时,对其中一个赋值不会影响另外一个。
5- 生成器与迭代器的概念分别是什么?
迭代是Python最强大的功能之一,是访问集合元素的一种方式。
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。
字符串,列表或元组对象都可用于创建迭代器.
在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象。
Python yield 使用浅析
6- 请简述内置函数zip的用法。迭代器的长度不一致时,是如何处理的,有什么替代方案吗?
zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。在 Python 3.x 中为了减少内存,zip() 返回的是一个对象。
如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。
a = [1, 2, 3, 4]
b = [4, 5, 6, 8, 9, 0]
zipped_data = list(zip(a, b))
print(zipped_data) # [(1, 4), (2, 5), (3, 6), (4, 8)]
list1, list2 = zip(*zip(a, b))
print(list1, list2) # (1, 2, 3, 4) (4, 5, 6, 8)
迭代器长度不一致时的替代方案:itertools.zip_longest(),以元素最多对象为基准,使用fillvalue的值来填充:
import itertools
list1 = ["A", "B", "C", "D", "E"] # len = 5
list2 = ["a", "b", "c", "d", "e"] # len = 5
list3 = [1, 2, 3, 4] # len = 4
temp = itertools.zip_longest(list1, list3)
print(list(temp))
temp = itertools.zip_longest(list1, list3, fillvalue='默认值')
print(list(temp))
7- 高阶函数map/reduce/filter/sorted的用法分别是怎样的?举例说明。
高阶函数就是可接受另一个函数作为参数传入的运行的函数
map() 会根据提供的函数对指定序列做映射。
第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。Python 2.x 返回列表。Python 3.x 返回迭代器。
def square(x):
return x ** 2
print(map(square, [1, 2, 3, 4, 5])) # <map object at 0x0000000002417130>
print(list(map(square, [1, 2, 3, 4, 5]))) # [1, 4, 9, 16, 25]
print(list(map(lambda x: x ** 2, [1, 3, 4, 5]))) # [1, 9, 16, 25], 使用 lambda 匿名函数
如果函数有多个参数, 但每个参数的序列元素数量不一样, 会根据最少元素的序列进行:
listx = [1, 2, 3, 4, 5, 6, 7] # 7 个元素
listy = [2, 3, 4, 5, 6, 7] # 6 个元素
listz = [100, 100, 100, 100] # 4 个元素
list_result = map(lambda x, y, z: x ** 2 + y + z, listx, listy, listz)
print(list(list_result))
# [103, 107, 113, 121]
reduce() 函数会对参数序列中元素进行累积。
函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。
reduce(function, iterable[, initializer])
def add(x, y):
return x + y
from functools import reduce
sum1 = reduce(add, [1, 2, 3, 4, 5]) # 计算列表和:1+2+3+4+5
print(sum1) # 15
sum2 = reduce(lambda x, y: x+y, [1,2,3,4,5]) # 使用 lambda 匿名函数
print(sum2) # 15
filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。
该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新列表中
filter(function, iterable)
def is_odd(n):
return n % 2 == 1
tmplist = filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8])
print(tmplist) # <filter object at 0x0000000001DE8A00>
print(list(tmplist)) # [1, 3, 5, 7]
sort() 函数用于对原列表进行排序,如果指定参数,则使用比较函数指定的比较函数。
list.sort( key=None, reverse=False)
# reverse -- 排序规则,reverse = True 降序, reverse = False 升序(默认)
aList = ['Google', 'Runoob', 'Taobao', 'Facebook']
aList.sort()
print(aList) # ['Facebook', 'Google', 'Runoob', 'Taobao']
aList.sort(reverse=True)
print(aList) # ['Taobao', 'Runoob', 'Google', 'Facebook']
具体用法可看Python3 List sort()方法其中的笔记
sort
函数和sorted
函数的区别在于,sort
会直接改变原对象,sorted
会返回一个新的对象,不改变原对象,用法上是L.sort
,参数方面是完全一样的
aList = ['Google', 'Runoob', 'Taobao', 'Facebook']
blist = sorted(aList)
print(aList) # ['Google', 'Runoob', 'Taobao', 'Facebook']
print(blist) # ['Facebook', 'Google', 'Runoob', 'Taobao']
8- 闭包的概念是什么?举例说明。
在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。
#闭包函数的实例
# outer是外部函数 a和b都是外函数的临时变量
def outer( a ):
b = 10
# inner是内函数
def inner():
#在内函数中 用到了外函数的临时变量
print(a+b)
# 外函数的返回值是内函数的引用
return inner
if __name__ == '__main__':
# 在这里我们调用外函数传入参数5
#此时外函数两个临时变量 a是5 b是10 ,并创建了内函数,然后把内函数的引用返回存给了demo
# 外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数
demo = outer(5)
# 我们调用内部函数,看一看内部函数是不是能使用外部函数的临时变量
# demo存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数
demo() # 15
demo2 = outer(7)
demo2()#17
在基本的python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:1 global 声明全局变量 2 全局变量是可变类型数据的时候可以修改
在闭包内函数也是类似的情况。在内函数中想修改闭包变量(外函数绑定给内函数的局部变量)的时候:
1 在python3中,可以用nonlocal 关键字声明 一个变量, 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。
2 在python2中,没有nonlocal这个关键字,我们可以把闭包变量改成可变类型数据进行修改,比如列表。
#修改闭包变量的实例
# outer是外部函数 a和b都是外函数的临时变量
def outer( a ):
b = 10 # a和b都是闭包变量
c = [a] #这里对应修改闭包变量的方法2
# inner是内函数
def inner():
#内函数中想修改闭包变量
# 方法1 nonlocal关键字声明
nonlocal b
b+=1
# 方法二,把闭包变量修改成可变数据类型 比如列表
c[0] += 1
print(c[0])
print(b)
# 外函数的返回值是内函数的引用
return inner
if __name__ == '__main__':
demo = outer(5)
demo() # 6 11
9- 匿名函数有什么好处?请举一个例子说明其用法。
lambda [para1, para2, ...]: expression
>>> lambda x, y: x+y # 在一行定义匿名函数
<function <lambda> at 0x00000217D39E1F28> # 打印上面
>>> a = lambda x, y: x + y # 定义匿名函数并赋值给a
>>> a(2, 3) # a具有匿名函数的功能, 通过参数传值
5 # 输出结果
ambda的冒号前面表示的是参数,冒号后面的是表达式. 注意, lambda 可以接受任意多个参数, 但只能有一个表达式.
匿名函数的优点:
1 使用Python写一些脚本时,使用lambda可以省去定义函数的过程,让代码更加精简.
2 对于一些抽象的,不会被别的地方再重复使用的函数,有时候函数起个名字也是个难题,使用lambda不需要考虑命名的问题.
3 使用lambda在某些时候能使代码更容易理解.
匿名函数一般不单独使用,经常与一些内置函数一块使用.
能使用匿名函数的内置函数为:map,filter,max,min,sorted
具体使用可看python之匿名函数以及在内置函数中的使用
10- 装饰器的概念是什么?如何使用?
装饰器(Decorators)是 Python 的一个重要部分。简单地说:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic(Python范儿)。
装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
具体看Python 函数装饰器的笔记一
装饰器的使用场景:
授权:装饰器能有助于检查某个人是否被授权去使用一个web应用的端点(endpoint)。它们被大量使用于Flask和Django web框架中。这里是一个例子来使用基于装饰器的授权:
from functools import wraps
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
authenticate()
return f(*args, **kwargs)
return decorated
日志是装饰器运用的另一个亮点。现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码。这是个例子:
def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func() # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
return wrapper
def foo():
print('i am foo')
foo = use_logging(foo) # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于 foo = wrapper
foo() # 执行foo()就相当于执行 wrapper()
use_logging 就是一个装饰器,它一个普通的函数,它把执行真正业务逻辑的函数 func 包裹在其中,看起来像 foo 被 use_logging 装饰了一样,use_logging 返回的也是一个函数,这个函数的名字叫 wrapper。在这个例子中,函数进入和退出时 ,被称为一个横切面,这种编程方式被称为面向切面的编程。
@ 语法糖
如果你接触 Python 有一段时间了的话,想必你对 @ 符号一定不陌生了,没错 @ 符号就是装饰器的语法糖,它放在函数开始定义的地方,这样就可以省略最后一步再次赋值的操作。
def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func()
return wrapper
@use_logging
def foo():
print("i am foo")
foo()
如上所示,有了 @ ,我们就可以省去foo = use_logging(foo)这一句了,直接调用 foo() 即可得到想要的结果。你们看到了没有,foo() 函数不需要做任何修改,只需在定义的地方加上装饰器,调用的时候还是和以前一样,如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。
装饰器在 Python 使用如此方便都要归因于 Python 的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。
带参数的装饰器
装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 foo 。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。比如,我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
elif level == "info":
logging.info("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name='foo'):
print("i am %s" % name)
foo()
11- 偏函数的概念是什么?如何使用?
偏函数 partial:
函数在执行时,要带上所有必要的参数进行调用。但是,有时参数可以在函数被调用之前提前获知。这种情况下,一个函数有一个或多个参数预先就能用上,以便函数能用更少的参数进行调用。
偏函数是将所要承载的函数作为partial()函数的第一个参数,原函数的各个参数依次作为partial()函数后续的参数,除非使用关键字参数。
from functools import partial
def mod( n, m ):
return n % m
mod_by_100 = partial( mod, 100 )
print mod( 100, 7 ) # 2
print mod_by_100( 7 ) # 2
12- enumerate相比range有什么优势?
enumerate:
为可迭代的对象添加序号,自动生成一列,默认从0开始自增1,也可以从别的数开始自增1,如:
li = ['电脑','鼠标垫','U盘','游艇']
for x,y in enumerate(li):
print(x,y)
inp = input("请输入商品:")
print(li[int(inp)])-------------------------------------
int(inp)为强制类型转换enumerate(li,10),从10开始自增1
在2.7中,xrange用来指定范围,生成数字,例如xrange(0,100),但是内存里不创建这些数字,只有通过for循环迭代时才会创建,第一次循环创建0,第二次循环创建1,这样大大节约了空间,提高了性能
在2.7中,range用来获取指定范围内的数,如range(0,100),但是在内存里会把这些数字全部创建出来
在3中,只有range,没有xrange,但是这里的range等同于2.7中的xrange
temp=range(0,10)
print(temp)
在2.7版本中,输出结果为[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
在3版本中,输出结果为range(0,10)
13- 什么是工厂函数?举例说明。
工厂函数顾名思义就是一个能产生函数的工厂,其目的是对一个需要输入多个参数的函数分类封装,不同使用者只需要输入更少的参数或单个参数就能调用。
Python2.2统一了类型和类,所有的内建类型现在也都是类,在这基础之上,原来的所谓内建转换函数比如int()、type()、list()等,现在都成了工厂函数。也就是说虽然他们看上去有点像函数,实质上他们是类。
所谓工厂函数就是指这些内建函数都是类对象, 当你调用它们时,实际上是创建了一个类实例。
面向对象技术简介
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- 方法:类中定义的函数。
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- 局部变量:定义在方法中的变量,只作用于当前实例的类。
- 实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
- 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
- 实例化:创建一个类的实例,类的具体对象。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。
Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。
对象可以包含任意数量和类型的数据。
self代表类的实例,而非类
方法重写
如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法,实例如下:
class Parent: # 定义父类
def myMethod(self):
print ('调用父类方法')
class Child(Parent): # 定义子类
def myMethod(self):
print ('调用子类方法')
c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法
super() 函数是用于调用父类(超类)的一个方法。
Python 子类继承父类构造函数说明:
类的专有方法:专有方法是在特殊情况下或使用特殊语法时由python调用的,而不是像普通方法一样在代码中直接调用。看到形如__XXX__的变量或函数名时就需要注意下,这在python中是有特殊用途的
- __init__ : 构造函数,在生成对象时调用
- __del__ : 析构函数,释放对象时使用
- __repr__ : 打印,转换
- __setitem__ : 按照索引赋值
- __getitem__: 按照索引获取值
- __len__: 获得长度
- __cmp__: 比较运算
- __call__: 函数调用
- __add__: 加运算
- __sub__: 减运算
- __mul__: 乘运算
- __truediv__: 除运算
- __mod__: 求余运算
- __pow__: 乘方
14- 举例说明类属性和实例属性的区别。
class Student:
count = 10 # count是类属性
def __init__(self, name):
self.name = name # name是实例属性
print(Student.count) # 10 通过类来访问类属性
# print(Student.name) # 报错:AttributeError: type object 'Student' has no attribute 'name'
s1 = Student("xiaoming")
print(s1.name) # xiaoming 必须通过实例来访问实例属性name
print(s1.count) # 10 实例也可以访问类属性
# 通过实例更改类属性的值,不影响类访问类属性的值
s1.count = 50
print(s1.count) # 50 实例更改类属性的10为50
print(Student.count) # 10 通过类访问count的值,发现还是原来的10,并没有被改成50
# 通过类更改类属性的值,不影响实例访问类属性的值
Student.count = 33
print(s1.count) # 50 实例访问类属性值为上次更改的值50,不是类更改的值33
print(Student.count) # 33 类访问类属性的值是被更改的33
# 另外实例化一个对象,其值不是默认值,而是上次由类更改类属性后的值
s2 = Student("xiaohua")
print(s2.count) # 33 此处对象访问的count值为33,而不是默认值10,也不是之前由对象更改的值50
print(Student.count) # 33 这里也是33,而不是默认值10
15- 请实例解释继承和多态的概念。
继承是一种创建新的类的方式,新创建的叫子类,继承的叫父类、超类、基类。继承的特点就是子类可以使用父类的属性(特征、技能)。继承是类与类之间的关系。
继承可以减少代码冗余、提高重用性。
多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。
所谓多态:定义时的类型和运行时的类型不一样,此时就成为多态。
-
Python伪代码实现Java或C#的多态。
Python “鸭子类型”
class Duck:
def quack(self):
print("Quaaaaaack!")
class Bird:
def quack(self):
print("bird imitate duck.")
class Doge:
def quack(self):
print("doge imitate duck.")
def in_the_forest(duck):
duck.quack()
duck = Duck()
bird = Bird()
doge = Doge()
for x in [duck, bird, doge]:
in_the_forest(x)
16- 如何设置类内属性的访问限制?
17- 如何使用__slots__?
18- 定制类__str__,__iter__, __getitem__,__getattr__,__call__分别有什么作用?
- 静态方法、类方法和成员方法有什么区别
实例方法
定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);
调用:只能由实例对象调用。
类方法
定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);
调用:类和实例对象都可以调用。
静态方法
定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;
调用:类和实例对象都可以调用。
- 类有哪些内置的属性?
- __dict__ : 类的属性(包含一个字典,由类的数据属性组成)
- __doc__ :类的文档字符串
- __name__: 类名
- __module__: 类定义所在的模块(类的全名是'__main__.className',如果类位于一个导入模块mymod中,那么className.__module__ 等于 mymod)
- __bases__ : 类的所有父类构成元素(包含了一个由所有父类组成的元组)
- @classmethod, @staticmethod, @property这些都是什么?
- __init__和__new__的区别是什么?
依照Python官方文档的说法,__new__方法主要是当你继承一些不可变的class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径。还有就是实现自定义的metaclass。
__init__ 和 __new__ 最主要的区别在于:
1.__init__ 通常用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性, 做一些额外的操作,发生在类实例被创建完以后。它是实例级别的方法。
2.__new__ 通常用于控制生成一个新实例的过程。它是类级别的方法。
- 什么是Python自省?
- python是如何进行内存管理的?
- 什么是GIL?
- 请简述python的异常处理机制。
- 你是如何如何定位python程序的bug的?在python中如何实现单步执行?
import pdb
pdb.set_trace()
- assert断言有什么用处?
python assert断言是声明其布尔值必须为真的判定,如果发生异常就说明表达示为假。可以理解assert断言语句为raise-if-not,用来测试表示式,其返回值为假,就会触发异常。
使用assert语句是一个很好的习惯. 我们在编写代码的时候, 不知道程序会在什么时候崩溃, 与其让它在深度运行时崩溃, 不如预先测试一个条件, 条件为真方可继续运行, 否则的话就让它立即崩溃掉(先抛出异常信息, 然后退出).
>>> list1 = range(5)
>>> list2 = range(6)
>>> assert len(list1)==len(list2), 'list length not equal'
>>> print 'Hellow World!'
Traceback (most recent call last):
File "<ipython-input-21-5a1acad0de8a>", line 3, in <module>
assert len(list1)==len(list2), 'list length not equal'
AssertionError: list length not equal
>>> list1 = range(5)
>>> list2 = range(5)
>>> assert len(list1)==len(list2), 'list length not equal'
>>> print 'Hellow World!'
Hellow World!
- 元素为字符串的列表如何转变为空格分隔的字符串?
>>> my_dashes = ['_', '_', '_', '_']
>>> print ''.join(my_dashes)
____
>>> print ' '.join(my_dashes)
_ _ _ _
- python中的is操作符是如何进行对比的?
is是判断两个变量是否引用同一个对象,即比较对象的地址;
==则是判断两个引用变量或者引用对象的值是否相等,默认调用对象的_eq_()方法;
- 请写出匹配邮箱地址的正则表达式。
- python如何传递命令行参数的?
Python 提供了 getopt 模块来获取命令行参数。
$ python test.py arg1 arg2 arg3
Python 中也可以使用 sys 的 sys.argv 来获取命令行参数:
-
sys.argv 是命令行参数列表。
-
len(sys.argv) 是命令行参数个数。
注:sys.argv[0] 表示脚本名
$ python test.py arg1 arg2 arg3
参数个数为: 4 个参数。
参数列表: ['test.py', 'arg1', 'arg2', 'arg3']
- 如何理解python中的线程?
1.什么是进程
计算机程序只不过是磁盘中可执行的二进制(或其他类型)的数据。它们只有在被读取到内存中,被操作系统调用的时候才开始它们的生命期。
进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间、内存、数据栈及其它记录其运行轨迹的辅助数据。
操作系统管理在其上运行的所有进程,并为这些进程公平的分配时间,进程也可以通过fork和spawn操作来完成其它的任务。
不过各个进程有自己的内存空间、数据栈等,所以只能使用进程间通讯,而不能直接共享信息。
2.线程的基本概念
线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
3、线程和进程的关系以及区别?
** 进程和线程的关系:**
-
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
-
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
-
(3)处理机分给线程,即真正在处理机上运行的是线程
-
(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体.
进程与线程的区别:
-
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
-
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
-
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
-
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
- 请简述python中的多进程。
多线程类似于同时执行多个不同程序,多线程运行有如下优点:
- 使用线程可以把占据长时间的程序中的任务放到后台去处理。
- 用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
- 程序的运行速度可能加快。
- 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
- 线程可以被抢占(中断)。
- 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。
线程可以分为:
- 内核线程:由操作系统内核创建和撤销。
- 用户线程:不需要内核支持而在用户程序中实现的线程。
Python3 线程中常用的两个模块为:
- _thread
- threading(推荐使用)
thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread" 模块。为了兼容性,Python3 将 thread 重命名为 "_thread"
多进程创建方式
可以归纳为三种:fork,multiprocessing以及进程池Pool。
python删除list中元素的三种方法
- a.pop(index):删除列表a中index处的值,并且返回这个值.
- del(a[index]):删除列表a中index处的值,无返回值. del中的index可以是切片,所以可以实现批量删除.
- a.remove(value):删除列表a中第一个等于value的值,无返回
Python中给List添加元素的4种方法分享
问题来自牛客网,作者:阿药algo
链接:https://www.nowcoder.com/discuss/701758?channel=-1&source_id=profile_follow_post_nctrack