小白提问:python 在什么时候用函数,什么时候用类?

半路自学的编程小透明,刚刚接触python这门语言。现在有个疑问想请教大家。

我想问的是,大家是如何判断、以及用什么标准来决定什么时候使用函数?什么时候使用类?

函数还好说,一段代码需要重复使用的时候用函数会减少代码量。
但是我遇到了这样一个实例,由于目前思想上更习惯使用面向过程编程,所以在编写下面这个小小爬虫程序的时候就写成了这样的形式:

# 任务目标是爬取特定网页上图片列表页面的所有图片。
# 由于是列表页面所以有一个总页数,就使用了 for 循环遍历所有页面,然后下载个页面图片的思路。
# 大致如下

base_url = 'xxx/page='    # 网址忽略了
pages = 90                # 假设一共有 90 页

for page in range(1, pages + 1):          # 首先使用 for 遍历列表的每一页,获取所有的 img 标签
    url = base_url + str(page)
    rq = requests.get(url)
    soup = BeautifulSoup(rq.text)
    all_tag_img = soup.find_all('img')
    
    for tag in all_tag_img:              # 然后在通过 for 遍历所有标签获取下载地址,下载后保存
        img_url = tag.get('src')
        img = requests.get(img_url)
        # 下面就是获取图片的名称,然后保存什么的了
        

这样下来的话,流程上面感觉比较接近普通浏览的顺序,
但这是这里用不到函数,更用不到类,但是见到过很多爬虫教程,大家会把例如下载图片、获取名称、保存这些东西封装成函数。有些直接就是创建一个爬虫的类,然后增加各种功能。

所有,问题就是,这样一个任务,你们回去用函数或类么?为什么?
我觉得编程最难的是思想,所有我特别想知道你们是怎么考虑的
万分感谢

阅读 20.1k
4 个回答

个人觉得,其实这个跟你需要完成的任务有关。打个比方,你只是自己想写一个程序,方便自己工作,那你就可以写函数为主,反正自己知道这些函数什么意思,有时候换地方使用直接copy一下,修修改改就好了。
而如果是完成某个大的项目,项目要分很多块,那么就必须写类了。有人说我写个函数,最终实现起来还不是一样?实现起来是一样,那么后期维护呢?你是更喜欢看一个模块,这个模块有几个类,还是说喜欢看几十个函数呢?显而易见,一般稍微有点样子的项目都是分成几个模块,每个模块会分出几个类来各自实现某个特定的任务。到代码层的时候,才会需要看函数的代码。
如果一个项目是由按照某些顺序,直接调用几十个函数完成的,相信后期维护的人会疯吧?好气啊,全是函数,虽然有说明,也很费事的吧?

用函数的话 代码量少很多 比如你发出来的代码 一行就够了

首要目标:完成任务。
次要目标:快速完成任务。

不使用类和对象的话,函数所有的输入输出都要在一个空间里管理,如果功能较多函数较多,管理上可能就比较有难度了。这时候就可以考虑让类对象来自己管理它需要的资源,对外只提供必要的输入输出和管理接口就行了。

写的多了就慢慢明白了。一切视需求而定,既不过渡设计用牛刀杀鸡,也不能代码一团糟难以理解。

新手上路,请多包涵

函数是无状态的,先记住这句话,后边与类的方法对比时就会明白了。

一、使用函数的场景

1. 需要复用的代码

这个比较容易理解,有一段代码你需要经常使用,但是每次都会修改其中的两个变量,这时把这段代码封装进一个函数,把经常修改的这两个变量设置为两个参数,你就可以把这段代码丢到一边不用动,每次使用的时候调用这个函数并传给函数两个参数就行了。
这样做的好处是封装之后你就可以完全不关心函数的内容,只需要关注函数提供给你的接口。

2. 对程序简单地分块

即使不需要复用的程序,当程序块过长,也会造成阅读上的障碍,这时候就需要将程序分块封装进函数,通过适当地取名,让你的主程序变得整洁。

二、使用类的场景

1. 大型程序区分模块

大型程序设计的时候一般都采用自顶向下的方法,先将大型程序划分为几个模块,这时就可以使用类来区分模块,其实也可以用文件或目录来划分,这个并不是绝对的。C语言这种非面向对象语言一般都是直接用文件或目录划分模块。

2. 需要带状态的函数

函数是无状态的,比如我想用一个模型来预测自己患某种疾病的风险,如果使用函数,我可以这样:

def predict(somatic_data, model_path):
    model = load_model(model_path)
    risk = model(somatic_data)
    return risk

predict(somatic_data, model_path)

这样做是有明显缺点的,每一次调用函数都会重新加载一遍模型,如果模型小还好说,但是如果模型非常大呢,每一次预测都需要等待加载好久。所以就有了如下写法:

model = load_model(model_path)

def predict(somatic_data, model):
    risk = model(somatic_data)
    return risk

predict(somatic_data, model)

这样写虽然避免了重复加载的问题,但是函数和模型不是一个完整的个体,而且因为我们想要的是用模型预测数据的患病风险,这时如果像 predict(somatic_data, model) 这样写,语义上并不清楚。C语言这种非面向对象语言一般都是这样写的。再看面向对象语言使用类的写法:

class Predictor(object):
    def __init__(self, model_path):
        self.model = load_model(model_path)
    def predict(self, somatic_data)
        risk = model(somatic_data)
        return risk

predictor = Predictor(model_path)
predictor.predict(somatic_data)

model 保存在 predictor 对象中,作为状态或属性,不会重复加载,每一次预测都只需要调用这个对象的 predict 方法。
还有一个好处,就是语义清晰、权责分明,predictor.predict(somatic_data) 这样写相较于 predict(somatic_data, model) 就能很清楚的表示是 predictor 去做预测,预测的对象是 somatic_data

* 以上代码都是在编辑器中写的,不保证 bug free

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题