Python学习笔记¶

第十一章 Python高级用法¶

不知不觉已经学习到了这个阶段。很高兴自己的努力已经得到了很多收获。

这次,将会继续学习Python相关内容,学习之前没有涉及到的高级用法。

11.1 六种标准数据类型¶

为了更好地理解,应当知道Python有六种标准数据类型。

根据是否可变进行分类,分类如下:

  • 不可变数据:Number数字、String字符串、Tuple元组
  • 可变数据:List列表、Dictionary字典、Set集合

也就是说,字符串确定了就不能变,元组也是,数字亦然。

如果想改变,就得新建一个这个类型的变量,在新的地址创建新的引用。

而列表、字典、集合不是这样。它们可以随时添加新的内容或者删除已有内容,不改变原有的引用。

11.2 字符串格式化¶

除了之前一直用的format函数,我们概览一下可能遇到的几种字符串格式化方法。

  • %运算符

使用和C语言基本一样的格式字符串。如下:

In [1]:
name = "xjh"
age = 20
score = 114.514
s = "我叫 %s , 今年 %d 岁了,考了 %lf 分。" % (name,age,score)
print(s)
我叫 xjh , 今年 20 岁了,考了 114.514000 分。
  • format函数

这个是在Python 2.6开始提供的一种方法,增强了字符串格式化的功能。

详细的格式化方法在第三章详细讲解过。

  • {<参数序号>:<格式控制标记>}
    • 一共有6个格式控制标记
  • 填充、对齐和宽度
    • 给输出宽度、对齐方式、多了的怎么填充
    • 冒号后,第一个字符是填充内容
    • 第二个字符 < 左对齐 ^ 居中对齐 > 右对齐
    • 之后的数字表示宽度
  • 千位分隔符、精度、类型
    • ,表示数字的千位分隔符
    • .精度 浮点数小数精度或者字符串最长输出长度
    • 类型
      • 整数类型b c d o x X
      • 分别是二进制、unicode字符、十进制、八进制、小写/大写十六进制
      • 浮点数类型 e E f %
      • 分别是小写/大写科学计数法、普通浮点数、百分数

用到的时候随时参考就行。第三章里面有例子。

之前没见过的用法:可以在槽里面直接指定索引:

In [7]:
d = {'name':"xjh","age":"20"}
s = "我叫{name} , 今年{age}岁。".format(**d)
print(s)

ls = ["xjh",20]
# 0[x] 中的0是必须的
s = "我叫{0[0]} , 今年{0[1]}岁。".format(ls)
print(s)
我叫xjh , 今年20岁。
我叫xjh , 今年20岁。

甚至可以直接在槽里引用class的参数。

  • f字符串

这是Python 3.6中新增加的功能。

f开头,然后是字符串,之后的表达式用{}括起来,会直接把计算后的表达式的值放进去。

In [11]:
name = "xjh"
s = f"我叫{name}"
print(s)
print(f"{114.514+1919810}")
print("{0}".format(114.514+1919810))
print(f"{114.514+1919810=}")
我叫xjh
1919924.514
1919924.514
114.514+1919810=1919924.514

这种方式更加简洁明了,不过format的功能更强大。

11.3 推导式¶

这是一个独特的数据处理方式,可以从一个数据序列构建出来新的数据序列。

也就是说,不用for循环,用一句话的方式对序列实现操作,从而生成新的序列。

组合数据类型(集合、元组、列表、字典)均支持。

11.3.1 集合推导式¶

  • 格式如下:{表达式 for item in set}
  • 或者:{表达式 for item in set if condition}

得到的结果是:一个集合,每个元素都是set中的内容进行了表达式的运算后的结果

如果有if,那就是符合if的元素进行运算才得到的结果。

In [12]:
s = {1,1,4,5,1,4}
s_new = {i**2 for i in s if i>=2}
print(s_new)
{16, 25}
In [13]:
a = {x for x in "abdsdkfjfsgavcbdfgs" if x not in "abc"}
print(a)
{'v', 'k', 'f', 's', 'g', 'j', 'd'}
In [16]:
a = {x for x in range(10)}
print(a)
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

11.3.2 元组推导式¶

  • 格式如下:(表达式 for item in set)
  • 或者:(表达式 for item in set if condition)

给我生成一个从1到9的元组:

In [15]:
a = (i for i in range(10))
print(a) # 是一个生成器对象
a = tuple(a)
print(a)
<generator object <genexpr> at 0x106f81850>
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

11.3.3 列表推导式¶

  • 格式如下:[表达式 for item in set]
  • 或者:[表达式 for item in set if condition]

甚至表达式可以是一个有返回值的函数,都行。

过滤掉长度小于或等于3的字符串列表,并将剩下的转换成大写字母:

In [17]:
names = ['Bob','Tom','alice','Jerry','Wendy','Smith']
new_names = [name.upper() for name in names if len(name)>3]
print(new_names)
['ALICE', 'JERRY', 'WENDY', 'SMITH']

11.3.4 字典推导式¶

  • 格式如下:{key:value for value in 组合数据类型}
  • 或者:{key:value for value in 组合数据类型 if 条件}

生成一个网站名到长度的字典:

In [18]:
web_list = ['Google','Apple','Bilibili','Wikipedia','Caiguu']
new_dict = {key:len(key) for key in web_list}
print(new_dict)
{'Google': 6, 'Apple': 5, 'Bilibili': 8, 'Wikipedia': 9, 'Caiguu': 6}

生成一个数字-平方数的字典:

In [19]:
num = [1,2,3,4,5,6,7,8,9,10]
num_dict = {n:n**2 for n in num}
print(num_dict)
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}

11.4 模块 module¶

11.4.1 模块¶

一个.py文件就是一个模块,module。

模块可以使用import语句进行引用。引用标准库就是很典型的例子。

In [20]:
import sys

print('命令行参数如下:')
for i in sys.argv:
   print(i)

print('\n\nPython 路径为:', sys.path, '\n')
命令行参数如下:
/opt/homebrew/lib/python3.10/site-packages/ipykernel_launcher.py
-f
/Users/caiguu/Library/Jupyter/runtime/kernel-ed58cdb5-333b-4dc1-8c32-30ce0c9f0f36.json


Python 路径为: ['/Applications/PyCharm.app/Contents/plugins/python/helpers-pro/jupyter_debug', '/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev', '/Users/caiguu', '/opt/homebrew/Cellar/python@3.10/3.10.1/Frameworks/Python.framework/Versions/3.10/lib/python310.zip', '/opt/homebrew/Cellar/python@3.10/3.10.1/Frameworks/Python.framework/Versions/3.10/lib/python3.10', '/opt/homebrew/Cellar/python@3.10/3.10.1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/lib-dynload', '', '/opt/homebrew/lib/python3.10/site-packages', '/opt/homebrew/opt/python-tk@3.10/libexec', '/opt/homebrew/lib/python3.10/site-packages/IPython/extensions', '/Users/caiguu/.ipython'] 

一个模块只会被导入一次,不管你执行了多少次。

会从sys.path中搜索对应的模块。如果没有,就会报错。

函数也可以赋值,只不过是给了个别名而已。

每个模块都有独立的符号表,全局变量只在模块内部是全局的。

  • 关于__name__属性:

如果你import了一个包,那么会自动执行其中的主程序。

如果你只希望自己才执行主程序,那么请使用:

if __name__ == '__main__':
    print("我被自己调用了")
else:
    print("我被其他人引用了")

这样,自己运行自己的时候,执行main里面的。如果被引用,执行其他的。

每个模块都有一个__name__属性,当其值是'__main__'时,表明该模块自身在运行,否则是被引入。

  • dir()函数

可以用dir函数来找到模块内定义的所有名称。

是一个字符串列表。

11.4.2 包¶

文件夹就是包!用点来分隔,和Java中差不多。

导入一个包的时候,会根据sys.path的目录来寻找对应的子目录。

目录只有包含了__init__.py才会被认作是一个包。

所以from package import 包就是这个意思!

如果你想要指定from package import *的时候导入谁,在init文件里边加一个__all__列表,字符串的形式告诉需要导入哪些即可。

11.5 迭代器和生成器¶

11.5.1 迭代器¶

迭代器很强大!能够用来访问集合元素。

迭代器是一个对象,能够记住遍历的位置。它只能前进不能后退。

迭代器需要掌握两个方法:iter()和next()

字符串、列表、元组对象都可以用于创建迭代器。

In [27]:
my_list = [1,1,4,5,1,4]
it = iter(my_list)
print(it)
print(next(it))
print(next(it))
print(next(it))
print(next(it))

it = iter(my_list)
for i in it:
   print(i,end=",")
<list_iterator object at 0x105219c00>
1
1
4
5
1,1,4,5,1,4,
In [26]:
import sys         # 引入 sys 模块

my_list=[1,2,3,4]
it = iter(my_list)    # 创建迭代器对象

while True:
    try:
        print (next(it))
    except StopIteration:
        print("直接来吧!我滴任务完成啦!")
        break
1
2
3
4
直接来吧!我滴任务完成啦!
  • 自制迭代器

如果实现了__iter__()和__next__(),那么就是一个迭代器。

__iter__():返回一个特殊的迭代器对象,实现了__next__()并通过StopIteration异常标识迭代的完成。

__next__():返回下一个迭代器对象。

我们创建一个迭代器,数字的迭代器。初始值为1,逐步增加1。

为了防止无限迭代,我们指定20次抛出StopIteration异常。

In [2]:
class MyNumbers:
   def __iter__(self):
      self.a = 1
      return self
   def __next__(self):
      if self.a > 20:
         raise StopIteration
      else:
         #返回当前的,同时位置+1
         x = self.a
         self.a += 1
         return x

myClass = MyNumbers()
myIter = iter(myClass)

for i in myIter:
   print(i)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

11.5.2 生成器¶

使用了 yield 的函数被称为生成器(generator)。

生成器只能用于迭代。

调用一个生成器函数,返回的是一个迭代器对象。

例:使用生成器实现斐波那契数列。

In [12]:
def fibonacci(n): # 生成器函数 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if counter > n:
            return
        yield a
        a, b = b, a + b
        counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成

while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        break
0 1 1 2 3 5 8 13 21 34 55 

在调试的时候,f是一个generator类型。

遇到了yield就会返回yield那里的表达式。等到了next之后,回到刚才的位置,继续执行。

11.6 类的访问权限控制¶

  • 首先,Python支持多继承。

  • 类的私有属性

    • 两个下划线开头,声明该属性为私有
    • 不能在类外部访问
    • 类内部访问需要self.__attribute
  • 类的私有方法
    • 两个下划线开头,声明该方法为私有
    • 类内部才能调用
    • 类内部访问需要self.__method
In [26]:
class Car:
   def __init__(self,name):
      self.name = name
      self.__price = 0

class GasCar(Car):
   def __init__(self,name):
      super().__init__(name)
      self.__price = 100
      # super().__price = 150 会报错
      self.a = 15
      print("hehe")


c = GasCar("olg")

print(c._GasCar__price)
hehe
100

经过调试,发现一个细节。私有成员在子类中是可以访问到的。

它们有各自的签名,比如_Car__price和_GasCar_price

而且如果子类试图访问父类的私有变量,是不允许的!

所以它客观存在,但是访问不到。

但是可以从外界暴力访问。但是不要这么干!

最好的办法:通过函数封装起来就好啦!

11.7 函数参数详解¶

11.7.1 可变类型与不可变类型¶

  • 函数传递的参数总共也逃不出六种类型。根据是否可变分为了两大类:
  • 可变类型:列表、字典、集合
  • 不可变类型:字符串、元组、数字
  • 区别在哪儿?不可变类型做更改,相当于丢弃原来的,指向新的
  • 可变类型做更改,相当于还指向它,但是改了改内容

为什么区分?因为对于不同的类型,操作是不一样的。

  • 不可变类型:类似于值传递。只传递值,不影响传进去的对象本身。
  • 可变类型:类似于引用传递,会改变对象本身。

让我们康康吧!

In [28]:
def change(a):
   print(id(a))
   a = 114514
   print(id(a))

a = 1
print(id(a))
change(a)
print(id(a))
print(a)
4304617712
4304617712
4566923920
4304617712
1
In [29]:
def changelist(li):
   li.append("哼哼哼")
   li.append("啊啊啊啊啊啊啊啊啊")

list = ["114514","1919810"]
changelist(list)
print(list)
['114514', '1919810', '哼哼哼', '啊啊啊啊啊啊啊啊啊']

11.7.2 四种参数¶

参数分为四类:必须参数、关键字参数、默认参数、不定长参数

  • 必须参数:必须按照正确的顺序传入正确的数量
In [30]:
def print_str(str):
   print(str)

print_str("哼哼哼 啊啊啊啊啊啊啊啊")
哼哼哼 啊啊啊啊啊啊啊啊
  • 关键字参数:使用关键字参数允许顺序不一样,因为解释器识别的出来
In [31]:
def print_add_str(str1,str2):
   s = str1+str2
   return s

print(print_add_str("114514","1919810"))
print(print_add_str(str2="1919810",str1="114514"))
1145141919810
1145141919810
  • 默认参数:如果没有传入,可以使用默认参数
In [32]:
def age(age=18):
   return age

print(age(10))
print(age())
10
18
  • 不定长参数:如果你需要不知道多少个参数,那就是不定长参数

传进去以后是一个元组

In [35]:
def print_info( arg1, *var_tuple ):
   # 打印任何传入的参数
   print ("输出: ")
   print (arg1)
   print (var_tuple)

# 调用print_info函数
print_info( 70, 60, 50, 114514, 1919810 )

print_info( 80)
输出: 
70
(60, 50, 114514, 1919810)
输出: 
80
()

如果不传入,那就是个空的元组。

还有一种,两个星号,这样会以字典的形式导入。

In [39]:
# 可写函数说明
def print_info( arg1, **var_dict ):
   print ("输出: ")
   print (arg1)
   print (var_dict)

# 调用print_info 函数
print_info(1, a=2,b=3)

print_info(1,a114514 = 2, a1919810 = 3)
输出: 
1
{'a': 2, 'b': 3}
输出: 
1
{'a114514': 2, 'a1919810': 3}

如果单独出现星号,那么后面的参数只能通过关键字才能传入。

In [43]:
def olg(a,b,*,c,d,e,f):
   print(f"{a,b,c,d,e,f}")

olg(1,1,d=5,c=4,f=4,e=1)
olg(1,1,4,5,1,4)
(1, 1, 4, 5, 1, 4)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/var/folders/nc/rrdwnt790c13mtwryb6prjfc0000gn/T/ipykernel_25322/810105125.py in <module>
      3 
      4 olg(1,1,d=5,c=4,f=4,e=1)
----> 5 olg(1,1,4,5,1,4)

TypeError: olg() takes 2 positional arguments but 6 were given
  • 强制位置参数:Python 3.8新增
    • 不允许使用关键字参数
    • 必须使用位置参数
In [45]:
def f(a, b, /, c, d, *, e, f):
    print(a, b, c, d, e, f)

f(1,2,3,4,e=5,f=6)
f(1,2,c=3,d=4,e=5,f=6)
1 2 3 4 5 6
1 2 3 4 5 6

11.8 解包¶

将元组(*)解包成位置参数,将字典(**)解包成关键字参数

In [46]:
def print_tuple(a,b,c):
    print('a:',a,end='  ')
    print('b:',b,end='  ')
    print('c:',c)
args=(1,11,111)
print_tuple(*args)
a: 1  b: 11  c: 111
  • 可以看到,利用*args,可以把参数变成一个元组,内容就是元组的值
  • 所以,可以利用*来完成把自己一个元组变成一系列的元组参数
In [53]:
def print_tuple(a,b,c,*olggg):
    print('a:',a,end='  ')
    print('b:',b,end='  ')
    print('c:',c)
    print('olg', olggg)
    print('olg', *olggg)

args=(1,11,111,114514,1919810,666)
print_tuple(*args)
a: 1  b: 11  c: 111
olg (114514, 1919810, 666)
olg 114514 1919810 666

对于字典,同样可以使用**进行解包,解包为value参数

对于*解包字典,就是解包为key参数

In [58]:
def print_dict(a,b,c):
    print('a:',a,end='  ')
    print('b:',b,end='  ')
    print('c:',c)

args={'a':3,'b':2,'c':1}
print_dict(**args)
print_dict(*args)
a: 3  b: 2  c: 1
a: a  b: b  c: c

巧妙利用,去掉一个最高分和最低分:

将列表解包为多个数字参数

In [60]:
score = [80,84,90,92,98,95,93,100]
score.sort()
print(score)
a , *b , c = score
print(b)
[80, 84, 90, 92, 93, 95, 98, 100]
[84, 90, 92, 93, 95, 98]

所以经过如上的所有探索,可以暂时认为*就是扩展序列参数为多个变量

对于字典,**就是解包为值参数,*就是解包为关键字参数

11.9 结语¶

经过数天的Python学习,对于基础语法体系和一些高级应用都有了很好的理解。

希望能够继续努力,不断提升自己的能力。

如果以后遇到了需要整理和总结的内容,也会更新到博客上。

至此,Python的基础学习阶段完结撒花!

See you!