2 python-模块和类

来源:互联网 发布:it行业发展方向 编辑:程序博客网 时间:2024/05/22 17:00

1 模块

为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Python中,一个.py文件就称之为一个模块(Module)。

我们在编写程序的时候,也经常引用其他模块,包括Python内置的模块和来自第三方的模块。

使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,但是也要注意,尽量不要与内置函数名字冲突。

https://docs.python.org/3/library/functions.html#abs

为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。

引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。

每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany

1.1 使用模块

(1)编写模块

#!/usr/bin/env python3# -*- coding: utf-8 -*-' a test module '__author__ = 'Michael Liao'import sysdef test():    args = sys.argv    if len(args)==1:        print('Hello, world!')    elif len(args)==2:        print('Hello, %s!' % args[1])    else:        print('Too many arguments!')if __name__=='__main__':    test()

第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;

第6行使用__author__变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;

假如我们只想在程序本身被使用的时候运行主块,而在它被别的模块输入的时候不运行主块,我们该怎么做呢?这可以通过模块的__name__属性完成。

最后,注意到这两行代码:

if __name__=='__main__':    test()

当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

(2)导入模块

使用hello模块的第一步,就是导入该模块:

>>> import hello>>>> hello.test()Hello, world!

(3)from..import语句

如果你想要直接输入argv变量到你的程序中(避免在每次使用它时打sys.),那么你可以使用
from sys import argv语句。如果你想要输入所有sys模块使用的名字,那么你可以使用from sys import *语句。

(4)dir()函数

你可以使用内建的dir函数来列出模块定义的标识符。标识符有函数、类和变量。

当你为dir()提供一个模块名的时候,它返回模块定义的名称列表。如果不提供参数,它返回当前模块中定义的名称列表。

>>> import sys>>> dir(sys) # get list of attributes for sys module

1.2 作用域

在一个模块中,有的函数和变量希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。

(1)正常的函数和变量名是公开的(public),可以被直接引用,比如:abcx123PI等;

(2)特殊变量,可以被直接引用,但是有特殊用途:类似__xxx____author____name__
hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名;

(3)非公开的函数或变量(private),不应该被直接引用,类似_xxx__xxx这样的函数或变量。

(4)private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。

def _private_1(name):    return 'Hello, %s' % namedef _private_2(name):    return 'Hi, %s' % namedef greeting(name):    if len(name) > 3:        return _private_1(name)    else:        return _private_2(name)

1.3 安装第三方模块

一般来说,第三方库都会在Python官方的pypi.python.org网站注册。

https://pypi.python.org/pypi

模块搜索路径:

当我们试图加载一个模块时,Python会在指定的路径下搜索对应的.py文件。默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中:

>>> import sys>>> sys.path

添加自己的搜索目录,有两种方法:

(1)临时修改:

>>> import sys>>> sys.path.append('/Users/michael/my_py_scripts')

(2)设置环境变量PYTHONPATH

该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。

2 面向对象编程

2.1 self

类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称,但是在调用这个方法的时候你不为这个参数赋值,Python会提供这个值。这个特别的变量指对象本身,按照惯例它的名称是self。

2.2 类

(1)创建一个类

class Student(object):    pass

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

(2)创建类的实例

>>> bart = Student()>>> bart<__main__.Student object at 0x10a67a590>>>> Student<class '__main__.Student'>

(3)可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:

>>> bart.name = 'Bart Simpson'>>> bart.name'Bart Simpson'

(4)通过定义一个特殊的__init__方法,做一些初始化的工作:

class Student(object):    def __init__(self, name, score):        self.name = name        self.score = score

①注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

②有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:

>>> bart = Student('Bart Simpson', 59)>>> bart.name'Bart Simpson'>>> bart.score59

(5)数据封装:

class Student(object):    def __init__(self, name, score):        self.name = name        self.score = score    def print_score(self):        print('%s: %s' % (self.name, self.score))

2.3 访问权限

(1)如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

class Student(object):    def __init__(self, name, score):        self.__name = name        self.__score = score    def print_score(self):        print('%s: %s' % (self.__name, self.__score))

无法从外部访问实例变量.__name实例变量.__score了:

>>> bart = Student('Bart Simpson', 98)>>> bart.__nameTraceback (most recent call last):  File "<stdin>", line 1, in <module>AttributeError: 'Student' object has no attribute '__name'

(2)但是如果外部代码要获取name和score,可以给Student类增加get_nameget_score这样的方法:

class Student(object):    ...    def get_name(self):        return self.__name    def get_score(self):        return self.__score    def set_score(self, score):        self.__score = score

注: 需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

(3)有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

(4)双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

>>> bart._Student__name'Bart Simpson'

但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。

(5)最后注意下面的这种错误写法:

>>> bart = Student('Bart Simpson', 98)>>> bart.get_name()'Bart Simpson'>>> bart.__name = 'New Name' # 设置__name变量!>>> bart.__name'New Name'

表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量。

>>> bart.get_name() # get_name()内部返回self.__name'Bart Simpson'

2.4 继承和多态

(1)判断一个变量是否是某个类型可以用isinstance()判断:

>>> isinstance(a, list)True>>> isinstance(b, Animal)True>>> isinstance(c, Dog)True

因为Dog是从Animal继承下来的:

>>> isinstance(c, Animal)True

但是,反过来不行:

>>> b = Animal()>>> isinstance(b, Dog)False

(2)继承

class Animal(object):    def run(self):        print('Animal is running...')class Dog(Animal):    def run(self):        print('Dog is running...')class Cat(Animal):    def run(self):        print('Cat is running...')

(3)多态

def run_twice(animal):    animal.run()    animal.run()>>> run_twice(Animal())Animal is running...Animal is running...>>> run_twice(Dog())Dog is running...Dog is running...>>> run_twice(Cat())Cat is running...Cat is running...

(4)静态语言 vs 动态语言

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:

class Timer(object):    def run(self):        print('Start...')

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

2.5 获取对象信息

(1)使用type()

判断对象类型,使用type()函数:

>>> type(123)<class 'int'>>>> type('str')<class 'str'>>>> type(None)<type(None) 'NoneType'>

type()函数返回的是什么类型呢?它返回对应的Class类型。

>>> type(123)==type(456)True>>> type(123)==intTrue>>> type('abc')==type('123')True>>> type('abc')==strTrue>>> type('abc')==type(123)False

要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:

>>> import types>>> def fn():...     pass...>>> type(fn)==types.FunctionTypeTrue>>> type(abs)==types.BuiltinFunctionTypeTrue>>> type(lambda x: x)==types.LambdaTypeTrue>>> type((x for x in range(10)))==types.GeneratorTypeTrue

(2)使用isinstance()

如果继承关系是:

object -> Animal -> Dog -> Husky>>> a = Animal()>>> d = Dog()>>> h = Husky()>>> isinstance(h, Husky)True>>> isinstance(h, Dog)True>>> isinstance(h, Animal)True

能用type()判断的基本类型也可以用isinstance()判断:

>>> isinstance('a', str)True>>> isinstance(123, int)True>>> isinstance(b'a', bytes)True

并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:

>>> isinstance([1, 2, 3], (list, tuple))True>>> isinstance((1, 2, 3), (list, tuple))True

(3)使用dir()

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list:

>>> dir('ABC')

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

>>> len('ABC')3>>> 'ABC'.__len__()3

配合getattr()setattr()以及hasattr(),我们可以直接操作一个对象的状态,可以测试该对象的属性:

>>> hasattr(obj, 'x') # 有属性'x'吗?True>>> obj.x9>>> hasattr(obj, 'y') # 有属性'y'吗?False>>> setattr(obj, 'y', 19) # 设置一个属性'y'>>> hasattr(obj, 'y') # 有属性'y'吗?True>>> getattr(obj, 'y') # 获取属性'y'19>>> obj.y # 获取属性'y'19

可以传入一个default参数,如果属性不存在,就返回默认值:

>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404404

2.6 实例属性和类属性

由于Python是动态语言,根据类创建的实例可以任意绑定属性。

(1)实例属性

给实例绑定属性的方法是通过实例变量,或者通过self变量:

class Student(object):    def __init__(self, name):        self.name = names = Student('Bob')s.score = 90

(2)类属性

如果Student类本身需要绑定一个属性呢?可以直接在class中定义属性,这种属性是类属性,归Student类所有:

class Student(object):    name = 'Student'>>> class Student(object):...     name = 'Student'...>>> s = Student() # 创建实例s>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性Student>>> print(Student.name) # 打印类的name属性Student>>> s.name = 'Michael' # 给实例绑定name属性>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性Michael>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问Student>>> del s.name # 如果删除实例的name属性>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了Student

从上面的例子可以看出,在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

这点没讲和清楚。