https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318447437605e90206e261744c08630a836851f51830001.模块 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里。 在Python中一个.py文件就称为一个模块 模块的好处: 1、大大提高代码的可维护性 2、一个模块编写完毕,可以被其他模块引用 3、使用模块还可以避免函数名和变量名冲突,相同的函数名以及变量名可以放在不同的模块当中。 为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package) 2.使用模块 Python本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用。 #!/usr/bin/env python3 # -*- coding: utf-8 -*- ‘ a test module ‘ __author__ = ‘Michael Liao‘ import sys def 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() 第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码; 第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释; 第6行使用__author__变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名; 以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。 后面开始就是真正的代码部分。 你可能注意到了,使用sys模块的第一步,就是导入该模块: import sys 导入sys模块后,我们就有了变量sys指向该模块,利用sys这个变量,就可以访问sys模块的所有功能。 sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称,例如: 运行python3 hello.py获得的sys.argv就是[‘hello.py‘]; 运行python3 hello.py Michael获得的sys.argv就是[‘hello.py‘, ‘Michael]。 最后,注意到这两行代码: if __name__==‘__main__‘: test() 当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。 我们可以用命令行运行hello.py看看效果: $ python3 hello.py Hello, world! $ python hello.py Michael Hello, Michael! 如果启动Python交互环境,再导入hello模块: $ python3 Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 23 2015, 02:52:03) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import hello >>> 导入时,没有打印Hello, word!,因为没有执行test()函数。 调用hello.test()时,才能打印出Hello, word!: >>> hello.test() Hello, world! 3.作用域 在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。 正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等; 类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊变量, hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名; 类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc等; 之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。 private函数或变量不应该被别人引用,那它们有什么用呢?请看例子: def _private_1(name): return ‘Hello, %s‘ % name def _private_2(name): return ‘Hi, %s‘ % name def greeting(name): if len(name) > 3: return _private_1(name) else: return _private_2(name) 我们在模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了,这样, 调用greeting()函数不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即: 外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。 如果在命令行直接调用该程序文件,该文件作为主程序入口,name == ‘main‘理所当然啊。 如果在命令行调用其他程序文件,主程序入口name == ‘main‘自然不成立,因为main等于那个你在命令行输入的程序名。 这个东西的好处就是,别人调用时(你并非是主程序入口)后面的东西不运行,自己命令行执行时(你是主程序入口)后面的东西运行。故可以作为测试用。 3.安装第三方模块 安装第三方模块,是通过包管理工具pip完成的 注意:Mac或Linux上有可能并存Python 3.x和Python 2.x,因此对应的pip命令是pip3。 一般来说,第三方库都会在Python官方的pypi.python.org网站注册,要安装一个第三方库,必须先知道该库的名称, 可以在官网或者pypi上搜索,比如Pillow的名称叫Pillow,因此,安装Pillow的命令就是: pip install Pillow 有了Pillow,处理图片易如反掌。随便找个图片生成缩略图: >>> from PIL import Image >>> im = Image.open(‘test.png‘) >>> print(im.format, im.size, im.mode) PNG (400, 300) RGB >>> im.thumbnail((200, 100)) >>> im.save(‘thumb.jpg‘, ‘JPEG‘) 其他常用的第三方库还有 MySQL的驱动:mysql-connector-python, 用于科学计算的NumPy库:numpy, 用于生成文本的模板工具Jinja2,等等。 默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中: >>> import sys >>> sys.path 如果我们要添加自己的搜索目录,有两种方法: 一是直接修改sys.path,添加要搜索的目录: >>> import sys >>> sys.path.append(‘/Users/michael/my_py_scripts‘) 这种方法是在运行时修改,运行结束后失效。 第二种方法是设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。 设置方式与设置Path环境变量类似。注意只需要添加你自己的搜索路径,Python自己本身的搜索路径不受影响。 4.面向对象编程 所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。 我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。 假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个dict表示: std1 = { ‘name‘: ‘Michael‘, ‘score‘: 98 } std2 = { ‘name‘: ‘Bob‘, ‘score‘: 81 } 而处理学生成绩可以通过函数实现,比如打印学生的成绩: def print_score(std): print(‘%s: %s‘ % (std[‘name‘], std[‘score‘])) 如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有name和score这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。 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)) 给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样: bart = Student(‘Bart Simpson‘, 59) lisa = Student(‘Lisa Simpson‘, 87) bart.print_score() lisa.print_score() 面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student。 所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。 面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。 5.类和实例 面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板, 比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。 仍以Student类为例,在Python中,定义类是通过class关键字: class Student(object): pass class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object), 表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类, 就使用object类,这是所有类最终都会继承的类。 定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的: >>> bart = Student() >>> bart <__main__.Student object at 0x10a67a590> >>> Student <class ‘__main__.Student‘> 可以看到,变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样, 而Student本身则是一个类。可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性: >>> bart.name = ‘Bart Simpson‘ >>> bart.name ‘Bart Simpson‘ 由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。 通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去: class Student(object): def __init__(self, name, score): self.name = name self.score = score 注意:特殊方法“init”前后有两个下划线!!! 注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部, 就可以把各种属性绑定到self,因为self就指向创建的实例本身。有了__init__方法,在创建实例的时候, 就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去: >>> bart = Student(‘Bart Simpson‘, 59) >>> bart.name ‘Bart Simpson‘ >>> bart.score 59 和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。 数据封装 面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例就拥有各自的name和score这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩: >>> def print_score(std): ... print(‘%s: %s‘ % (std.name, std.score)) ... >>> print_score(bart) Bart Simpson: 59 但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问, 可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类 本身是关联起来的,我们称之为类的方法: 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)) 要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用, 除了self不用传递,其他参数正常传入: >>> bart.print_score() Bart Simpson: 59 这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出name和score,而如何打印, 都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。 封装的另一个好处是可以给Student类增加新的方法,比如get_grade: class Student(object): ... def get_grade(self): if self.score >= 90: return ‘A‘ elif self.score >= 60: return ‘B‘ else: return ‘C‘ 同样的,get_grade方法可以直接在实例变量上调用,不需要知道内部实现细节: >>> bart.get_grade() ‘C‘ 小结 类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响; 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据; 通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。 和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不 同实例,但拥有的变量名称都可能不同: >>> bart = Student(‘Bart Simpson‘, 59) >>> lisa = Student(‘Lisa Simpson‘, 87) >>> bart.age = 8 >>> bart.age 8 >>> lisa.age Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: ‘Student‘ object has no attribute ‘age‘ 6.访问限制 在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据, 这样,就隐藏了内部的复杂逻辑。但是,从前面Student类的定义来看,外部代码还是可以自由 地修改一个实例的name、score属性: >>> bart = Student(‘Bart Simpson‘, 98) >>> bart.score 98 >>> bart.score = 59 >>> bart.score 59 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中, 实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改: 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怎么办?可以给Student类增加get_name和get_score这样的方法 class Student(object): ... def get_name(self): return self.__name def get_score(self): return self.__score 如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score方法: class Student(object): ... def set_score(self, score): self.__score = score 在方法中,可以对参数做检查,避免传入无效的参数: class Student(object): ... def set_score(self, score): if 0 <= score <= 100: self.__score = score else: raise ValueError(‘bad score‘) 需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量, 特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。有些时候,你会看到 以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到 这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。 双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外 把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量: >>> bart._Student__name ‘Bart Simpson‘ 但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。 7.继承和多态 判断一个变量是否是某个类型可以用isinstance()判断: >>> isinstance(a, list) True class Animal(object): def run(self): print(‘Animal is running...‘) class Dog(Animal): def run(self): print(‘Dog is running...‘) def eat(self): print(‘Eating meat...‘) class Cat(Animal): def run(self): print(‘Cat is running...‘) >>> isinstance(c, Animal) def run_twice(animal): animal.run() animal.run() >>> run_twice(Animal()) >>> run_twice(Dog()) >>> run_twice(Cat()) class Tortoise(Animal): def run(self): print(‘Tortoise is running slowly...‘) >>> run_twice(Tortoise()) 新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。 多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了, 因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法, 因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思: 对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法, 而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定, 这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确, 不用管原来的代码是如何调用的。这就是著名的“开闭”原则: 对扩展开放:允许新增Animal子类; 对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。 继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。比如如下的继承树: 8.静态语言 vs 动态语言 对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。 对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了: class Timer(object): def run(self): print(‘Start...‘) 这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。 Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象, 只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正 的文件对象,完全可以传入任何实现了read()方法的对象。 小结 继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。 动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。 9.获取对象信息 当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢? 使用type() 使用isinstance() 使用dir() 使用type() 基本类型都可以用type()判断: >>> type(123) <class ‘int‘> >>> type(‘str‘) <class ‘str‘> >>> type(None) <type(None) ‘NoneType‘> 如果一个变量指向函数或者类,也可以用type()判断: >>> type(abs) <class ‘builtin_function_or_method‘> >>> type(a) <class ‘__main__.Animal‘> 但是type()函数返回的是什么类型呢?它返回对应的Class类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同: >>> type(123)==type(456) True >>> type(123)==int True >>> type(‘abc‘)==type(‘123‘) True >>> type(‘abc‘)==str True >>> type(‘abc‘)==type(123) False 判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量: >>> 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()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上 对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。 我们回顾上次的例子,如果继承关系是: object -> Animal -> Dog -> Husky 那么,isinstance()就可以告诉我们,一个对象是否是某种类型。先创建3种类型的对象: >>> a = Animal() >>> d = Dog() >>> h = Husky() 然后,判断: >>> isinstance(h, Husky) 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 使用dir() 如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list, 比如,获得一个str对象的所有属性和方法: >>> dir(‘ABC‘) [‘__add__‘, ‘__class__‘, ‘__contains__‘, ‘__delattr__‘, ‘__dir__‘, ‘__doc__‘, ‘__eq__‘, ‘__format__‘, ‘__ge__‘, ‘__getattribute__‘, ‘__getitem__‘, ‘__getnewargs__‘, ‘__gt__‘, ‘__hash__‘, ‘__init__‘, ‘__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‘, ‘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‘] 类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中, 如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法, 所以,下面的代码是等价的: >>> len(‘ABC‘) 3 >>> ‘ABC‘.__len__() 3 我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法: >>> class MyDog(object): ... def __len__(self): ... return 100 ... >>> dog = MyDog() >>> len(dog) 100 仅仅把属性和方法列出来是不够的,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态: >>> 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 如果试图获取不存在的属性,会抛出AttributeError的错误: >>> getattr(obj, ‘z‘) # 获取属性‘z‘ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: ‘MyObject‘ object has no attribute ‘z‘ 以传入一个default参数,如果属性不存在,就返回默认值: >>> getattr(obj, ‘z‘, 404) # 获取属性‘z‘,如果不存在,返回默认值404 404 也可以获得对象的方法: >>> hasattr(obj, ‘power‘) # 有属性‘power‘吗? True >>> getattr(obj, ‘power‘) # 获取属性‘power‘ <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>> >>> fn = getattr(obj, ‘power‘) # 获取属性‘power‘并赋值到变量fn >>> fn # fn指向obj.power <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>> >>> fn() # 调用fn()与调用obj.power()是一样的 81 小结 通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。如果可以直接写: sum = obj.x + obj.y 就不要写: sum = getattr(obj, ‘x‘) + getattr(obj, ‘y‘) 一个正确的用法的例子如下: def readImage(fp): if hasattr(fp, ‘read‘): return readData(fp) return None 假设我们希望从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()就派上了用场。 请注意,在Python这类动态语言中,根据鸭子类型,有read()方法,不代表该fp对象就是一个文件流,它也可能是网络流,也可能是内存中的一个字节流,但只要read()方法返回的是有效的图像数据,就不影响读取图像的功能。 10.实例属性和类属性 由于Python是动态语言,根据类创建的实例可以任意绑定属性。给实例绑定属性的方法是通过实例变量,或者通过self变量: class Student(object): def __init__(self, name): self.name = name s = Student(‘Bob‘) s.score = 90 但是,如果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 从上面的例子可以看出,在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性, 但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
时间: 2024-11-08 15:37:46