1. 列表(list)和元组(tuple)有什么区别?
- 列表是可变的,创建后可以对其进行修改;元组是不可变的,元组一旦创建,就不能对其进行更改。
- 列表表示的是顺序。它们是有序序列,通常是同一类型的对象。比如说按创建日期排序的所有用户名,如 [“Seth”, “Ema”, “Eli”];元组表示的是结构。可以用来存储不同数据类型的元素。比如内存中的数据库记录,如(2, “Ema”, “2020–04–16”)(#id, 名称,创建日期)。
2. “is”和“==”有什么区别?
“is”用来检查对象的标识(id),而“==”用来检查两个对象是否相等。
3. 如何创建一个字典?
- 使用花括号{}和冒号:来手动创建字典,并指定键值对:
my_dict = {"key1": value1, "key2": value2, "key3": value3}
- 使用dict()函数创建字典,传入键值对作为参数:
my_dict = dict(key1=value1, key2=value2, key3=value3)
- 通过列表或元组生成字典,其中列表或元组包含了键值对的子序列:
my_dict = dict([(key1, value1), (key2, value2), (key3, value3)])
- 使用字典推导式创建字典,类似于列表推导式的用法:
my_dict = {key: value for key, value in iterable}
其中iterable是可迭代对象,可以是列表、元组、集合等。
4. 如何进行字符串插值?
- 使用f-string(格式化字符串字面量):
name = "Alice"
age = 25
print(f"My name is {name} and I'm {age} years old.")
- 使用format()方法:
name = "Alice"
age = 25
print("My name is {} and I'm {} years old.".format(name, age))
- 使用百分号%进行格式化(旧式字符串格式化):
name = "Alice"
age = 25
print("My name is %s and I'm %d years old." % (name, age))
- 使用string模块中的Template对象拼接:
from string import Template
first_name = "Alice"
last_name = "Smith"
# 创建一个Template对象,使用$符号作为占位符
template = Template("My name is ${first_name} ${last_name}.")
# 使用substitute方法进行替换
result = template.substitute(first_name=first_name, last_name=last_name)
print(result)
5. 双下划线和单下划线的区别?
- 单下划线
_var
:单下划线前缀的标识符表示某个属性是“内部实现细节”或“私有的”,建议不要在类的外部使用和访问它。 - 双下划线
__var
:双下划线用于名称修饰,即将属性或方法前面的双下划线作为前缀,使其成为私有成员。私有成员只能在类内部访问,无法在类外部直接访问;名称改写。双下划线还可用于名称改写(name mangling),即将属性或方法前面的双下划线和类名组合起来,以防止子类意外覆盖父类的属性或方法。
6. 自省的方法?
自省(introspection)是指在程序运行时能够获取和查询对象的信息,包括对象的类型、属性、方法和其他相关信息。
type()
函数:type(obj)
可以返回obj
的类型;dir()
函数:dir(obj)
返回一个列表,列出对象obj
所有可用的属性和方法的名称。getattr()
函数:getattr(obj, name[, default])
返回对象 obj 的属性或方法name
的值。如果属性或方法不存在,可以提供默认值default
,否则会抛出AttributeError
异常。hasattr()
函数:hasattr(obj, name)
判断对象obj
是否具有属性或方法name
,返回布尔值。callable()
函数:callable(obj)
判断对象obj
是否可调用(即可以像函数一样调用),返回布尔值。
7. 文件可以使用for循环进行遍历?
Python 中的文件对象支持迭代器协议,因此可以使用 for 循环逐行读取文件内容。
with open('example.txt', 'r') as f:
for line in f:
print(line, end='') # 注意末尾的换行符
在这个例子中,我们打开名为 ‘example.txt’ 的文件,并使用 for 循环依次遍历文件中的每一行。由于文件对象是可迭代的,for 循环可以对其进行迭代,从而读取文件内容。
需要注意的是,在使用 for 循环遍历文件时,文件指针会自动向后移动,因此不需要手动调用 readline()
或 readlines()
方法来读取每一行的内容。同时,由于文件中每一行末尾都包含一个换行符,因此在输出时需要将末尾换行符去掉,否则会出现多余的空行。
8. 迭代器和生成器的区别?
迭代器:
class FibonacciIterator:
def __init__(self, n):
self.n = n
self.current, self.next = 0, 1
def __iter__(self):
return self
def __next__(self):
if self.n > 0:
value = self.current
self.current, self.next = self.next, self.current + self.next
self.n -= 1
return value
else:
raise StopIteration
fibonacci = FibonacciIterator(10)
for num in fibonacci:
print(num)
生成器:
def fibonacci_generator(n):
current, next = 0, 1
while n > 0:
yield current
current, next = next, current + next
n -= 1
fibonacci = fibonacci_generator(10)
for num in fibonacci:
print(num)
9. *args and **kwargs?
*args
用于传递可变数量的非关键字参数给一个函数。它允许你在函数定义时指定一个可变数量的参数,可以传递任意多个参数值,这些参数值将被收集到一个元组(tuple)中。在函数内部,可以通过遍历该元组来访问这些参数值。
python
def print_args(*args):
for arg in args:
print(arg)
print_args(1, 2, 3, "hello", [4, 5])
1
2
3
hello
[4, 5]
**kwargs
用于传递可变数量的关键字参数给一个函数。它允许你在函数定义时指定一个可变数量的关键字参数,可以传递任意多个键值对,这些键值对将被收集到一个字典(dictionary)中。在函数内部,可以通过访问该字典的键值对来获取参数值。
def print_kwargs(**kwargs):
for key, value in kwargs.items():
print(key, value)
print_kwargs(name="Alice", age=25, country="USA")
name Alice
age 25
country USA
10. 装饰器怎么用?装饰器解释下,基本要求是什么?
在Python中,函数和类可以视为对象,所以装饰器实际上就是一个能够接收函数或类作为输入,并返回一个新函数或类的函数。这个新函数或类通常会在原始函数或类的基础上做一些额外的操作或增强功能。
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print("Function {} took {} seconds to run.".format(func.__name__, end_time - start_time))
return result
return wrapper
@timer_decorator
def countdown(n):
while n > 0:
n -= 1
countdown(10000000)
Function countdown took 0.46547579765319824 seconds to run.
在这个例子中,我们定义了一个名为timer_decorator的装饰器函数,该函数接受一个函数func作为参数,并返回一个新函数wrapper。wrapper函数记录了函数func的运行时间,并打印出运行时间,然后将结果返回给调用者。
通过在函数定义前加上@timer_decorator
语法,我们将countdown函数传递给timer_decorator装饰器,使其在函数运行前自动应用装饰器。
- 装饰器函数名应该以decorator结尾,例如log_decorator、timer_decorator等。
- 装饰器函数应该接受一个函数作为参数,并返回一个新函数。
- 新函数应该能够接受任意数量和类型的参数,并转发给原始函数。
- 新函数应该在调用原始函数前执行额外的操作或增强功能。
- 新函数应该返回原始函数的结果,并在必要时进行转换或修正。
几个常见的内置装饰器:
@staticmethod
:将一个方法转变为静态方法。静态方法不需要访问实例的状态,因此不会传递self参数。可以通过类名直接调用静态方法,而无需创建类的实例对象。
class MyClass:
@staticmethod
def my_method():
print("This is a static method.")
MyClass.my_method() # 直接通过类名调用静态方法
@classmethod
:将一个方法转变为类方法。类方法的第一个参数通常被约定为cls,表示类本身,而不是实例。类方法可以访问和修改类的状态,并且也可以被子类继承和覆盖。
class MyClass:
count = 0
@classmethod
def increase_count(cls):
cls.count += 1
@classmethod
def get_count(cls):
return cls.count
MyClass.increase_count() # 调用类方法增加计数
print(MyClass.get_count()) # 获取计数值
@property
:将一个方法转变为属性访问。使用@property装饰器可以在访问属性时执行一些额外的代码,例如进行数据验证、计算等。可以通过直接访问属性的方式来调用被装饰的方法,无需使用括号。
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self): # 将方法转变为属性访问
return self._radius
@radius.setter
def radius(self, value):
if value >= 0:
self._radius = value
else:
raise ValueError("Radius cannot be negative.")
circle = Circle(5)
print(circle.radius) # 直接访问属性
circle.radius = 10 # 使用属性赋值
11. 作用域的类型有哪些?
- 全局作用域:全局作用域是在整个程序中都可访问的范围,定义在全局作用域中的变量可以在程序任何地方被引用。
- 局部作用域:局部作用域是在某个特定代码块中可访问的范围,例如函数或类的方法。在局部作用域中定义的变量只能在该代码块内部使用。
- 嵌套作用域:嵌套作用域是指嵌套函数中的作用域。嵌套函数可以访问其外部函数的变量,形成了一个闭包。
- 内置作用域:内置作用域是Python解释器预先定义的一些名称的作用域,可以在程序的任何地方使用这些名称。例如,内置函数和模块级别的名称就属于内置作用域。
12. 解释 map 函数的工作原理?
map()
是 Python 内置的一个高阶函数,它接受一个函数和一个可迭代对象(如列表、元组等),并对可迭代对象中的每个元素都应用该函数,最终返回一个新的可迭代对象(通常是一个迭代器),其中包含了对原可迭代对象中每个元素应用函数后的结果。
# 定义一个函数,将给定的数字平方
def square(x):
return x ** 2
# 使用 map() 函数对列表中的每个元素进行平方操作
numbers = [1, 2, 3, 4, 5]
result = map(square, numbers)
# 打印结果
print(list(result)) # 输出 [1, 4, 9, 16, 25]
13. 解释 filter 函数的工作原理?
filter() 是 Python 内置的一个高阶函数,它接受一个函数和一个可迭代对象(如列表、元组等),并通过对可迭代对象中的每个元素应用该函数的结果(通常为布尔值)进行过滤,最终返回一个新的可迭代对象(通常是一个迭代器),其中只包含符合条件的元素。
# 定义一个函数,筛选出列表中的偶数
def is_even(x):
return x % 2 == 0
# 使用 filter() 函数对列表中的元素进行筛选
numbers = [1, 2, 3, 4, 5]
result = filter(is_even, numbers)
# 打印结果
print(list(result)) # 输出 [2, 4]
14. Python 是按引用调用还是按值调用?
Python的函数参数既支持按值传递也支持按引用传递。具体来说,当向函数传递一个可变对象(如列表、字典等)的引用时,函数可以改变该对象的内容,因为它可以通过引用访问该对象,并修改其内容。而当向函数传递一个不可变对象(如数字、字符串、元组等)的引用时,函数无法改变该对象的内容,因为它不能通过引用访问该对象并修改其内容。
# 定义一个函数,尝试修改传入的列表
def modify_list(lst):
lst[0] = 'b'
lst.append('d')
# 调用函数
my_list = ['a', 'c']
modify_list(my_list)
# 打印原始列表和函数修改后的结果
print(my_list) # 输出 ['b', 'c', 'd']
# 定义一个函数,尝试替换传入的字符串
def replace_str(s):
s = 'world'
# 调用函数
my_str = 'hello'
replace_str(my_str)
# 打印原始字符串和函数运行后的结果
print(my_str) # 输出 'hello'
15. 深拷贝和浅拷贝的区别?
- 浅拷贝是创建一个新对象,该对象与原始对象共享内部对象的引用。也就是说,浅拷贝只复制了对象的引用,并没有复制内部的子对象。因此,当修改浅拷贝的对象时,原始对象和浅拷贝对象的内部子对象都会发生变化。
- 深拷贝是创建一个全新的对象,该对象及其内部包含的所有子对象都是原始对象的副本。深拷贝会递归复制整个对象的内部结构,而不仅仅是复制引用。因此,当修改深拷贝的对象时,原始对象和深拷贝对象以及它们的内部子对象都是相互独立的,互不影响。
16. 列表和数组有什么区别?
- 数据类型:列表可以包含不同类型的元素,例如整数、浮点数、字符串等。而数组通常只能包含相同类型的元素,如整数数组、浮点数数组等。在 Python 中,使用第三方库
numpy
提供的ndarray
类型来创建数组。 - 内存占用和性能:列表是动态的,可以根据需要动态调整大小,并且可以随意插入、删除元素。这种灵活性带来了一些额外的内存开销和性能损失。而数组是在创建时就需要指定大小,且元素类型相同,因此在存储和处理大量数据时更有效率。
- 数学运算:使用
numpy
库创建的数组支持矢量化操作,您可以对整个数组进行数学运算,而不需要显式地遍历数组中的每个元素。这种矢量化操作使得处理数值计算和科学计算更加高效和方便。列表不直接支持这种矢量化操作,因此在大规模数值计算时效率较低。
import numpy as np
# 创建列表
my_list = [1, 2, 3, 4, 5]
# 创建数组
my_array = np.array([1, 2, 3, 4, 5])
# 输出类型
print(type(my_list)) # <class 'list'>
print(type(my_array)) # <class 'numpy.ndarray'>
# 数学运算
print(my_list * 2) # [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
print(my_array * 2) # [ 2 4 6 8 10]
17. 解释下闭包?
具体来说,当一个内部函数引用了外部函数的变量,并且该内部函数在外部函数之外被调用时,就形成了一个闭包。闭包可以捕获和保存引用的外部变量的状态,即使外部函数已经执行完毕,内部函数仍然可以访问和操作这些变量。
def outer_function(x):
def inner_function(y):
return x + y
return inner_function
closure = outer_function(10)
result = closure(5)
print(result) # 输出 15
18. 介绍下 python 垃圾回收机制?
Python采用自动垃圾回收(Garbage Collection)机制来管理内存。垃圾回收是一种自动化的内存管理技术,它能够检测和释放不再使用的内存资源,从而帮助开发者减轻手动内存管理的负担。
Python的垃圾回收机制主要基于引用计数(Reference Counting)和循环垃圾回收(Cycle Detection and Collection)两个方面。
- 引用计数:Python中的每个对象都有一个引用计数器,用于记录当前对象被引用的次数。当对象被引用时,引用计数器加1;当引用对象的引用失效时,引用计数器减1。当引用计数器为0时,表示该对象不再被引用,即为垃圾对象。Python会定期扫描内存中的对象,将引用计数器为0的对象进行释放。
- 循环垃圾回收:引用计数机制无法处理循环引用的情况,即多个对象之间形成了一个循环引用链,导致这些对象的引用计数器始终不为0,无法被回收。为了解决这个问题,Python引入了循环垃圾回收机制。它通过标记-清除算法来检测和回收循环引用产生的垃圾对象。具体而言,Python会周期性地执行垃圾回收操作,在这个过程中,它会标记所有可以访问到的对象,并清除那些不可访问的对象。
19. + 和 join 的区别?
+
操作符:+
操作符是用于字符串的连接操作,可以将两个或多个字符串连接成一个新的字符串。
str1 = "Hello"
str2 = "World"
result = str1 + " " + str2
print(result) # 输出 "Hello World"
join()
方法:join()
方法是字符串对象的方法,它接受一个可迭代对象作为参数,并将该可迭代对象中的字符串按照指定的分隔符连接起来。
my_list = ["Hello", "World"]
result = " ".join(my_list)
print(result) # 输出 "Hello World"
join()
方法更适合用于连接大量字符串的场景,因为它只需要创建一个新的字符串对象,并一次性地将所有字符串连接起来,相比于多次使用 +
操作符连接字符串,可以提升性能。
20. any 和 all 如何工作?
any()
和 all()
是 Python 内置的两个函数,用于对可迭代对象进行逻辑判断。
any()
:当可迭代对象中至少有一个元素满足条件时,返回True
;否则,返回False
。
nums = [2, 4, 6, 8]
result = any(num % 2 == 0 for num in nums)
print(result) # 输出 True
all()
:当可迭代对象中所有元素都满足条件时,返回True
;否则,返回False
。
nums = [2, 4, 6, 7]
result = all(num % 2 == 0 for num in nums)
print(result) # 输出 False
21. 什么是pickling?
在 Python 中,Pickling 是指将 Python 对象序列化为二进制数据的过程,也可以将其称为对象持久化或对象序列化。反过来,将序列化后的二进制数据恢复成原始 Python 对象的过程则称为 Unpickling。
import pickle
data = {'name': 'Alice', 'age': 25, 'score': [90, 80, 95]}
# Pickling
with open('data.pickle', 'wb') as f:
pickle.dump(data, f)
# Unpickling
with open('data.pickle', 'rb') as f:
data_loaded = pickle.load(f)
print(data_loaded) # 输出 {'name': 'Alice', 'age': 25, 'score': [90, 80, 95]}
22. 解释下 reduce 函数的原理?
reduce()
函数是 Python 中的一个内置函数,位于 functools 模块中。它的作用是对一个序列中的元素按照指定的函数进行累积操作,返回一个最终的结果。
from functools import reduce
# 定义一个加法函数
def add(x, y):
return x + y
# 序列
numbers = [1, 2, 3, 4, 5]
# 使用 reduce 函数对序列中的元素进行累加操作
result = reduce(add, numbers)
print(result) # 输出结果为 15
23. 解释下 lambda 函数的原理?
lambda
函数是 Python 中的一种匿名函数,也被称为"lambda 表达式"。它与常规的函数定义不同,可以在需要函数的地方使用一个简洁的表达式来代替。lambda
函数通常用于简单的函数操作,特别是在需要定义一些简短的、临时的函数时非常有用。
# 定义一个 lambda 函数,实现两数相加
add = lambda x, y: x + y
# 调用 lambda 函数进行计算
result = add(3, 5)
print(result) # 输出结果为 8
24. 谈下 python 的 GIL ?
GIL 是python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行。
多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大
25. 列表去重的方法有哪些?
- 使用集合去重::将列表转换为集合,去除重复元素后再转换回列表。
# 定义一个包含重复元素的列表
my_list = [1, 2, 3, 2, 4, 3, 5]
# 转换为集合去重
my_set = set(my_list)
new_list = list(my_set)
# 打印去重后的列表
print(new_list)
- 使用循环遍历去重:遍历列表中每个元素,将未出现过的元素添加到新列表中。
python
# 定义一个包含重复元素的列表
my_list = [1, 2, 3, 2, 4, 3, 5]
# 去重
new_list = []
for element in my_list:
if element not in new_list:
new_list.append(element)
# 打印去重后的列表
print(new_list)
- 使用列表推导式去重:利用列表推导式的特性去重。
# 定义一个包含重复元素的列表
my_list = [1, 2, 3, 2, 4, 3, 5]
# 去重
new_list = list(set([element for element in my_list]))
# 打印去重后的列表
print(new_list)
26. 一个类如何继承Python的另一个类?
在 Python 中,类的继承是通过在定义类时指定基类来实现的。要使一个类继承另一个类的属性和方法,可以将基类作为参数传递给子类的类定义。
class ParentClass:
def __init__(self):
self.parent_property = "Parent Property"
def parent_method(self):
print("Parent Method")
class ChildClass(ParentClass):
def __init__(self):
super().__init__() # 调用父类的初始化方法
self.child_property = "Child Property"
def child_method(self):
print("Child Method")
27. 如何按字母顺序对字典进行排序?
在 Python 中,可以使用 sorted 函数对字典进行按键(key)排序。默认情况下,sorted
函数按照字母表顺序(ASCII 码值顺序)对键进行排序。
# 定义一个字典
my_dict = {'cat': 2, 'dog': 1, 'bird': 3}
# 对字典按键排序
sorted_dict = dict(sorted(my_dict.items()))
# 打印排序后的字典
print(sorted_dict)
{'bird': 3, 'cat': 2, 'dog': 1}
需要注意的是,上述代码只对字典的键进行了排序,如果需要对值进行排序,可以使用类似的方法,将 items() 中的键值对按照值的大小进行排序,然后再提取键或值即可。
# 按照字典的值进行排序
sorted_dict = dict(sorted(my_dict.items(), key=lambda x: x[1]))
# 打印排序后的字典
print(sorted_dict)
{'dog': 1, 'cat': 2, 'bird': 3}