异常

在这个实验我们学习 Python 的异常以及如何在你的代码中处理它们。

知识点
  • NameError
  • TypeError
  • 异常处理(try..except)
  • 异常抛出(raise)
  • finally 子句

异常

在程序执行过程中发生的任何错误都是异常。每个异常显示一些相关的错误信息,比如你在 Python3 中使用 Python2 独有的语法就会发生 SyntaxError

此处输入图片的描述

不小心在行首多打了一个空格就会产生 IndentationError

此处输入图片的描述

NameError

当访问一个未定义的变量则会发生 NameError

>>> print(kushal)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'kushal' is not defined

最后一行包含了错误的详细信息,其余行显示它是如何发生(或什么引起该异常)的详细信息。

TypeError

TypeError 也是一种经常出现的异常。当操作或函数应用于不适当类型的对象时引发,一个常见的例子是对整数和字符串做加法。

>>> print(1 + "kushal")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

处理异常

我们使用 try...except 块来处理任意异常。基本的语法像这样:

try:
    statements to be inside try clause
    statement2
    statement3
    ...
except ExceptionName:
    statements to evaluated in case of ExceptionName happens

它以如下方式工作:

  • 首先,执行  try  子句 (在  try  和  except  关键字之间的部分)。

  • 如果没有异常发生,except  子句 在  try  语句执行完毕后就被忽略了。

  • 如果在 try 子句执行过程中发生了异常,那么该子句其余的部分就会被忽略。

    如果异常匹配于  except  关键字后面指定的异常类型,就执行对应的 except 子句。然后继续执行  try  语句之后的代码。

  • 如果发生了一个异常,在  except  子句中没有与之匹配的分支,它就会传递到上一级  try  语句中。

  • 如果最终仍找不到对应的处理语句,它就成为一个未处理异常,终止程序运行,显示提示信息。

下面的例子展示了这些情况:

>>> def get_number():
...     "Returns a float number"
...     number = float(input("Enter a float number: "))
...     return number
...
>>>
>>> while True:
...     try:
...         print(get_number())
...     except ValueError:
...         print("You entered a wrong value.")
...
Enter a float number: 45.0
45.0
Enter a float number: 24,0
You entered a wrong value.
Enter a float number: Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
  File "<stdin>", line 3, in get_number
KeyboardInterrupt

首先我输入了一个合适的浮点值,解释器返回输出这个值。

然后我输入以逗号分隔的值,抛出 ValueError 异常,except 子句捕获之,并且打印出错误信息。

第三次我按下 Ctrl + C ,导致了 KeyboardInterrupt 异常发生,这个异常并未在 except 块中捕获,因此程序执行被中止。

一个空的 except 语句能捕获任何异常。阅读下面的代码:

>>> try:
...     input() # 输入的时候按下 Ctrl + C 产生 KeyboardInterrupt
... except:
...     print("Unknown Exception")
...
Unknown Exception

抛出异常

使用 raise 语句抛出一个异常。

>>> raise ValueError("A value error happened.")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: A value error happened.

我们可以捕获任何其它普通异常一样,来捕获这些异常。

>>> try:
...     raise ValueError("A value error happened.")
... except ValueError:
...     print("ValueError in our code.")
...
ValueError in our code.

定义清理行为

try 语句还有另一个可选的 finally 子句,目的在于定义在任何情况下都一定要执行的功能。例如:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in ?

不管有没有发生异常,finally 子句 在程序离开 try 后都一定会被执行。当 try 语句中发生了未被 except 捕获的异常(或者它发生在 except 或 else 子句中),在 finally 子句执行完后它会被重新抛出。

在真实场景的应用程序中,finally 子句用于释放外部资源(文件或网络连接之类的),无论它们的使用过程中是否出错。

总结

本实验我们知道了异常是什么,然后怎样处理异常以及抛出异常。记得在前面说过的 with 语句吧,它是 try-finally 块的简写,使用 with 语句能保证文件始终被关闭。

异常是什么?其实异常是一种,而类将会在下一个实验介绍。

挑战:玩转函数

介绍

本次挑战中我们将实现一个程序,将分钟转为小时和分钟。

注意:代码中不要使用 input() 函数,否则挑战测试会卡住,出现 Timeout 的报错。

目标

请在 /home/shiyanlou/Code 创建代码文件 MinutesToHours.py

cd /home/shiyanlou/Code
touch MinutesToHours.py

在 MinutesToHours.py 文件中实现一个函数 Hours(),将用户输入的 分钟数 转化为 小时数和分钟数,并要求小时数尽量大。将结果以 XX H, XX M 的形式打印出来。

要求

  1. 用户能够通过命令行参数输入分钟数,不要使用 input,命令行参数可以使用 sys.argv 来提取。例如程序执行为 python3 MinutesToHours.py 80,传入的参数 80 就是分钟数,程序需要打印出相应的小时数和分钟数,输出为 1 H, 20 M
  2. 如果用户输入的是一个负值,程序需要 raise 来抛出 ValueError 异常。
  3. Hours() 函数调用的时候,需要使用 try...except 处理异常。获取异常后,在屏幕上打印出 Parameter Error 提示用户输入的值有误。

操作实例

python3 /home/shiyanlou/Code/MinutesToHours.py 80
1 H, 20 M

python3 /home/shiyanlou/Code/MinutesToHours.py 95
1 H, 35 M

python3 /home/shiyanlou/Code/MinutesToHours.py -10
Parameter Error

python3 /home/shiyanlou/Code/MinutesToHours.py abcd
Parameter Error

提示语

  • sys.argv 获取命令行参数,注意获取的参数为字符串,可以使用 int() 将字符串转为整数,此处也可能会出现异常情况,例如输入为 "abcd" 是无法转为整数的
  • raise 语句
  • try...except 语句

知识点

  • 异常
  • 文件处理
  • if-else

参考代码

注意:请务必先独立思考获得 PASS 之后再查看参考代码,直接拷贝代码收获不大。

/home/shiyanlou/Code/MinutesToHours.py 参考代码:

参考答案

import sys
# 转换函数
def Hours(minute):
    # 如果为负数则 raise 异常
    if minute < 0:
        raise ValueError("Input number cannot be negative")
    else:
        print("{} H, {} M".format(int(minute / 60), minute % 60))

# 函数调用及异常处理逻辑
try:
    Hours(int(sys.argv[1]))
except:print("Parameter Error")

在 Python 中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

知识点
  • 类的定义
  • 对象初始化

本部分实验中将通过定义一些简单的 Python 类,来学习 Python 面向对象编程的基本概念。

实验安排如下:

  1. 定义简单的类
  2. __init__ 方法
  3. Python 中的继承
  4. 多继承
  5. 删除对象
  6. 属性读取方法
  7. @property 装饰器

定义类

在写你的第一个类之前,你应该知道它的语法。我们以下面这种方式定义类:

class nameoftheclass(parent_class):
    statement1
    statement2
    statement3

在类的声明中你可以写任何 Python 语句,包括定义函数(在类中我们称为方法)。

>>> class MyClass(object):
...     """A simple example class"""
...     i = 12345
...     def f(self):
...         return 'hello world'

__init__ 方法

类的实例化使用函数符号。只要将类对象看作是一个返回新的类实例的无参数函数即可。例如(假设沿用前面的类):

x = MyClass()

以上创建了一个新的类实例并将该对象赋给局部变量  x

这个实例化操作创建一个空的对象。很多类都倾向于将对象创建为有初始状态的。因此类可能会定义一个名为  __init__()  的特殊方法,像下面这样:

def __init__(self):
    self.data = []

类定义了  __init__()  方法的话,类的实例化操作会自动为新创建的类实例调用  __init__()  方法。所以在下例中,可以这样创建一个新的实例:

x = MyClass()

当然,出于弹性的需要,__init__()  方法可以有参数。事实上,参数通过__init__()  传递到类的实例化操作上。例如:

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

继承

当一个类继承另一个类时,它将继承父类的所有功能(如变量和方法)。这有助于重用代码。

在下一个例子中我们首先创建一个叫做 Person 的类,然后创建两个派生类 Student 和 Teacher。当两个类都从 Person 类继承时,它们的类除了会有 Person 类的所有方法还会有自身用途的新方法和新变量。

student_teacher.py

代码写入文件 /home/shiyanlou/student_teacher.py

#!/usr/bin/env python3

class Person(object):
    """
    返回具有给定名称的 Person 对象
    """

    def __init__(self, name):
        self.name = name

    def get_details(self):
        """
        返回包含人名的字符串
        """
        return self.name


class Student(Person):
    """
    返回 Student 对象,采用 name, branch, year 3 个参数
    """

    def __init__(self, name, branch, year):
        Person.__init__(self, name)
        self.branch = branch
        self.year = year

    def get_details(self):
        """
        返回包含学生具体信息的字符串
        """
        return "{} studies {} and is in {} year.".format(self.name, self.branch, self.year)


class Teacher(Person):
    """
    返回 Teacher 对象,采用字符串列表作为参数
    """
    def __init__(self, name, papers):
        Person.__init__(self, name)
        self.papers = papers

    def get_details(self):
        return "{} teaches {}".format(self.name, ','.join(self.papers))


person1 = Person('Sachin')
student1 = Student('Kushal', 'CSE', 2005)
teacher1 = Teacher('Prashad', ['C', 'C++'])

print(person1.get_details())
print(student1.get_details())
print(teacher1.get_details())

运行程序

此处输入图片的描述

在这个例子中你能看到我们是怎样在 Student 类和 Teacher 类中调用 Person 类的 __init__ 方法。

我们也在 Student 类和 Teacher 类中重写了 Person 类的 get_details() 方法。

因此,当我们调用 student1 和 teacher1 的 get_details() 方法时,使用的是各自类(Student 和 Teacher)中定义的方法。

多继承

一个类可以继承自多个类,具有父类的所有变量和方法,语法如下:

class MyClass(Parentclass1, Parentclass2,...):
    def __init__(self):
        Parentclass1.__init__(self)
        Parentclass2.__init__(self)
        ...
        ...

这里没有提供额外的实例,将会在其他实验楼的项目实战中用到。

删除对象

现在我们已经知道怎样创建对象,现在我们来看看怎样删除一个对象。我们使用关键字 del 来做到这个。

>>> s = "I love you"
>>> del s
>>> s
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 's' is not defined

del 实际上使对象的引用计数减少一,当对象的引用计数变成零的时候,垃圾回收器会删除这个对象。

属性(_attributes_)读取方法

在 Python 里请不要使用属性(attributes_)读取方法(_getters 和 _setters_)。如果你之前学过其它语言(比如 Java),你可能会想要在你的类里面定义属性读取方法。请不要这样做,直接使用属性就可以了,就像下面这样:

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...
>>> std = Student("Kushal Das")
>>> print(std.name)
Kushal Das
>>> std.name = "Python"
>>> print(std.name)
Python

装饰器

你可能想要更精确的调整控制属性访问权限,你可以使用 @property 装饰器,@property 装饰器就是负责把一个方法变成属性调用的。

下面有个银行账号的例子,我们要确保没人能设置金额为负,并且有个只读属性 cny 返回换算人民币后的金额。

代码写入文件 /home/shiyanlou/property.py

#!/usr/bin/env python3

class Account(object):
    """账号类,
    amount 是美元金额.
    """
    def __init__(self, rate):
        self.__amt = 0
        self.rate = rate

    @property
    def amount(self):
        """账号余额(美元)"""
        return self.__amt

    @property
    def cny(self):
        """账号余额(人民币)"""
        return self.__amt * self.rate

    @amount.setter
    def amount(self, value):
        if value < 0:
            print("Sorry, no negative amount in the account.")
            return
        self.__amt = value

if __name__ == '__main__':
    acc = Account(rate=6.6) # 基于课程编写时的汇率
    acc.amount = 20
    print("Dollar amount:", acc.amount)
    print("In CNY:", acc.cny)
    acc.amount = -100
    print("Dollar amount:", acc.amount)

运行程序:

此处输入图片的描述

总结

实验知识点回顾:

  • 类的定义
  • 对象初始化

本实验我们了解了类的定义,类的继承以及多继承,并且最后我们还接触了装饰器这个概念,本质上,装饰器也是一种高阶函数。

模块

在这个实验我们将要学习 Python 模块相关知识。包括模块的概念和导入方法,包的概念和使用,第三方模块的介绍,命令行参数的使用等。

知识点
  • 模块的导入
  • 默认/第三方模块介绍
  • 命令行参数

模块

到目前为止,我们在 Python 解释器中写的所有代码都在我们退出解释器的时候丢失了。但是当人们编写大型程序的时候他们会倾向于将代码分为多个不同的文件以便使用,调试以及拥有更好的可读性。在 Python 中我们使用模块来达到这些目的。模块是包括 Python 定义和声明的文件。文件名就是模块名加上  .py  后缀。

你可以由全局变量 __name__ 得到模块的模块名(一个字符串)。

现在我们来看看模块是怎样工作的。创建一个 bars.py 文件。文件内容如下:

"""
Bars Module
============
这是一个打印不同分割线的示例模块
"""
def starbar(num):
    """打印 * 分割线

    :arg num: 线长
    """
    print('*' * num)

def hashbar(num):
    """打印 # 分割线

    :arg num: 线长
    """
    print('#' * num)

def simplebar(num):
    """打印 - 分割线

    :arg num: 线长
    """
    print('-' * num)

现在我们启动解释器然后导入我们的模块。

>>> import bars
>>>

我们必须使用模块名来访问模块内的函数。

>>> bars.hashbar(10)
##########
>>> bars.simplebar(10)
----------
>>> bars.starbar(10)
**********

导入模块

有不同的方式导入模块。我们已经看到过一种了。你甚至可以从模块中导入指定的函数。这样做:

>>> from bars import simplebar, starbar
>>> simplebar(20)
--------------------

你也可以使用 from module import * 导入模块中的所有定义,然而这并不是推荐的做法。

含有 __init__.py 文件的目录可以用来作为一个包,目录里的所有 .py 文件都是这个包的子模块。

本节实验将创建下面的 mymodule 目录,目录结构如下:

此处输入图片的描述

在这个例子中,mymodule 是一个包名并且 bars 和 utils 是里面的两个子模块。

首先创建 mymodule 目录:

cd /home/shiyanlou
mkdir mymodule

然后将上一节编写的 bars.py 拷贝到 mymodule 目录下,然后可以使用 touch 创建一个 utils.py 文件。

使用 touch 命令创建一个空的 __init__.py 文件。

touch mymodule/__init__.py

如果 __init__.py 文件内有一个名为 __all__ 的列表,那么只有在列表内列出的名字将会被公开。

因此如果 mymodule 内的 __init__.py 文件含有以下内容:

from mymodule.bars import simplebar
__all__ = [simplebar, ]

那么导入时将只有 simplebar 可用。如果你在 python3 解释器中进行测试,需要确定是在 mymodule 目录同级的目录下执行的 python3,类似下面的操作,否则会出现 ImportError: No module named 'mymodule' 的报错。

cd /home/shiyanlou
python3
>>>

from mymodule import * 只能工作在模块级别的对象上,试图导入函数或类将导致 syntax error。

参考资料

默认模块

现在你安装 Python 的时候会附带安装不同的模块,你可以按需使用它们,也可以为其它特殊用途安装新模块。在下面的几个例子中,我们将要看到同样例子很多。

modules

上面的例子展示了怎样获得你系统中安装的所有模块的列表。在这里就不粘贴它们了,因为这是一个很大的列表。

你也能在解释器里使用 help() 函数查找任何模块/类的文档。如果你想要知道字符串所有可用的方法,你可以像下面这样做:

>>> help(str)

os 模块

os 模块提供了与操作系统相关的功能。你可以使用如下语句导入它:

>>> import os

getuid() 函数返回当前进程的有效用户 id。

>>> os.getuid()
500

getpid() 函数返回当前进程的 id。getppid() 返回父进程的 id。

>>> os.getpid()
16150
>>> os.getppid()
14847

uname() 函数返回识别操作系统的不同信息,在 Linux 中它返回的详细信息可以从 uname -a 命令得到。uname() 返回的对象是一个元组,(sysname, nodename, release, version, machine)

>>> os.uname()
('Linux', 'd80', '2.6.34.7-56.fc13.i686.PAE', '#1 SMP Wed Sep 15 03:27:15 UTC 2010', 'i686')

getcwd() 函数返回当前工作目录。chdir(path) 则是更改当前目录到 path。在例子中我们首先看到当前工作目录是 /home/shiyanlou,然后我们更改当前工作目录到 /Code 并再一次查看当前工作目录。

>>> os.getcwd()
'/home/shiyanlou'
>>> os.chdir('Code')
>>> os.getcwd()
'/home/shiyanlou/Code'

所以现在让我们使用 os 模块提供的另一个函数来创建一个自己的函数,它将列出给定目录下的所有文件和目录。

def view_dir(path='.'):
    """
    这个函数打印给定目录中的所有文件和目录
    :args path: 指定目录,默认为当前目录
    """
    names = os.listdir(path)
    names.sort()
    for name in names:
        print(name, end =' ')
    print()

使用例子中的 view_dir() 函数。

>>> view_dir('/')
.bashrc .dockerenv .profile bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

os 模块还有许多非常有用的函数,你可以在这里阅读相关内容。

Requests 模块

Requests 是一个第三方 Python 模块,其官网的介绍如下:

Requests 一个非转基因的 Python HTTP 库,人类可以安全享用。

警告:非专业使用其他 HTTP 库会导致危险的副作用,包括:安全缺陷症、冗余代码症、重新发明轮子症、啃文档症、抑郁、头疼、甚至死亡。

第三方模块并不是默认的模块,意味着你需要安装它,我们使用 pip3 安装它。

首先要安装 pip3

sudo apt-get update
sudo apt-get install python3-pip

然后用 pip3 安装 requests

sudo pip3 install requests

上面的命令会在你的系统中安装 Python3 版本的 Requests 模块。

获得一个简单的网页

你可以使用 get() 方法获取任意一个网页。

>>> import requests
>>> req = requests.get('https://github.com')
>>> req.status_code
200

req 的 text 属性存有服务器返回的 HTML 网页,由于 HTML 文本太长就不在这里贴出来了。

使用这个知识,让我们写一个能够从指定的 URL 中下载文件的程序。

代码写入文件 /home/shiyanlou/download.py

#!/usr/bin/env python3
import requests

def download(url):
    '''
    从指定的 URL 中下载文件并存储到当前目录
    url: 要下载页面内容的网址
    '''
    # 检查 URL 是否存在
    try:
        req = requests.get(url)
    except requests.exceptions.MissingSchema:
        print('Invalid URL "{}"'.format(url))
        return
    # 检查是否成功访问了该网站
    if req.status_code == 403:
        print('You do not have the authority to access this page.')
        return
    filename = url.split('/')[-1]
    with open(filename, 'w') as fobj:
        fobj.write(req.content.decode('utf-8'))
    print("Download over.")

if __name__ == '__main__':
    url = input('Enter a URL: ')
    download(url)

测试一下程序:

此处输入图片的描述

可以看到目录下已经多了一个 sample.txt 文件。

你可能已经注意到了 if __name__ == '__main__': 这条语句,它的作用是,只有在当前模块名为 __main__ 的时候(即作为脚本执行的时候)才会执行此 if 块内的语句。换句话说,当此文件以模块的形式导入到其它文件中时,if 块内的语句并不会执行。

你可以将上面的程序修改的更友好些。举个例子,你可以检查当前目录是否已存在相同的文件名。os.path 模块可以帮助你完成这个。

argparse 命令行参数处理模块

你还记得 ls 命令吗,你可以传递不同的选项作为命令行参数。

这里是用到的模块是 sys,命令行传入的所有参数都可以使用 sys.argv 获取。如果希望对参数进行处理可以使用 argparse 模块,阅读这篇 文档 学习。

TAB 补全

首先创建一个文件:~/.pythonrc ,文件内写入如下内容:

import rlcompleter, readline
readline.parse_and_bind('tab: complete')


history_file = os.path.expanduser('~/.python_history')
readline.read_history_file(history_file)

import atexit
atexit.register(readline.write_history_file, history_file)

下一步在 ~/.bashrc 文件中设置 PYTHONSTARTUP 环境变量指向这个文件:

export PYTHONSTARTUP=~/.pythonrc

现在,从今以后每当你打开 bash shell,你将会有 TAB 补全和 Python 解释器中代码输入的历史记录。

要在当前 shell 中使用,source 这个 bashrc 文件。

source ~/.bashrc

总结

本实验了解了什么是模块,模块怎样导入,举例了 os 和 Requests 模块的使用。Python 吸引人的一点是其有众多的模块可以使用,对于自带模块,可以看看 Python3 的官方文档,对于第三方模块,可以在 PyPI 上找找。很多时候你都能找到合适的包帮你优雅的完成部分工作。比如 argparse 模块帮你非常容易的编写用户友好的命令行接口。

Collections 模块

collections 是 Python 内建的一个集合模块,提供了许多有用的集合类。

知识点
  • Counter 类
  • defaultdict 类
  • namedtuple 类

Counter

在这个实验我们会学习 Collections 模块。这个模块实现了一些很好的数据结构,它们能帮助你解决各种实际问题。

>>> import collections

这是如何导入这个模块,现在我们来看看其中的一些类。

Counter 是一个有助于 hashable 对象计数的  dict  子类。它是一个无序的集合,其中 hashable  对象的元素存储为字典的键,它们的计数存储为字典的值,计数可以为任意整数,包括零和负数。

我们可以这样查看 Counter 的帮助信息,事实上这些信息来源于 Counter 的文档字符串(collections.Counter.__doc__)。

collections.Counter

此处输入图片的描述

下面我们来看一个例子,例子中我们查看 Python 的 LICENSE 文件中某些单词出现的次数。

Counter 示例
>>> from collections import Counter
>>> import re
>>> path = '/usr/lib/python3.5/LICENSE.txt'
>>> words = re.findall('\w+', open(path).read().lower())
>>> Counter(words).most_common(10)
[('the', 80), ('or', 78), ('1', 66), ('of', 61), ('to', 50), ('and', 48), ('python', 46), ('in', 38), ('license', 37), ('any', 37)]

Counter 对象有一个叫做 elements() 的方法,其返回的序列中,依照计数重复元素相同次数,元素顺序是无序的。

>>> c = Counter(a=4, b=2, c=0, d=-2)
>>> list(c.elements())
['b','b','a', 'a', 'a', 'a']

most_common() 方法返回最常见的元素及其计数,顺序为最常见到最少。

>>> Counter('abracadabra').most_common(3)
[('a', 5), ('r', 2), ('b', 2)]

defaultdict

defaultdict 是内建 dict 类的子类,它覆写了一个方法并添加了一个可写的实例变量。其余功能与字典相同。

defaultdict() 第一个参数提供了 default_factory 属性的初始值,默认值为 Nonedefault_factory 属性值将作为字典的默认数据类型。所有剩余的参数与字典的构造方法相同,包括关键字参数。

同样的功能使用 defaultdict 比使用 dict.setdefault 方法快。

defaultdict 用例:

>>> from collections import defaultdict
>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
...
>>> d.items()
dict_items([('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])])

在例子中你可以看到,即使 defaultdict 对象不存在某个,它会自动创建一个空列表。

namedtuple

命名元组有助于对元组每个位置赋予意义,并且让我们的代码有更好的可读性和自文档性。你可以在任何使用元组地方使用命名元组。在例子中我们会创建一个命名元组以展示为元组每个位置保存信息。

>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])  # 定义命名元组
>>> p = Point(10, y=20)  # 创建一个对象
>>> p
Point(x=10, y=20)
>>> p.x + p.y
30
>>> p[0] + p[1]  # 像普通元组那样访问元素
30
>>> x, y = p     # 元组拆封
>>> x
10
>>> y
20

总结

本节知识点回顾:

  • Counter 类
  • defaultdict 类
  • namedtuple 类

这个实验我们使用了 Collections 中的一些数据结构,可能你目前并用不上他,但希望你以后需要的时候会想起它们。

挑战:类和 Collection

介绍

本次挑战中我们将通过改写之前实验中的 student_teacher.py 程序实现更加丰富的功能。

目标

改写 我们在  这个实验中 继承 部分的 student_teacher.py 脚本,实现以下功能:

  1. 在 Person() 类中增添函数 get_grade()
  2. 对于教师类,get_grade() 函数可以自动统计出老师班上学生的得分情况并按照频率的高低以 A: X, B: X, C: X, D: X 的形式打印出来
  3. 对于学生类,get_grade() 函数则可以以 Pass: X, Fail: X 来统计自己的成绩情况(A,B,C 为 Pass, 如果得了 D 就认为是 Fail)。

student_teacher.py 文件可以通过在 Xfce 终端中输入如下代码来获取

cd /home/shiyanlou/Code
wget https://labfile.oss-internal.aliyuncs.com/courses/790/student_teacher.py

要求:

  1. 请把最终的student_teacher.py 代码文件放在 /home/shiyanlou/Code/ 路径下
  2. 根据命令行中的第一个参数 teacher 或者 student 来判断最终输出的格式。
  3. 命令行中第二个输入的参数是需要统计的字符串

执行实例:

此处输入图片的描述

提示语

  • Teacher 及 Student 类的 __init__() 也要增加 grade 参数
  • import sys
  • collections 中的 Counter 子类
  • format() 以及 join

知识点

  • Collection 模块
  • 注意最终的打印形式

参考代码

注意:请务必先独立思考获得 PASS 之后再查看参考代码,直接拷贝代码收获不大。

/home/shiyanlou/Code/student_teacher.py 参考代码:

参考答案
#!/usr/bin/env python3
import sys
from collections import Counter

class Person(object):
    """
    返回具有给定名称的 Person 对象
    """

    def __init__(self, name):
        self.name = name

    def get_details(self):
        """
        返回包含人名的字符串
        """
        return self.name

    def get_grade(self):
        return 0


class Student(Person):
    """
    返回 Student 对象,采用 name, branch, year 3 个参数
    """

    def __init__(self, name, branch, year,grade):
        Person.__init__(self, name)
        self.branch = branch
        self.year = year
        self.grade = grade

    def get_details(self):
        """
        返回包含学生具体信息的字符串
        """
        return "{} studies {} and is in {} year.".format(self.name, self.branch, self.year)

    def get_grade(self):

        common = Counter(self.grade).most_common(4)
        n1 = 0
        n2 = 0
        for item in common:
            if item[0] != 'D':
                n1 += item[1]
            else:
                n2 += item[1]
        print("Pass: {}, Fail: {}".format(n1,n2))

class Teacher(Person):
    """
    返回 Teacher 对象,采用字符串列表作为参数
    """
    def __init__(self, name, papers, grade):
        Person.__init__(self, name)
        self.papers = papers
        self.grade = grade

    def get_details(self):
        return "{} teaches {}".format(self.name, ','.join(self.papers))

    def get_grade(self):
        s = []
        common = Counter(self.grade).most_common(4)
        for i,j in common:
            s.append("{}: {}".format(i,j))
        print(', '.join(s))

person1 = Person('Sachin')
if sys.argv[1] == "student":
    student1 = Student('Kushal', 'CSE', 2005, sys.argv[2])
    student1.get_grade()
else:
    teacher1 = Teacher('Prashad', ['C', 'C++'], sys.argv[2])
    teacher1.get_grade()

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部