为什么有一些Python入门书不教class,yield,self之类的方法?
作者:卡卷网发布时间:2025-01-19 19:09浏览数量:80次评论数量:0次
一、必要性
正好问主说的这本书 —— 《Python编程快速上手——让繁琐工作自动化》,也是我当年入门 Python 时,最喜欢看的两本书之一。
我和问主一样,也不是科班程序员,学 Python 之目的,除个人兴趣外,也是奔着提高工作效率去的。
想当年,我没有问主那么多困惑,顺着大众评价,就认准了上述两本入门书(没有再去拓展其他的入门书籍和视频)。因此,即便看完书,实践了一段时间后,我也不知道 yield
的存在。对 class
里的 self
和 init
更是一知半解。
直到工作需要,经常上手,用多了后,这些(yield、class
)东西才有了点眉目。
1、函数式编程速成
严格来说《Python编程快速上手——让繁琐工作自动化》这本书,是一本 函数式编程的速成书。它的主要编写目的,是让读者快速了解 Python 的变量、控制(流程和循环)、函数体系,好让初学者可以快速上手开发一些实用的小工具。
所以、不只是 class
(含 self
和 init
)、yield
被略过了,即使是比较实用的 lambda
、map
、reduce
、filter
、sorted
等内置高级函数,也是能省则省。
问主以为这就完了?远远不是,该书连基本的序列知识(list、tuple、set、dict
)都是能省则省( set
只字未提)。
很多人觉得神书《Python 编程从入门到实践》比 《Python 编程快速上手——让繁琐工作自动化》全,也是误解。《Python 编程从入门到实践》除了蜻蜓点水地提及 class
之外,对 self
和 __init__
压根就没有做深入辨析。而且基础序列同样缺失 set
,更不用提问主说的 yield
了。
2、初学者疑惑
class
是面向对象编程范式,如果真要讨论,那《Python 编程快速上手——让繁琐工作自动化》整本书的篇幅,都不够!
况且这些内容也不符合该书 速成 之主旨(初学者面向过程编程范式都没掌握好,就直接引入面向对象,不符合初学者的理解规律)。
至于引入 yield
这种处理生成器函数的关键字,就更不可能了。因为,这是处理大数据时,才该考虑节的细节(节约内存)。况且这本书的应用案例中,根本涉及不到这一需求。强制引入,只会让初学者不适。
最后,我个人理解《Python 编程快速上手——让繁琐工作自动化》第一部分介绍的基础知识(前 6 章),都是紧扣第二部分(第 8-16 章)应用的。主打一个短平快速成。
二、yield 概念
既然问主对 class
、yield
有想法了,那我就先从简单一点的 yield
开吹。
1、官话
Python 的yield
是一个用于在生成器函数(一种特殊类型的函数,它可以在需要时生成一系列值,而不是一次性生成并存储在内存中)中,产生值的关键字。
也就是说 yield
可以将值产生给调用方,并在生成器函数的执行中,保持状态。
是不是很绕,初学者听到这话,估计瞬间就能被劝退。
2、白话
下面说说我的理解,当代码需要处理大量数据时,yield
允许生成器在迭代过程中,动态产生值(不需要一次性,将所有的值加载到内存中),从而节省内存空间。
三、yield 示例
问主可以读读我的理解,看是不是比官话通俗?
不过,对初学者来说,不论官话、白话,都含有大量“劝退”成分。
如果问主意志力够坚定,硬要刚 yield
,可以用我的理解,结合如下的示例自己悟。至于能悟多少,那就全赖自身的 Python 基础了。
1、无限数据生成
def infinite_sequence():
num = 0
while True:
num += 1
inf_seq = infinite_sequence()
for code in inf_seq: # 由于 inf_seq 在无限自增,所以程序根本进行不到这一步
print(code)
比如,上述代码就是个无限生成数据的大坑,按照正常的 while
循环来理解,inf_seq
就会成为一个自增死循环的容器(变量)。
如果运行这段代码,inf_seq
就会爆体而亡(内存耗尽,被卡死)。
这时,yield
这个神器出现了。
def infinite_sequence():
num = 0
while True:
yield num
num += 1
inf_seq = infinite_sequence()
for _ in range(30): # 指定迭代次数
print(next(inf_seq)) # 允许生成器在迭代过程中,动态产生值(不需要一次性,将所有的值加载到内存中),从而节省内存空间
有了它,inf_seq
再也不用无脑自增了(不会因为内存耗尽,而卡死)。它会等着 for
遍历中 range
指定的迭代数,来动态、优雅地产生值。
是不是很神奇呢?
因此,Python 中 yield
就经常被拿来生成斐波那契,这种无限序列。
def fibonacci():
code_1, code_2 = 0, 1
while True:
yield code_1
code_1, code_2 = code_2, code_1 + code_2
fib = fibonacci()
for _ in range(100):
print(next(fib))
如果没有yield
,那斐波那契这种变态的无限序列,就会把fib
这个容器(变量)撑爆。
2、读取大文件
这个比较容易理解,就以读取文本文件中的行数来举例吧。
比如,我们要读取的《红楼梦》,是一个 红楼梦.txt
的文本,那这个 红楼梦.txt
文件的字符行数,一定也是海量的。
如果要直接读取,很可能撑爆内存。
因此,yield
就可以大显神威了。
def read_long_txt(file_path):
with open(file_path, 'r') as f:
for line in f:
yield line.strip()
hongloumeng = read_long_txt('hongloumeng.txt')
for _ in range(5):
print(next(hongloumeng))
有了它(按需读取),就再也不用担心内存耗尽了。
四、class 中的 init 和 self
至于,class
中 __init__
和 self
辨析,我之前也简单总结过,不过,这些知识对于 Python 基础不够厚的初学者来说,如同嚼蜡。
1、没有 __init__ 方法的 class 和不带 self 变量的 def
使用 Python 定义 class
时,不写 __init__
方法可行吗?答案是可以的。
class Example:
def __init__(self, avg): # 去掉 init 也是可以的
比如,我们写一个小狗的类,小狗有名字,也会跑。
class Dog:
def dog_name(self, name):
print(f'The dog`s name is {name}')
def dog_run(self):
print(f'Tom run')
tom = Dog()
tom.dog_name('Tom')
tom.dog_run()
运行结果
The dog`s name is Tom
Tom run
2、class def 中的 self 变量
dog_run
方法中的名字被固定成了 Tom
,如果要把 dog_name
方法中的变量 name
, 用起来,应当怎么办呢?
先来写一段错误代码:
class Dog:
def dog_name(self, name):
print(f'The dog`s name is {name}')
def dog_run(self):
print(f'{name} run') # 错误,注意name的作用域
tom = Dog()
tom.dog_name('Tom')
tom.dog_run()
报错如下:
username@usernamedeMacBookPro1 lab %python -u"/Users/username/Coding/lab/dog_example.py"
The dog`s name is Tom
Traceback (most recent call last):
File "/Users/username/Coding/lab/dog_example.py", line 12, in<module>
tom.dog_run()
File "/Users/username/Coding/lab/dog_example.py", line 7, in dog_run
print(f'{name} run') # 错误,注意name的作用域
NameError: name 'name' is not defined
可以看到,tom
在调用 dog_run
方法的时候,出现了 name
变量未定义错误。这是为什么呢?
因为 name
变量是 dog_name
方法的参数,它的作用域仅限于 dog_name
方法内,dog_run
方法自然不能使用,
class Dog:
def dog_name(self, name):
print(f'The dog`s name is {name}')
def dog_run(self):
print(f'{name} run') # 错误,注意name的作用域
dog_run
方法要使用 dog_name
方法的参数变量 name
,就必须得把 name
的作用域,扩展至 dog_run
方法内。
用 self
(约定俗成的单词,当然,也可以使用其他单词)关键字在 dog_name
方法内初始化 name
变量,让 name
变量的作用域扩展至 dog_run
方法。
class Dog:
def dog_name(self, name):
self.name = name # self 初始化变量 name,让 name 的作用域扩展至整个 class 内
print(f'The dog`s name is {name}')
def dog_run(self):
print(f'{self.name} run') # self.name就具有了被对象调用的能力
tom = Dog()
tom.dog_name('Tom')
tom.dog_run()
程序运行如下:
The dog`s name is Tom
Tom run
3、sub class def 中的 self 变量
继续写一个 Dog class
的 sub class
Cat,使用 self
初始化 name
变量,就可以让 Dog class
中 name
变量的作用域,扩展至 sub class
Cat,反之亦然。
比如,Cat
叫 Tony
,那么调用 Dog
中的 dog_run
方法时,sub class
中的 name
只要被 self
调用,那么这个 name
也会被被 father class
中的 dog_run
读取到,从而显示 Tony 在跑。
class Dog:
def dog_name(self, name):
self.name = name # self初始化变量name,让name的作用域扩展至class内
print(f'The dog`s name is {name}')
def dog_run(self):
print(f'{self.name} run') # self.name就具有了被对象调用的能力
class Cat(Dog):
def cat_name(self, name):
self.name = name
tom = Cat()
tom.cat_name('Tony')
tom.dog_run()
程序运行效果:
Tony run
4、作用域
因此,class
内 __init__
函数,所携带的参数(变量),就能被 self
初始化后,扩展作用域至整个 class
,这样的初始化形式,特别适合初始化 class
内,被多个方法反复调用的参数(变量)。
class Dog:
def __init__(self, name, age):
self.name = name # self.name 被 show_name、show choose 反复调用
self.age = age # self.age 被 show_name、show choose 反复调用
def show_name(self):
print(f'我的小狗叫 {self.name}, 今年已经 {self.age} 岁了')
def show_choose(self, number):
number = int(input())
if number == 1:
print(f'我的小狗叫 {self.name}')
elif number == 2:
print(f'我的小狗 {self.age} 岁了')
else:
print('您的输入有误!')
if __name__ == '__main__':
dog = Dog('Toy', 3)
dog.show_name()
dog.show_choose(2)
运行效果
我的小狗叫 Toy, 今年已经 3 岁了
2
我的小狗 3 岁了
可以看到 show_name
和 show_choose
,都需要调用变量 name
和 age
,所以,在 __init__
方法中直接用 self
参数,让 name
和 age
的作用域扩展至整个 class
。而 show_choose
方法中的 number
变量,就只能被 show_choose
自己调用(不用 self
关键字初始化的变量,作用域只限本地)。
若 show choose
方法内的 number
变量,要被其他 def
调用,就必须用 self
初始化。
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def show_name(self):
print(f'我的小狗叫 {self.name}, 今年已经 {self.age} 岁了')
def show_choose(self, number):
self.namber = number # self 参数让 number 作用域扩展至整个 class 内
self.number = int(input())
if self.number == 1:
print(f'我的小狗叫 {self.name}')
elif self.number == 2:
print(f'我的小狗 {self.age} 岁了')
else:
print('您的输入有误!')
def add(self, code):
sum_code = self.number + code
print(sum_code)
if __name__ == '__main__':
dog = Dog('Toy', 3)
dog.show_name()
dog.show_choose(2)
dog.add(3)
运行效果
我的小狗叫 Toy, 今年已经 3 岁了
2
我的小狗 3 岁了
5
思维过程大概如下图:
五、总结
综上,是不是感觉《Python 编程快速上手——让繁琐工作自动化》用来入门,比较友好了?
1、题外话
Python 零基础的入门书籍,我更推荐 《Python 编程从入门到实践》、《Python编程快速上手——让繁琐工作自动化》。
理由是,这两本书的知识体系,并非填鸭式罗列。而是充分照顾了零基础读者的学习逻辑。
2、感慨
反观某些大而全 Python 入门教材,不仅填鸭式罗列知识点,还经常跳跃式推进。
比如,在函数入门章节,堆砌 lambda
、yield
、map
、reduce
、filter
、sorted
,在函数入门章节后,高论装饰器。
再比如,在类、对象、实例的辨析都没讲明白的情况下,突入并发、并行、异步等概念!
3、比喻
最后再作一个类似开车般的比喻。
yield
、class
(含 self 、__init__
)等知识,就好比开手动挡汽车,坡启时的离合器半联动。只要入了 Python 的门,这些东西基本一用就会。
《Python编程快速上手——让繁琐工作自动化》就好比告诉新手司机,踩离合挂档起步的开车教学。至于坡启什么的,在学会开车后,多去坡上练几把,自然就会了(找到离合器半联动的踩踏脚感)。
既然是入门后多用就会的东西,何必在入门前徒增烦恼?
免责声明:本文由卡卷网编辑并发布,但不代表本站的观点和立场,只提供分享给大家。
相关推荐

你 发表评论:
欢迎