首页 Python 入门 - 11 - 类
文章
取消

Python 入门 - 11 - 类

Python 类源自于 C++Modula-3 这两种语言的类机制的结合。
Python 中一切皆 对象(Object),类里边又引入了 3 种对象:类对象(Class)实例对象(Instance)方法对象(Method)

作用域和命名空间

作用域(scope) 指的是 Python 代码中的一个文本区域,分为以下几类:

  • 模块
  • 函数

命名空间(namespace) 是一个名字到对象的映射,一个作用域对应会有一个命名空间来保存该作用域中的 名称(name),Python 中按照以下顺序去查找一个名称:

  1. 最内部作用域的命名空间(包含局部名称)
  2. 最内部作用域与最近的作用域之间的 中间作用域 的命名空间(包含非全局名称 nonlocal
  3. 当前模块的命名空间(包含全局名称 global
  4. 内置名称模块(builtins)的命名空间

命名空间是动态创建的,不同时刻创建的命名空间具有不同的生存期:

  • 包含内置名称的命名空间(builtins)是在 Python 解释器启动时创建的,会持续到解释器退出;
  • 模块的全局命名空间在模块被读入时创建,也会持续到解释器退出();
  • 函数的本地命名空间在函数被调用时创建,当函数返回或者抛出异常时被删除(递归调用的函数每次都有自己的本地命名空间);

以下是一个作用域和命名空间的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

# 其输出内容是:
'''
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
'''

类定义

语法格式

类定义语法格式如下:

1
2
3
4
5
6
class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

通常类定义内的语句都是 函数定义,但也可以是其他语句(如属性定义等)。
在进入类定义时,将创建一个 命名空间,用于保存类中的名称。
当(从结尾处)正常离开类定义时,将创建一个 类对象

类对象支持两种操作:属性引用实例化

1
2
3
4
5
6
class MyClass:
    """A simple example class"""
    i = 12345

    def f(self):
        return 'hello world'

例如以上示例中定义的 MyClass 类,那么 MyClass.i 和 MyClass.f 就是有效的属性引用,将分别返回一个整数和一个函数对象。
类的 实例化 使用函数表示法:

1
2
# 创建类的新 实例 并将此对象分配给局部变量 x
x = MyClass()

初始化方法

类中可以定义一个 __init__() 方法,用于自定义类的初始化操作:

1
2
3
class MyClass:
    def __init__(self):
        self.data = []

__init__() 方法定义时还可以有额外的参数:

1
2
3
4
5
6
7
8
>>> 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)

self 参数

类中定义的方法的第一个参数固定被当作类初始化后的 实例本身,通常写作 self,可在方法中使用该变量引用其他 实例属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart

    def get_realpart(self):
        return self.r

    def get_imagpart(self):
        return self.i

    # 也可以不定义参数,只是这样就不能在该方法中访问实例属性 r 和 i 了
    def get_nothing():
        return None

访问限制

如果类中定义的 属性(包括变量和方法) 是以 双下划线(__) 开头,并且以 最多一个下划线结尾,那么即表示该属性是 私有的(Private),不能从外部访问:

1
2
3
4
5
6
7
8
9
10
>>> class Student(object):
...     def __init__(self, name, score):
...         self.__name = name
...         self.__score = score
... 
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

实际上 Python 并没有机制严格的限制对私有属性的访问,只是简单的对私有属性进行了 改名(加上了下划线开头的类名前缀) 而已:

1
2
3
4
5
6
7
8
9
>>> class Student(object):
...     def __init__(self, name, score):
...         self.__name = name
...         self.__score = score
... 
>>> bart = Student('Bart Simpson', 59)
>>  # 虽然通过下面这种方式可以访问私有属性,但强烈不建议这样做
>>> bart._Student__name
'Bart Simpson' 

双下划线开头 并且 双下划线结尾 的名称是 Python 类的一些特殊属性,比如 __name__ 表示类名,__doc__ 表示类注释,还有用于初始化实例的 __init__() 方法等,所以建议也不要自定义这样的属性:

1
2
3
4
5
6
7
8
9
>>> class MyClass(object):
...     """my class"""
...     pass
...
>>> MyClass.__name__
'MyClass'
>>> MyClass.__doc__
'my class'
>>>

单下划线 开头的属性也可以直接访问,但是约定俗成的规范是这样的属性表示 保护属性,即能在子类中访问,但不建议从外部访问。

数据属性 会覆盖掉同名的 方法属性,为了避免这种情况发生,建议数据属性使用名词,方法属性使用动词。

类与实例

类是抽象模板,实例是根据类创建出来的具体的对象,每个实例都拥有相同的方法,但各自的数据可能不同。

1
2
3
4
5
6
7
8
9
10
11
>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...
>>> student1 = Student('tom')
>>> student2 = Student('jack')
>>> student1.name
'tom'
>>> student2.name
'jack'
>>>

在类中直接定义的属性为 类属性,而绑定到实例上的属性是 实例属性,类属性是所有实例 共有 的,实例属性是每个实例 独有 的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> class Student(object):
...     # 类属性
...     clsname = 'Student'
...     def __init__(self, instname):
...         # 实例属性
...         self.instname = instname
...
>>> student1 = Student('tom')
>>> student2 = Student('jack')
>>> student1.clsname
'Student'
>>> student1.instname
'tom'
>>> student2.clsname
'Student'
>>> student2.instname
'jack'
>>>

类继承

单继承

单继承的语法格式如下:

1
2
3
4
5
6
class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

其中 BaseClassName 叫做 DerivedClassName基类,也可叫做 父类超类,DerivedClassName 则叫做 BaseClassName 的 子类

通过继承的方式,子类可以拥有父类的所有属性(包括数据属性和方法属性),当引用类的属性时,搜索顺序是先搜索子类,再搜索父类,然后是父类的父类,依次往上,所以子类如果定义了和父类同名的属性,就相当于覆盖了父类的同名属性。

多重继承

多种继承的语法格式如下:

1
2
3
4
5
6
class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

子类 DerivedClassName 同时从父类 Base1Base2Base3 继承,这种情况下搜索一个属性的顺序是 深度优先、从左至右、同一个类只搜索一次,比如以上示例中先搜索 DerivedClassName,然后搜索 Base1,然后搜索 Base1 的父类一直搜索到顶,然后再搜索 Base2 依次到顶,依此类推。

关于更详细的搜索顺序见:https://www.python.org/download/releases/2.3/mro/

获取对象信息

type()

使用 type() 函数可以判断一个对象的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> type(123)
<class 'int'>
>>> type('hello')
<class 'str'>
>>> type(True)
<class 'bool'>
>>> type(int)  # 类的类型都是 type
<class 'type'>
>>> type(str)
<class 'type'>
>>> type(bool)
<class 'type'>
>>>

types 模块中定义了各种类型,可以方便直观的用于类型判断和比较:

1
2
3
4
5
6
7
8
9
10
11
12
>>> import types
>>> def fn():
...     pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

isinstance()

isinstance() 函数可以判断一个实例是否为某个 类或其子类 的实例:

1
2
3
4
5
6
7
8
9
10
11
12
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
>>> isinstance([1, 2, 3], (list, tuple))  # 判断是否某些类型中的一种
True
>>> isinstance((1, 2, 3), (list, tuple))
True
>>> isinstance(True, int)  # bool 是 int 的子类
True

issubclass()

issubclass() 函数可以判断某个类是否是另一个类的子类:

1
2
3
4
>>> issubclass(bool, int)  # bool 是 int 的子类
True
>>> issubclass(str, int)
False

dir()

dir() 函数可以获取一个对象的所有 属性方法:

1
2
>>> dir('hello')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

getattr(), setattr(), hasattr()

使用 getattr(), setattr(), hasattr() 这三个函数可以操作对象的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19

类的一些特殊属性

下面只简单介绍一些特殊属性,完整的特性属性详见:https://docs.python.org/3/reference/datamodel.html#special-method-names

__iter__()

如果一个类想被用于 for ... in 循环,类似 list 或 tuple 那样,就必须实现一个 __iter__() 方法,该方法返回一个迭代对象,然后,Python 的 for 循环就会不断调用该迭代对象的 __next__() 方法拿到循环的下一个值,直到遇到 StopIteration 错误时退出循环。

我们以斐波那契数列为例,写一个 Fib 类,可以作用于 for 循环:

1
2
3
4
5
6
7
8
9
10
11
12
class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化两个计数器a,b

    def __iter__(self):
        return self # 实例本身就是迭代对象,故返回自己

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b # 计算下一个值
        if self.a > 100000: # 退出循环的条件
            raise StopIteration()
        return self.a # 返回下一个值

现在,试试把 Fib 实例作用于 for 循环:

1
2
3
4
5
6
7
8
9
10
11
>>> for n in Fib():
...     print(n)
...
1
1
2
3
5
...
46368
75025

__len__

如果想要让对象适用于 len() 函数,即像 list 或 tuple 那样,可以自定义一个 __len__() 方法:

1
2
3
4
5
6
7
8
9
>>> class MyList(object):
...     def __init__(self, datas):
...         self.datas = datas
...     def __len__(self):
...         return len(self.datas)
...
>>> mylist = MyList([1, 2, 3])
>>> len(mylist)
3

参考资料

  • [Classes] : https://docs.python.org/3.5/tutorial/classes.html
  • [Special method names] : https://docs.python.org/3/reference/datamodel.html#special-method-names
  • [面向对象编程] : https://www.liaoxuefeng.com/wiki/1016959663602400/1017495723838528
  • [面向对象高级编程] : https://www.liaoxuefeng.com/wiki/1016959663602400/1017501628721248
本文由作者按照 CC BY 4.0 进行授权