Python是一个面向对象的解释型语言,所以当然也有类的概念 。 
在Python中 ,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。 
之前接触类的概念是在学习C++时,现在学习了python后 ,觉得两者还是有很大的区别的 。面向对象的思想是一样的 ,但是python做为更高级的语言,在类的定义与使用更加简便 。

很多人学习python,不知道从何学起。
很多人学习python ,掌握了基本语法过后,不知道在哪里寻找案例上手。
很多已经做案例的人,却不知道如何去学习更加高深的知识 。
那么针对这三类人 ,我给大家提供一个好的学习平台,免费领取视频教程,电子书籍 ,以及课程的源代码!??¤
QQ群:623406465


类的定义

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'

这点与静态语言,比如C++是不一样的。我们可以随时给一个对象添加属性。 
在python中 ,类的属性就等同于c++类的成员变量,类的方法等同于c++类的成员函数 。 
由于类可以起到模板的作用,因此 ,可以在创建实例的时候 ,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的init方法,在创建实例的时候,就把name ,score等属性绑上去:

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

对比c++,__init__函数就等同于c++类得构造函数,注意:特殊方法“init”前后有两个下划线。 
注意到init方法的第一个参数永远是self ,表示创建的实例本身,因此,在init方法内部 ,就可以把各种属性绑定到self,因为self就指向创建的实例本身 。

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

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

和普通的函数相比 ,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且 ,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别 ,所以,你仍然可以用默认参数 、可变参数、关键字参数和命名关键字参数。

我们可以给我们定义的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'

 

访问限制

在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了:

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

但是如果外部代码要获取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

需要注意的是,在Python中,变量名类似__xxx__的 ,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量 ,特殊变量是可以直接访问的 ,不是private变量 。 
有些时候,你会看到以一个下划线开头的实例变量名,比如_name ,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定 ,当你看到这样的变量时,意思就是,“虽然我可以被访问 ,但是,请把我视为私有变量,不要随意访问 ”。

类的私有成员一定不可以在外部访问吗?其实也不是。 
不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name ,所以,仍然可以通过_Student__name来访问__name变量:

>>> bart._Student__name 'Bart Simpson'

但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名 。 
总的来说就是 ,Python本身没有任何机制阻止你干坏事 ,一切全靠自觉。 
最后注意下面的这种错误写法:

>>> 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'

 

多继承

在Python中,类也是支持多继承的 。只需要在定义类时的括号里把继承的所有类名写入就可以。 
例如:

class Dog(Mammal, Runnable): pass

上面的例子定义了一个名为Dog的类,同时继承了Mammal和Runnable类。

我们再看一个例子 ,比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印:

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

当我们需要编写Dog和Cat类时 ,就可以直接从Animal类继承:

class Dog(Animal): pass class Cat(Animal): pass

我们再编写一个函数,这个函数接受一个Animal类型的变量:

def run_twice(animal): animal.run() animal.run()

我们传入Animal的实例时,run_twice()就打印出:

>>> run_twice(Animal()) Animal is running... Animal is running...

当我们传入Dog的实例时 ,同样也会打印出:

>>> run_twice(Dog()) Animal is running... Animal is running...

因为Dog类继承了Animal类,是Animal的子类,在执行run函数时 ,由于Dog类实例没有定义自己的run函数 ,执行的是Animal类的run函数 。 
但是如果我们传入一个跟Animal类没有任何关系的一个类实例时,会出现什么情况呢? 
我们定义一个Timer的类,该类也有一个名为run的方法。

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

传入run_twice函数:

run_twice(Timer()) Start... Start...

我们会发现该函数仍可以正常运行。

对于静态语言(例如C++)来说 ,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则 ,将无法调用run()方法 。 
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了。
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系 ,一个对象只要“看起来像鸭子,走起路来像鸭子 ”,那它就可以被看做是鸭子 。

 

获取对象类型

当我们拿到一个对象的引用时 ,如何知道这个对象是什么类型 、有哪些方法呢?

使用type

首先,我们来判断对象类型,使用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()

对于class的继承关系来说 ,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。 
如果继承关系是:object -> Animal -> Dog -> Husky 
那么,isinstance()就可以告诉我们 ,一个对象是否是某种类型 。先创建3种类型的对象:

>>> a = Animal() >>> d = Dog() >>> h = Husky()

然后,判断:

>>> isinstance(h, Husky) True

没有问题,因为h变量指向的就是Husky对象。 
再判断:

>>> isinstance(h, Dog) True

h虽然自身是Husky类型 ,但由于Husky是从Dog继承下来的,所以,h也还是Dog类型。换句话说 ,isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上 。

使用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', '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'

 

使用__slots__

正常情况下 ,当我们定义了一个class ,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:

class Student(object): pass

然后 ,尝试给实例绑定一个属性:

>>> s = Student() >>> s.name = 'Michael' # 动态给实例绑定一个属性 >>> print(s.name) Michael

但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性 。 
为了达到限制的目的 ,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

class Student(object): __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

然后 ,我们试试:

>>> s = Student() # 创建新的实例 >>> s.name = 'Michael' # 绑定属性'name' >>> s.age = 25 # 绑定属性'age' >>> s.score = 99 # 绑定属性'score' Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'score'

由于’score’没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。 
使用__slots__要注意 ,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。 
除非在子类中也定义__slots__,这样 ,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__ 。

 

使用@property

在绑定属性时 ,如果我们直接把属性暴露出去,虽然写起来很简单,但是 ,没办法检查参数,导致可以把成绩随便改:

s = Student() s.score = 9999

这显然不合逻辑。为了限制score的范围,可以通过一个set_score()方法来设置成绩 ,再通过一个get_score()来获取成绩,这样,在set_score()方法里 ,就可以检查参数:

class Student(object): def get_score(self): return self._score def set_score(self, value): if not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value

现在,对任意的Student实例进行操作,就不能随心所欲地设置score了:

>>> s = Student() >>> s.set_score(60) # ok! >>> s.get_score() 60 >>> s.set_score(9999) Traceback (most recent call last): ... ValueError: score must between 0 ~ 100!

但是 ,上面的调用方法又略显复杂,没有直接用属性这么直接简单。 
有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的Python程序员来说 ,这是必须要做到的! 
还记得装饰器(decorator)可以给函数动态加上功能吗?对于类的方法 ,装饰器一样起作用 。Python内置的@property装饰器就是负责把一个方法变成属性调用的:

class Student(object): @property def score(self): return self._score @score.setter def score(self, value): if not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value

把一个getter方法变成属性,只需要加上@property就可以了,此时 ,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是 ,我们就拥有一个可控的属性操作:

>>> s = Student() >>> s.score = 60 # OK,实际转化为s.set_score(60) >>> s.score # OK,实际转化为s.get_score() 60 >>> s.score = 9999 Traceback (most recent call last): ... ValueError: score must between 0 ~ 100!

注意到这个神奇的@property ,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的 。

还可以定义只读属性 ,只定义getter方法,不定义setter方法就是一个只读属性:

class Student(object): @property def birth(self): return self._birth @birth.setter def birth(self, value): self._birth = value @property def age(self): return 2015 - self._birth

上面的birth是可读写属性,而age就是一个只读属性 ,因为age可以根据birth和当前时间计算出来。

@property广泛应用在类的定义中 ,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样 ,程序运行时就减少了出错的可能性。

文章来源于网络,如有侵权请联系站长QQ61910465删除
本文版权归趣快排SEO www.SeogurUblog.com 所有,如有转发请注明来出,竞价开户托管,seo优化请联系QQ▶61910465