Python的面向对象编程
1、类的定义(class)
在Python中,定义类是通过class
关键字,类名通常是大写开头的单词。语法格式如下:
class ClassName: <statement-1> . . . <statement-N>
类实例化后,可以使用其属性,实际上,创建一个类之后,可以通过类名访问其属性。
1.1、构造方法(__init__(self))
类有一个名为 __init__() 的特殊方法(构造方法),该方法在类实例化时会自动调用,像下面这样:
def __init__(self): self.data = []
如下实例化类 MyClass,对应的 __init__() 方法就会被调用:
x = MyClass()
注意,__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。当然, __init__() 方法也可以有其他参数,参数通过 __init__() 传递到类的实例化操作上。在构造实例时,self参数
不需要传,Python解释器自己会把实例变量传进去。
示例:
#!/usr/bin/python3 class Complex: def __init__(self, realpart, imagpart): self.r = realpart self.i = imagpart
x = Complex(3.0, -4.5) print(x.r, x.i) # 输出结果:3.0 -4.5
类的方法与普通的函数只有一个特别的区别:它们必须有一个额外的第一个参数名称(我们习惯将它命名为 self,但不是必须叫这个名字),除此之外,类的方法和普通函数没有什么区别。self代表的是类的实例,而非类。
1.2、创建一个类实例
类对象支持两种操作:属性引用和实例化。属性引用使用和 Python 中所有的属性引用一样的标准语法:obj.name。
类对象创建后,类命名空间中所有的命名都是有效属性名。所以如果类定义是这样:
#!/usr/bin/python3 class MyClass: """一个简单的类实例""" i = 12345 def f(self): return 'hello world' # 实例化类 x = MyClass() # 访问类的属性和方法 print("MyClass 类的属性 i 为:", x.i) print("MyClass 类的方法 f 输出为:", x.f())
可以自由地给一个实例变量绑定属性,比如,给实例 x 随便绑定一个name
属性:
x.name = 'Bart Simpson' print(bart.name) #'Bart Simpson'
和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同。
1.3、实例属性和类属性
Python是动态语言,可以给类的实例绑定任意的属性。给实例绑定属性我们可以通过self
变量,或者直接给类实例绑定也行:
class Student(object): def __init__(self, name): self.name = name s = Student('Bob') s.score = 90 #直接绑定一个属性
在一个类中定义的属性,这种属性是类属性,归类本身所有:
class Student(object): name = 'Student' #类属性
类属性可以直接通过类来访问,也可以通过类实例来访问:
class Student(object): ... name = 'Student' ... s = Student() # 创建实例s print(s.name) #Student 打印出name属性,因为实例并没有name属性,所以会继续查找class的name属性 print(Student.name) #Student 打印类的name属性 s.name = 'Michael' # 给实例绑定name属性 print(s.name) #Michael 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性 print(Student.name) #Student 但是类属性并未消失,用Student.name仍然可以访问 del s.name # 如果删除实例的name属性 print(s.name) #Student 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
在给一个类实例绑定属性时,建议不要使用和类属性相同的名字。
实例属性属于各个实例所有,互不干扰。类属性属于类所有,所有实例共享一个属性。建议不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。
1.4、类的方法
在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self, 且为第一个参数,self 代表的是类的实例。
#!/usr/bin/python3 #类定义 class people: #定义基本属性 name = '' age = 0 #定义私有属性,私有属性在类外部无法直接进行访问 __weight = 0 #定义构造方法 def __init__(self,n,a,w): self.name = n self.age = a self.__weight = w def speak(self): print("%s 说: 我 %d 岁。" %(self.name,self.age)) # 实例化类 p = people('runoob',10,30) p.speak() #输出 runoob 说: 我 10 岁。
1.5、私有属性和私有方法(以__开头的属性和方法)
在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。如果外部直接通过实例来访问私有属性和方法的话,代码将直接报错。
私有属性:以两个下划线开头(比如:__private_attrs)的属性为类的私有属性,该属性不能在类的外部被使用或直接访问,在类内部的方法中使用 self.__private_attrs 进行访问。
私有方法:以两个下划线开头(比如:__private_method)的属性为类的私有方法,该方法不能在类的外部被使用或直接访问,在类内部的方法中使用 self.__private_method 进行访问。
私有属性代码示例:
#!/usr/bin/python3 class JustCounter: __secretCount = 0 # 私有变量 publicCount = 0 # 公开变量 def count(self): self.__secretCount += 1 self.publicCount += 1 print (self.__secretCount) counter = JustCounter() counter.count() print (counter.publicCount) print (counter.__secretCount) # 这里将报错,实例不能访问私有变量
私有方法代码示例:
#!/usr/bin/python3 class Site: def __init__(self, name, url): self.name = name # public self.__url = url # private def who(self): print('name : ', self.name) print('url : ', self.__url) def __foo(self): # 私有方法 print('这是私有方法') def foo(self): # 公共方法 print('这是公共方法') self.__foo() # 可以通过类内部的方法来访问私有方法 x = Site('菜鸟教程', 'www.runoob.com') x.who() # 正常输出 x.foo() # 正常输出 x.__foo() # 访问私有方法这里将报错
1.5.1、通过实例直接访问私有变量
一般来说,私有变量是不能直接访问的,比如 __name,这
是因为Python解释器对外把__name
变量改成了_Student__name。但是实际上我们
仍然可以通过_Student__name
来访问__name
变量:obj.__Student_name。
但是非常不建议这么做,因为不同版本的Python解释器可能会把__name
改成不同的变量名。
2、继承
Python 同样支持类的继承。基类必须与派生类定义在一个作用域内。
派生类的定义如下所示:则此时基类 BaseClassName 必须和类 DerivedClassName 定义在同一个作用域内。
class DerivedClassName(BaseClassName1): <statement-1> . . . <statement-N>
除了直接使用类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:
class DerivedClassName(modname.BaseClassName):
继承代码示例:
#!/usr/bin/python3 #类定义 class people: #定义基本属性 name = '' age = 0 #定义私有属性,私有属性在类外部无法直接进行访问 __weight = 0 #定义构造方法 def __init__(self,n,a,w): self.name = n self.age = a self.__weight = w def speak(self): print("%s 说: 我 %d 岁。" %(self.name,self.age)) #单继承示例 class student(people): grade = '' def __init__(self,n,a,w,g): #调用父类的构函 people.__init__(self,n,a,w) self.grade = g #覆写父类的方法 def speak(self): print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade)) s = student('ken',10,60,3) s.speak() #输出 ken 说: 我 10 岁了,我在读 3 年级
2.1、多继承
Python同样有限的支持多继承形式。继承了多个类的类定义形如下例:
class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N>
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
代码实例:
#!/usr/bin/python3 #类定义 class people: #定义基本属性 name = '' age = 0 #定义私有属性,私有属性在类外部无法直接进行访问 __weight = 0 #定义构造方法 def __init__(self,n,a,w): self.name = n self.age = a self.__weight = w def speak(self): print("%s 说: 我 %d 岁。" %(self.name,self.age)) #单继承示例 class student(people): grade = '' def __init__(self,n,a,w,g): #调用父类的构函 people.__init__(self,n,a,w) self.grade = g #覆写父类的方法 def speak(self): print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade)) #另一个类,多重继承之前的准备 class speaker(): topic = '' name = '' def __init__(self,n,t): self.name = n self.topic = t def speak(self): print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic)) #多重继承 class sample(speaker,student): a ='' def __init__(self,n,a,w,g,t): student.__init__(self,n,a,w,g) speaker.__init__(self,n,t) test = sample("Tim",25,80,4,"Python") test.speak() #方法名同,默认调用的是在括号中排前地父类的方法 输出:我叫 Tim,我是一个演说家,我演讲的主题是 Python
2.2、super() 函数
super() 函数是用于调用父类(超类)的一个方法。
class A: def add(self, x): y = x+1 print(y) class B(A): def add(self, x): super().add(x) b = B() b.add(2) # 3
3、判断对象类型的方法
3.1、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类型。我们可以直接比较两个变量的type类型是否相同:
type(123)==type(456) #True # 判断基本数据类型可以直接写int,str等 type(123)==int #True type('abc')==str #True #如果要判断一个对象是否是函数可以使用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
3.2、使用 isinstance(实例, 类)
对于class的继承关系来说,使用type()
就很不方便。我们要判断class的类型,可以使用isinstance()
函数:
#假设继承关系:object -> Animal -> Dog -> Husky a = Animal() d = Dog() h = Husky() isinstance(h, Dog) #true isinstance(h, Animal) #true isinstance(d, Dog) and isinstance(d, Animal) #true isinstance(d, Husky) #false isinstance('a', str) #True isinstance(123, int) #True isinstance(b'a', bytes) #True
使用 isinstance() 还可以判断一个变量是否是多个类型中的其中一种。比如下面的代码就可以判断是否是list或者tuple:
isinstance([1, 2, 3], (list, tuple)) #True isinstance((1, 2, 3), (list, tuple)) #True