Python语法基础(下)

在这一部分,我们介绍Python最基础的语法知识。尽管网上关于Python的入门教程浩如烟海,在这一节我们只介绍最基本和最常见的语法。

函数

3.1 函数定义

函数(function)是Python中最主要也是最重要的代码组织和复用手段。Python内置了很多函数,我们可以直接调用,例如我们之前接触过的print和len函数:

a = [1, 2, 3, 4]
print(len(a))

如果我们要重复使用相同或非常类似的代码,就需要写一个函数。通过给函数起一个名字,还可以提高代码的可读性。函数名应该为小写,可以用下划线风格单词以增加可读性。函数使用def关键字声明:

def my_function():
    print('Hello world!')


# 比较好的习惯是在函数定义的语句下面空两行
my_function()

用return关键字返回值:

def my_function():
    return 'Hello world!'


print(my_function())

同时写多条return语句不会报错,但是如果函数执行了return语句,函数会立刻返回,结束调用,return之后的其它语句都不会被执行了。

def my_function():
    return 'Hello world!'
    return 'Nothing'


print(my_function())

此时输出为:

Hello world!

如果到达函数末尾时没有遇到任何一条return语句,则返回None。

def my_function():
    print('Hello world!')


print(my_function())

输出为:

Hello world!
None

在return语句中,我们可以用逗号隔开多个返回值,Python会隐式地将它们封装成一个元组返回:

def my_function():
    return 1, 'string', [1, 2, 3]


a = my_function()
print(a)
a, b, c = my_function()  # 如果我们知道返回值的个数,也可以联系之前讲过的元组赋值的操作
print(a)
print(b)
print(c)

输出为:

(1, 'string', [1, 2, 3])
1
string
[1, 2, 3]

或者我们也可以把函数的返回值“打包”成一个字典:

def my_function():
    a = 1
    b = 2
    c = 3
    return {'a': a, 'b': b, 'c': c}


result = my_function()
print(result['b'])  # 可以根据我们的需要使用对应的函数返回值

输出为:

2

3.2 函数参数

函数可以有一些位置参数(positional)和一些关键字参数(keyword)。关键字参数通常用于指定默认值或可选参数。例如下面的例子中,x和y是位置参数,z和w是关键字参数:

def my_function(x, y, z=0.0001, w=None):
    if w is not None:
        return x + y + z + w
    else:
        return x + y + z

函数参数可以只有位置参数,或者只有关键字参数,如果都有的时候,主要限制在于:关键字参数必须位于位置参数(如果有的话)之后。
下面这段代码就会报错:

def my_function(x, z=0.0001, y, w=None):  # 程序会报错
    if w is not None:
        return x + y + z + w
    else:
        return x + y + z

对于有参数的函数,我们在调用的时候需要按顺序指明位置参数的值,而关键字参数可以指明也可以不指明:

def my_function(x, y, z=0.0001, w=None):
    if w is not None:
        return x + y + z + w
    else:
        return x + y + z


# 以下都是合法的调用方式
my_function(1, 2)
my_function(1, 2, z=3, w=4)
my_function(1, 2, 3)  # 如果不指明关键字参数的名称,默认按照函数定义时的参数顺序进行对应,例如这里认为z=3
my_function(1, 2, w=4)  # 如果想跳过z,指明w的值,那么这里一定要写明w=4

my_function(x=1, y=2)  # 调用时我们也可以用关键字的形式传递位置参数
# my_function(x=1, 2)  # 但是采用这种形式,需要保证关键字的这种形式在普通的位置参数后面,要不然会报错
my_function(1, y=2)  # 这样就不会报错

用关键字参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。例如我们可以定义一个指数函数:

def power(a, x=2):
    return a ** x


print(power(2))  # 当传入一个参数的时候,默认计算平方值
print(power(2, 3))  # 当我们想要计算立方时,再传入一个参数

输出为:

4
8

前面提到的都是参数个数固定的情况,如果位置参数和关键字参数的个数不固定呢?在Python函数中,还可以定义可变参数,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。这些可变参数在函数调用时自动组装为一个元组。例如我们要计算一组数值的平方和,一种方法是定义只有一个参数的函数,然后将这组数值组装成一个元组或数组传进来:

def my_function(numbers):
    sum = 0
    for n in numbers:
        sum += n * n
    return sum


print(my_function([1, 2, 3]))

输出为:

14

我们也可以定义可变参数:

# 注意我们仅仅是多加了一个*
def my_function(*args):
    sum = 0
    for n in args:
        sum += n * n
    return sum


print(my_function())
print(my_function(1))
print(my_function(1, 2))
print(my_function(1, 2, 3))

a = [1, 2, 3]
# 对于已有的列表或元组,Python允许你在前面加一个*号,把它的元素变成可变参数传进去
print(my_function(*a))

输出为:

0
1
5
14
14

对于关键字参数,Python也允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

def my_function(**kw):
    if 'city' in kw:
        print('city: %s' % kw['city'])
    if 'country' in kw:
        print('country: %s' % kw['country'])
    print()


my_function()
my_function(city='Beijing')
my_function(country='China')
my_function(city='Beijing', country='China')

输出为:

city: Beijing

country: China

city: Beijing
country: China

这些参数也可以组合使用,但是要注意先后顺序,关键字参数必须位于位置参数之后:

def my_function(x, y, *args, z=0.0001, w=None, **kw):
    if w is not None:
        print(x + y + z + w)
    else:
        print(x + y + z)

    sum = 0
    for n in args:
        sum += n * n
    print(sum)

    if 'city' in kw:
        print('city: %s' % kw['city'])
    if 'country' in kw:
        print('country: %s' % kw['country'])
    print()


my_function(1, 2, 1, 2, 3, z=3, w=4, city='Beijing', country='China')

输出为:

10
14
city: Beijing
country: China

3.3 递归函数

Python也支持递归函数。在函数内部,我们可以调用其他函数。如果一个函数在内部调用自身,这个函数就是递归函数。例如我们要计算阶乘:

def fact(n):
    if n == 1:
        return 1
    else:
        return n * fact(n - 1)


print(fact(5))

输出为:

120

3.4 函数作用域

Python用命名空间(namespace)描述变量作用域的名称。任何在函数中赋值的变量默认都是被分配到局部命名空间(local namespace)中的。与之相对的,是全局命名空间(global namespace)。局部命名空间是在函数被调用时创建的,函数参数会立即填入该命名空间。在函数执行完毕之后,局部命名空间就会被销毁。见下面的例子:

a = 1  # 变量a在全局命名空间

def my_function():
    a = 2  # 变量a在局部命名空间,相当于“新建”了一个“临时”的新变量
    print(a)


my_function()  # 调用函数,会打印局部命名空间中的变量a
print(a)  # 全局命名空间中的a的值没有改变

输出为:

2
1

虽然可以在函数中对全局变量进行赋值操作,但是那些变量必须用global关键字声明成全局的才行。不过这种做法并不是很建议:

a = 1  # 变量a在全局命名空间

def my_function():
    global a  # 声明我们要使用全局命名空间中的a
    a = 2 
    print(a)


my_function()  # 调用函数,会打印全局命名空间中的变量a
print(a)  # 全局命名空间中的a的值发生了改变

输出为:

2
2

3.5 匿名函数

Python支持一种被称为匿名的、或lambda函数。它仅由单条语句组成,该语句的结果就是返回值。它是通过lambda关键字定义的,这个关键字没有别的含义,仅仅是说“我们正在声明的是一个匿名函数”。

def my_function(a):
    return a * 2


same_function = lambda a: a * 2  # 这里仅仅是演示,实际使用中不会这样赋值
a = 1
print(my_function(a))
print(same_function(a))

输出为:

2
2

匿名函数在数据分析工作中非常方便,因为你会发现很多数据转换函数都以函数作为参数的。直接传入lambda函数比编写完整函数声明要少输入很多字(也更清晰),甚至比将lambda函数赋值给一个变量还要少输入很多字。

def apply_to_list(some_list, f):
    return [f(x) for x in some_list]


ints = [4, 0, 1, 5, 6]
result = apply_to_list(ints, lambda x: x * 2)
print(result)

输出为:

[8, 0, 2, 10, 12]

在对数据进行排序时,也可以利用匿名函数方便地定义排序的准则。假设有一组字符串,你想要根据各字符串不同字母的数量对其进行排序:

strings = ['foo', 'card', 'bar', 'aaaa', 'abab']

# 我们可以传入一个lambda函数到列表的sort方法
strings.sort(key=lambda x: len(set(list(x))))
print(strings)

输出为:

['aaaa', 'foo', 'abab', 'bar', 'card']

3.6 模块

有很多的函数并不需要我们亲自动手去实现,可以去调用前人已经写好的模块。而且在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。为了编写可维护的代码,我们可以把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Python中,一个.py文件就称之为一个模块(Module)。Python本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用。

导入模块的格式包括以下几种:

格式 举例
import 模块名 直接导入整个模块,占用内存多,例如import matplotlib
import 模块名 as 名称缩写 导入模块的同时取一个“别名”,例如import matplotlib as mpl
import 模块名.子模块名 as 名称缩写 导入某个模块的子模块,并给子模块取一个“别名”(可选),例如import matplotlib.pyplot as plt
from 模块名 import 函数名 从模块中导入某个或某几个函数(英文逗号隔开),节约内存,例如from math import exp, log, sqrt
from 模块名.子模块名 import 函数名 从子模块中导入某个或某几个函数,例如from matplotlib.pyplot import figure, plot

我们也可以定义自己的模块,例如在my_package.py中定义:

def my_function():
    print('Hello world')

要使用my_function,我们可以导入my_package这个模块:

import my_package
my_package.my_function()

或者可以直接导入特定的函数:

from my_package import my_function
my_function()

我们也可以对导入的模块或函数起一个更短的“别名”:

from my_package import my_function as my
my()

3.7 柯里化

我们也可以在一个函数的基础上通过柯里化的方式建立一个新的函数。假设我们有一个执行两数相加的简单函数,通过这个函数,我们可以派生出一个新的只有一个参数的函数,add_five,它用于对其参数加5:

def add_numbers(x, y):
    return x + y
add_five = lambda y: add_numbers(5, y)

add_numbers的第二个参数称为“柯里化的”(curried)。我们其实就只是定义了一个可以调用现有函数的新函数而已。内置的functools模块可以用partial函数将此过程简化:

from functools import partial
add_five = partial(add_numbers, 5)

3.8 生成器

Python能以一种一致的方式对序列进行迭代(比如列表中的对象或文件中的行)。这是通过一种叫做迭代器协议(iterator protocol,它是一种使对象可迭代的通用方式)的方式实现的。比如说,对字典进行迭代可以得到其所有的键:

d = {'a': 1, 'b': 2, 'c': 3}
for key in d:
    print(key)

输出为:

a
b
c

当我们编写for key in some_dict时,Python解释器首先会尝试从字典创建一个迭代器:

d = {'a': 1, 'b': 2, 'c': 3}
print(iter(d))

输出为:

<dict_keyiterator object at 0x000002075BB69408>

迭代器是一种特殊对象,它可以在诸如for循环之类的上下文中向Python解释器输送对象。大部分能接受列表之类的对象的函数也都可以接受任何可迭代对象,比如min、max、sum等内置方法以及list、tuple等类型构造器:

d = {'a': 1, 'b': 2, 'c': 3}
print(min(iter(d)))  # 计算了字典d的键中最小的一个元素
print(list(iter(d)))  # 将字典d的键构造成了一个列表

输出为:

a
['a', 'b', 'c']

生成器(generator)是构造新的可迭代对象的一种简单方式。一般的函数执行之后只会返回单个值,而生成器则是以延迟的方式返回一个值序列,即每返回一个值之后暂停,直到下一个值被请求时再继续。要创建一个生成器,只需将函数中的return替换为yield即可:

def generator_function():
    for i in range(3):
        yield i


for item in generator_function():
    print(item)

输出为:

0
1
2

生成器最佳的应用场景是:当我们不想在同一时间将所有计算出来的大量结果分配到内存中,或者是我们不想一下子往内存中存入大量的数据训练模型的时候。
除了for循环,我们也可以手动通过next函数取得下一个值,但是当yield掉所有的值之后,next会触发一个StopIteration的异常,而for循环的方便之处在于它会自动捕捉到这个异常并停止调用next:

def generator_function():
    for i in range(3):
        yield i


gen = generator_function()
print(next(gen))
print(next(gen))
print(next(gen))
# print(next(gen))  # 第四次调用会报错

输出为:

0
1
2

对于一些可以迭代的类型,例如字符串、元组、列表等,我们也可以通过iter函数将它们变成迭代器对象,从而可以使用next函数:

a = [1, 2, 6, 7, 3, 4]
a_iter = iter(a)
print(next(a_iter))

输出为:

1

另一种更简洁的构造生成器的方法是使用生成器表达式(generator expression)。这是一种类似于列表、字典、集合推导式的生成器。其创建方式为,把列表推导式两端的方括号改成圆括号:

gen = (x ** 2 for x in range(100))

# 跟下面这种函数定义方式是等价的
def _make_gen():
    for x in range(100):
        yield x ** 2
gen = _make_gen()

生成器表达式也可以取代列表推导式,作为函数参数:

print(sum(x ** 2 for x in range(100)))
print(dict((i, i ** 2) for i in range(5)))

输出为:

328350
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

标准库itertools模块中有一组用于许多常见数据算法的生成器。例如,groupby函数可以接受任何序列和一个函数,根据函数的返回值对序列中的连续元素进行分组。下面是一个例子:

import itertools

first_letter = lambda x: x[0]
names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Steven']
for letter, names in itertools.groupby(names, first_letter):
    print(letter, list(names))  # names is a generator

输出为:

A ['Alan', 'Adam']
W ['Wes', 'Will']
A ['Albert']
S ['Steven']

combinations(iterable, k)函数会生成一个序列iterable中所有可能的k元元组组成的序列,不考虑顺序;permutations(iterable, k)类似,但考虑顺序,因此会生成更多的元素:

import itertools

lst = [1, 2, 3, 4]
print(list(itertools.combinations(lst, 2)))
print(list(itertools.permutations(lst, 2)))

输出为:

[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
[(1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 2), (3, 4), (4, 1), (4, 2), (4, 3)]

product(*iterables,repeat=1)生成输入的多个序列iterables的笛卡尔积,结果为元组:

import itertools

a = [1, 2, 3]
b = [4, 5, 6]
print(list(itertools.product(a, b)))

输出为:

[(1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6)]

文件操作

在Python中,为了打开一个文件以便读写,可以使用内置的open函数以及一个相对或绝对的文件路径。默认情况下,文件是以只读模式('r')打开的。然后,我们就可以像处理列表那样来处理这个文件句柄f了,比如对行进行迭代。从文件中取出的行都带有完整的行结束符,因此我们也可以使用rstrip函数去掉这个行结束符。如果使用open创建文件对象,一定要用close关闭它,因为关闭文件可以返回操作系统资源。

例如我们有一个a.txt这个文件:

path = 'a.txt'
f = open(path)
for line in f:
    print(line.rstrip())
f.close()

用with语句可以更容易地清理打开的文件。这样可以在退出代码块时,自动关闭文件。

path = 'a.txt'
with open(path) as f:
   print([x.rstrip() for x in f])

除了只读模式,我们也可以写文件:

with open('tmp.txt', 'w') as f:
   f.writelines(str(x) for x in range(10))  # 这里注意需要将要写入文本文件的变量转成字符串类型

常用的文件操作模式如下:

  • r: 只读模式。
  • w: 只写模式。创建新文件,如果文件已经存在,会进行覆盖。
  • a: 追加写。如果文件不存在则新建一个。
  • r+: 读写模式。
  • b: 附加说明用于二进制文件,即'rb'或'wb'。

错误和异常处理

Python中也有错误和异常处理的机制。例如,Python的float函数可以将字符串转换成浮点数,但输入有误时,有ValueError错误:

print(float('1.2345'))
print(float('string'))

输出为:

1.2345
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-17-ab350e8097a9> in <module>
      1 print(float('1.2345'))
----> 2 print(float('string'))

ValueError: could not convert string to float: 'string'

假如想优雅地处理float的错误,让它返回输入值。我们可以写一个函数,在try/except中调用float。当float(x)抛出异常时,才会执行except的部分:

def attempt_float(x):
    try:
        return float(x)
    except:
        return x


print(attempt_float('1.2345'))
print(attempt_float('string'))

我们也可以在except中指明一个异常类型,或者用元组指明多个异常:

def attempt_float(x):
    try:
        return float(x)
    except (TypeError, ValueError):
        return x

某些情况下,你可能不想抑制异常,你想无论try部分的代码是否成功,都执行一段代码。可以使用finally:

path = 'tmp.txt'
def write_to_file(f):
    f.writelines(str(x) for x in range(10))


f = open(path, 'w')

try:
    write_to_file(f)
finally:
    f.close()

对象和类

6.1 对象

万物皆对象:Python语言的一个重要特性就是它的对象模型的一致性。每个数字、字符串、数据结构、函数、类、模块等等,都是在Python解释器的自有“盒子”内,它被认为是Python对象。每个对象都有类型(例如,字符串或函数)和内部数据。在实际中,这可以让语言非常灵活,因为函数也可以被当做对象使用。之前提到过,我们在定义一个变量的时候,不需要指明它的类型,后面也可以修改:

a = 5  # 这是一个整数
print(type(a))
a = 'string'  # 我们可以将其改成一个字符串
print(type(a))

输出为:

<class 'int'>
<class 'str'>

我们可以用isinstance函数检查对象是否是某种类型:

a = 5
print(isinstance(a, int))

输出为:

True

isinstance可以用类型元组,检查对象的类型是否在元组中:

a = 5
b = 4.5

print(isinstance(a, (int, float)))
print(isinstance(b, (int, float)))

输出为:

True
True

Python的对象通常都有属性(其它存储在对象内部的Python对象)和方法(对象的附属函数可以访问对象的内部数据)。可以用obj.attribute_name访问属性和方法:

a = 'string'
print(a.upper())

输出为:

STRING

在输入a.之后按Tab键,会显示默认出默认的方法有哪些。
也可以用getattr函数,通过名字访问属性和方法:

a = 'string'
print(getattr(a, 'split'))

输出为:

<built-in method split of str object at 0x0000020758827928>

要判断两个变量是否指向同一个对象,可以使用is方法。is not可以判断两个对象是不同的:

a = [1, 2, 3]
b = a
c = list(a)  # 因为list函数总是创建一个新的Python列表(即复制),我们可以断定c是不同于a的。

print(a is b)
print(a is c)

输出为:

True
False

使用is比较与==运算符不同,如下:

a = [1, 2, 3]
c = list(a)

print(a is c)
print(a == c)

输出为:

False
True

is和is not常用来判断一个变量是否为None,因为只有一个None的实例:

a = None

print(a is None)
print(a == None)  # 不建议这样比较

输出为:

True
True

Python中的大多数对象,比如前面讲到的列表、字典、后面会讲到的NumPy数组,和用户定义的类型(类),都是可变的。意味着这些对象或包含的值可以被修改:

a = [1, 2, 3]
a[1] = 4  # 列表是可变对象
print(a)

输出为:

[1, 4, 3]

其它的,例如字符串和元组,是不可变的:

a = (1, 2, 3)
a[1] = 4  # 元组是不可变对象

输出为:

TypeError                                 Traceback (most recent call last)
<ipython-input-27-02309702bb07> in <module>
      1 a = (1, 2, 3)
----> 2 a[1] = 4  # 元组是不可变对象

TypeError: 'tuple' object does not support item assignment

可以修改一个对象并不意味就要修改它。这被称为副作用。例如,当写一个函数,任何副作用都要在文档或注释中写明。如果可能的话,我们推荐避免副作用,采用不可变的方式,即使要用到可变对象。
我们可以看一下副作用会导致的问题。使用列表的时候:

a = [1, 2, 3]
b = a  # 前面看到b和a对应同一个对象
a[1] = 4  # 有可能我们只想改变a
print(b)  # 但是b也被意外地改变了

输出为:

[1, 4, 3]

有三种简单的方式可以避免这种情况:

a = [1, 2, 3]
# b = list(a)  # 前面提到的list函数
# b = a[:]  # 用切片
b = a.copy()  # 用copy函数
a[1] = 4  # 有可能我们只想改变a
print(b)  # b没有被改变

输出为:

[1, 2, 3]

在使用函数参数的时候,也需要小心副作用:

def my_function(d):
    d['a'] = 3  # 我们在函数内部修改了这个可变变量
    print(d)


d = {'a': 1, 'b': 2}
my_function(d)
print(d)  # 我们发现这种影响传达到了函数外部

输出为:

{'a': 3, 'b': 2}
{'a': 3, 'b': 2}

要避免这种情况,要么就是在函数内部避免修改这些传入的参数,要么就是用copy函数等方式复制一个副本,再进行修改:

def my_function(d):
    d = d.copy()  # 在内部我们创建了一个“临时”的变量
    d['a'] = 3  # 我们在函数内部修改了这个可变变量
    print(d)


d = {'a': 1, 'b': 2}
my_function(d)
print(d)  # 我们阻止了这种影响传达到函数外部

输出为:

{'a': 3, 'b': 2}
{'a': 1, 'b': 2}

6.2 类

6.2.1 类和实例

面向对象最重要的概念是类(Class)和实例(Instance):类是抽象的模板,而实例是根据类创建出来的一个个具体的对象。我们可以用class关键词定义一个类:

class Student(object):
    pass

class后面跟着的Student表示类名,而(object)代表继承的最一般的Python自带的object类,后面我们会看到Python里面可以继承一个类然后发展出来新的类。
定义了类之后,我们就可以创建Student类的实例,例如下面的tom,实例是有对应的内存地址的。

tom = Student()
print(tom)
print(Student)

输出为:

<__main__.Student object at 0x0000018013415278>
<class '__main__.Student'>

我们可以用type()来检查变量的类型:

print(type(tom))
print(type(Student))

输出为:

<class '__main__.Student'>
<class 'type'>

我们也可以用isinstance()来判断一个实例是否属于某个类:

print(isinstance(tom, Student))

输出为:

True

我们可以给一个实例变量绑定属性:

tom.name = 'Tom'
tom.score = 59
print(tom.name)
print(tom.score)

输出为:

Tom
59

如果每个学生都需要有姓名和分数,而且最好在初始化的时候就设置好,那么我们可以在Student类中加一个特殊的init函数(特殊的函数前后都是两个下划线):

class Student(object):

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

当我们在调用这个初始化函数时,self不需要传参数,因为Python解释器自己会把实例变量传进去:

tom = Student('Tom', 59)
print(tom.name)
print(tom.score)

输出为:

Tom
59

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且调用时不用传递该参数。除此之外,类的方法和普通函数没有什么区别。我们可以用dir()来查看一个类中的全部函数:

print(dir(Student))
print(dir(tom))

输出为:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'score']

我们可以修改这些内置的函数,例如init。再例如修改了str之后,当我们print一个实例时,就会自动调用这个函数把这个实例转成字符串。

# 修改前
print(tom)

输出为:

<__main__.Student object at 0x0000018013415588>

重新修改Student类的定义

class Student(object):

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

    def __str__(self):
        return '%s gets a score of %s' % (self.name, self.score)

# 重新定义tom
tom = Student('Tom', 59)

# 重新print tom
print(tom)

这次输出为:

Tom gets a score of 59

6.2.2 继承

当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。比如我们上面定义的Student类其实就是继承了object这个类,我们还可以在Student类的基础上去定义新的类:

class GoodStudent(Student):
    pass

即便我们什么都不做,GoodStudent也具备了Student类的所有功能:

# 重新定义tom
tom = GoodStudent('Tom', 59)

# 重新print tom
print(tom)

输出为:

Tom gets a score of 59

在我们定义子类的时候,可以添加新的函数:

class GoodStudent(Student):

    def reward(self, num):
        print('%s gets a reward of %s' % (self.name, str(num)))

tom = GoodStudent('Tom', 59)
tom.reward(10)

输出为:

Tom gets a reward of 10

tom既是Student类的实例,也是GoodStudent类的实例:

print(isinstance(tom, Student))
print(isinstance(tom, GoodStudent))

输出为:

True
True

继承的关系还可以变得更加复杂,例如多重继承。

6.2.3 访问限制

在我们目前定义的类里面,name,score等属性都是直接可以访问和修改的:

class Student(object):

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

    def __str__(self):
        return '%s gets a score of %s' % (self.name, self.score)

# 定义tom
tom = Student('Tom', 59)

# 查看tom的分数
print(tom)

# 直接修改tom的分数
tom.score = 73

# 查看修改后的分数
print(tom)

输出为:

Tom gets a score of 59
Tom gets a score of 73

如果说想要保护内部属性不被外部访问,那么我们可以在属性的名称前面加两个下划线:

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def __str__(self):
        return '%s gets a score of %s' % (self.__name, self.__score)

# 定义tom
tom = Student('Tom', 59)

# 查看tom的分数
print(tom)
print(tom._Student__score)  # 被自动地改名为_Student__score

输出为:

Tom gets a score of 59
59

比较好的方式是定义set和get函数来获取这些属性:

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def __str__(self):
        return '%s gets a score of %s' % (self.__name, self.__score)

    def get_score(self):
        return self.__score

    def set_score(self, score):
        self.__score = score

# 定义tom
tom = Student('Tom', 59)

# 修改tom的分数
tom.set_score(66)

# 查看tom的分数
print(tom.get_score())

此时查看tom的分数就是我们设置的值66.

小结

在这一部分,我们介绍了Python中函数的定义和使用方式、文件的读写、错误与异常的处理,以及对象和类的基本内容。

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务