一、Python3的面向对象
高级编程语言都有面向对象的概念,简单来说就是类的抽象。Python中的类与面向对象与JAVA语言的语言略有不同。类是面向对象的基础也是最重要的部分。下面简单的介绍下什么是类:
类:用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
Python中的所有东西都是类(都是对象,对象是类的实例),即使是int,str,他们的定义也都是类(参考前面的文章,基础数据类型)。
下面介绍类的构成:
>属性:类里面的特性 不加()
>方法:类里面的函数 要加()
我们从另一个角度去理解类:类也是一种数据类型,和int,char,float等基本数据类型一样,只不过它是一种复杂的数据类型。因为类的本质是数据类型,而不是具体的数据,所以无法直接存储于内存中,需要实例化后才会进入内存,并可以操作。
二、类的定义
Python3中定义类的语法很简单,类似于函数的定义,语法如下:
class ClassName(object): Statement
1.class定义类的关键字 2.ClassName类名,类名的每个单词的首字母大写(驼峰规则)。 3.object是父类名,object是一切类的基类。在python3中如果继承类是基类可以省略不写。
例子1:定义1个动物类,包含属性和方法
class Animal(): 'this is a class' eye=2 def __init__(self,name,food,color="yellow",leg=2): self.food = food self.name = name self.__color = color self.__leg = leg def play(self): print('%s:lalala' % (self.name + self.food)) def get_name(self): self.play()
>定义类时,这种方法可以使类对象实例按某种特定的模式生产出来。
>后面的参数中第一个参数我们约定俗成的为self参数名,self代表的是在类实例化后这个实例对象本身。
>__init__初始化函数除了有self这个参数表示实例对象本身之外,其他的参数的定义也遵循函数的必备参数和默认参数一样的原则,
>必备参数就是在实例化是一定要传入的参数,
>默认参数就是在定义时可以给这个参数一个初始值。
三、类的实例化
类的实例化很简单,就是按照类的初始化函数(__init__())定义一个具体的类对象。
例子1:
class Animal(): 'this is a class' eye=2 def __init__(self,name,food,color="yellow",leg=2): self.food = food self.name = name self.__color = color self.__leg = leg def play(self): print('%s:lalala' % (self.name + self.food)) def get_name(self): self.play() minions = Animal('minions','banana') #minions 小黄人 实例化一个类对象 minions.play()
输出:
minionsbanana:lalala
例子2:
很多类都倾向于将对象创建为有初始状态的。因此类可能会定义一个名为 __init__() 的特殊方法(构造方法),像下面这样:
def __init__(self):
self.data = []
类定义了 __init__() 方法的话,类的实例化操作会自动调用 __init__() 方法。所以在下例中,可以这样创建一个新的实例:
x = MyClass()
当然, __init__() 方法可以有参数,参数通过 __init__() 传递到类的实例化操作上。例如:
>>> 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)
四、属性-类属性
我们知道:类=属性+方法。但是属性还可以继续划分:属性=类属性+实例属性。我们先来介绍类属性,类属性简单来说就是类里面直接定义的属性,不是在方法里面的属性。
类属性有如下特点:
>.类属性是可以直接通过“类名.属性名”来访问和修改。(Animal.eye=2,dog.eye=2)
>.类属性是这个类的所有实例对象所共有的属性,
>任意一个实例对象都可以访问并修改这个属性(私有隐藏除外)。
>对类属性的修改,遵循基本数据类型的特性:列表可以直接修改,字符串不可以,
>所以当类属性是一个列表时,可以通过任意一个实例对象对其进行修改。
>但字符串类型的类属性不能通过实例对象对其进行修改。
>当实例对不可变对象进行修改之后,会查找实例的类属性,不会查找类的属性,同时类的属性不会变。
>.实例的类属性都是指向类的,但是如果实例改变类属性,就会建一个对象,不会改变类的属性
>.类本身属性变量啦,实例是指向类的话,也会改变,实例如果指向自己,就不会受影响
仍然以之前的Animal类为例:
class Animal(): 'this is a class' eye=2 ## 类属性 def __init__(self,name,food,color="yellow",leg=2): self.food = food self.name = name self.__color = color self.__leg = leg def play(self): print('%s:lalala' % (self.name + self.food)) def get_name(self): self.play()
在这个类的定义里面可以看到,直接定义了一个 eye=2的变量,这个变量就是类的属性,并且是类属性。意思就是说这个属性,属于这个类,不仅仅属于具体的实例,类和实例可以改变类属性。
class Animal(): 'this is a class' eye=2 def __init__(self,name,food,color="yellow",leg=2): self.food = food self.name = name self._color = color self.__leg = leg def play(self): print('%s:lalala' % (self.name + self.food)) def get_name(self): self.play() minions = Animal('minions','banana') #minions 小黄人 dog = Animal("wangwang",'gutou') print(minions.eye) print(dog.eye) print(Animal.eye) 输出: 2 2 2
》多个实例可以访问该类的类属性
》实例可以修改类属性
class Animal(): 'this is a class' eye=2 def __init__(self,name,food,color="yellow",leg=2): self.food = food self.name = name self._color = color self.__leg = leg def play(self): print('%s:lalala' % (self.name + self.food)) def get_name(self): self.play() minions = Animal('minions','banana') #minions 小黄人 dog = Animal("wangwang",'gutou') minions.eye=3 ##实例中改变类属性 dog.eye=4 ##实例中改变类属性 print(minions.eye) print(dog.eye) print(Animal.eye) 输出: 3 4 2
#实例虽然可以改变类属性,但是仅仅只改变当前实例的类属性,并不会改变类的属性,所以Animai.eye还是=2.
#可以通过类.类属性的方法修改类属性,例如:Animal.eye=5,那么print(Animal.eye)=5
》如果通过类修改类属性,那么实例的类属性都会改变
class Animal(): 'this is a class' eye=2 def __init__(self,name,food,color="yellow",leg=2): self.food = food self.name = name self._color = color self.__leg = leg def play(self): print('%s:lalala' % (self.name + self.food)) def get_name(self): self.play() minions = Animal('minions','banana') #minions 小黄人 dog = Animal("wangwang",'gutou') Animal.eye=5 print(minions.eye) print(dog.eye) print(Animal.eye) 输出: 5 5 5
五、属性-实例属性
在类的定义中,如果变量前面是self.attribute。那么这种属性就是实例属性,类无法直接访问实例属性。下面的例子中的红色部分的属性就是实例属性。
class Animal(): 'this is a class' eye=2 def __init__(self,name,food,color="yellow",leg=2): self.food = food self.name = name self.__color = color self.__leg = leg def play(self): print('%s:lalala' % (self.name + self.food)) def get_name(self): self.play()
实例1:访问实例属性:实例.属性名
class Animal(): 'this is a class' eye=2 def __init__(self,name,food,color="yellow",leg=2): self.food = food self.name = name self._color = color self.__leg = leg def play(self): print('%s:lalala' % (self.name + self.food)) def get_name(self): self.play() minions = Animal('minions','banana') #minions 小黄人 dog = Animal("wangwang",'gutou') print(minions.name) print(dog.food)
print(Animal.name)
输出:
minios
gutou
Traceback (most recent call last):
File "D:/WorkSpace/Python3/cekai/class1.py", line 22, in <module>
print(Animal.name)
AttributeError: type object 'Animal' has no attribute 'name'
可以看出来,实例属性只能通过实例.属性名的方式访问,但是类无法访问实例属性('Animal' has no attribute 'name')
六、类的私有属性,方法
java的变量权限有public,protected,private之分,python中对类属性(变量)的权限划分只有普通的类属性和私有属性。但是私有属性不是通过关键字来定义的,而是通过特殊的变量名来确定。
>在Python中,通过单下划线”_”来实现模块级别的私有化,一般约定以单下划线”_”开头的变量、函数为模块私有的,
>也就是说”from moduleName import *”将不会引入以单下划线”_”开头的变量、函数
>对于Python中的类属性,可以通过双下划线”__”来实现一定程度的私有化,因为双下划线开头的属性在运行时会被”混淆”(mangling)。
实例:类的私有属性,方法的定义和获取
class Animal():
'this is a class'
eye=2
def __init__(self,name,food,color="yellow",leg=2):
self.food = food
self.name = name
self._color = color ##单下划线类私有属性:self._attr
self.__leg = leg ##双下划线类私有属性:self.__attr
def _play(self):##单下划线类私有方法:self._fun()
print('%s:lalala' % (self.name + self.food))
def __get_name(self): ##双下划线类私有方法:self.__fun()
self._play() ##单下划线私有方法在类内部直接 self._fun()
def run(self):
self.__get_name() ##双下划线私有方法在类内部直接 self.__fun()
minions = Animal('minions','banana') #minions 小黄人
dog = Animal("wangwang",'gutou')
print(dog._color) ##访问单下划线私有属性:实例._attr
print(dog._Animal__leg) ##访问双下划线私有属性:实例._Class__attr
#print(dog.__leg) ##无法通过 ‘实例.__attr’直接访问双下划线私有属性
dog.run()
dog._play() ##访问单下划线私有方法:实例._fun()
dog._Animal__get_name()##访问单下划线私有方法:实例._Class__fun()
#dog.__get_name() ##无法通过 ‘实例.__fun()’直接访问双下划线私有方法
输出:
yellow
2
wangwanggutou:lalala
wangwanggutou:lalala
wangwanggutou:lalala
总结如下:
定义 | 访问 | |
私有属性(_attr):单下划线 | self._attr | 实例._attr |
私有属性(_ _attr):双下划线 | self._ _attr | 实例._Class_ _attr |
私有方法(_fun()):单下划线 | def _fun(self): | 实例._fun() |
私有方法(_ _fun()):双下划线 | def _ _fun(self): | 实例._Class_ _fun() |
说明:
>_”和” _ _”的使用 更多的是一种规范/约定,不没有真正达到限制的目的:
>_”:以单下划线开头只能允许其本身与子类进行访问
> _ _”:双下划线的表示的是私有类型的变量。这类属性在运行时属性名会加上单下划线和类名。
七、类的继承与多态
上面介绍了类的定义和类属性,方法。面向对象的3大特征:封装,继承,多态。其中封装就是体现在了类的属性,方法和私有属性,方法的定义与访问上。下面我们介绍一下面向对象的其他特性:继承和多态
1.继承
Python3在定义类时,可以从已有的类继承,被继承的类称为基类(父类),新定义的类称为派生类(子类)。object类是所有类的基类,如果定义类没有指定父类,那么默认就是object类。
继承具有如下特性:
特性1:如果子类中没有定义初始化函数,那么将继承父类的初始化函数,子类在实例化的时候需要按照父类的初始化函数定义进行传参数。
class Animal: #父类 eye = 2 leg = [] """ animal class""" def __init__(self,name,food,color = 'yellow'): self.name = name self.food = food def play(self): print('Animals are strong') class People(Animal): pass minions = Animal('minions','banana') dog = Animal('dog','bone') people = People() # 输出: Traceback (most recent call last): File "D:/WorkSpace/Python3/cekai/class2.py", line 20, in <module> people = People() TypeError: __init__() missing 2 required positional arguments: 'name' and 'food'
People类没有定义__init__初始化函数,但是父类Animal类定义了初始化函数。那么People类在初始化的时候,会向上搜索Animal的初始化函数,如果找到单独定义的初始化函数,那么People类在定义的时候必须使用父类的初始化函数进行初始化。
所以上面的People类实例化的方法如下:people = People('man','food') #正确的实例化
特性2:如果子类中定义了自己的初始化函数,那么子类在实例化的时候按照自己的实例化函数进行传参。
class Animal: #父类 eye = 2 leg = [] """ animal class""" def __init__(self,name,food,color = 'yellow'): self.name = name self.food = food def play(self): print('Animals are strong') class People(Animal): def __init__(self, weight): self.weight = weight people = People(60) ##子类定义了__init__方法,子类实例化的时候必须使用自己的初始化函数实例化
特性3--重要:子类自己定义了初始化函数,同时也想访问父类的初始化函数,可以通过如下几种方式进行父类的初始化。
class Animal: #父类 eye = 2 leg = [] """ animal class""" def __init__(self,name,food,color = 'yellow'): self.name = name self.food = food def play(self): print('Animals are strong') class People(Animal): def __init__(self, weight): super().__init__("FatherName","FatherFood") ##方式1:推荐。可以屏蔽父类的细节,即使父类改变,代码无需改变。 Animal.__init__(self,"FatherName","FatherFood") super(People, self).__init__("FatherName","FatherFood") self.weight = weight minions = Animal('minions','banana') dog = Animal('dog','bone') people = People(60)
类继承的特性:
.在类中找不到调用的属性时就搜索基类,如果基类是从别的类派生而来,这个规则会递归的应用上去。反过来不行。
.如果派生类中的属性与基类属性重名,那么派生类的属性会覆盖掉基类的属性。包括初始化函数。
.派生类在初始化函数中需要继承和修改初始化过程,使用’类名+__init__(arg)’来实现继承和私有特性,也可以使用super()函数。
.issubclass(类名1,类名2): 判断类1是否继承了类2
.作用:面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。 继承完全可以理解成类之间的类型和子类型关系。子类在重写父类方法之后,如果要继承父类方法中的功能,要先调用父类的方法 class.fun(self)
2.多态
如果子类中重写了父类的方法,同一个函数有了不同的用法,就是多态。
class Animal: #父类 eye = 2 leg = [] """ animal class""" def __init__(self,name,food,color = 'yellow'): self.name = name self.food = food def play(self): print('Animals are strong') class People(Animal): def __init__(self, weight): super().__init__("FatherName","FatherFood") ##方式1:推荐。可以屏蔽父类的细节,即使父类改变,代码无需改变。 Animal.__init__(self,"FatherName","FatherFood") super(People, self).__init__("FatherName","FatherFood") self.weight = weight def play(self): print("This is People Play") minions = Animal('minions','banana') dog = Animal('dog','bone') people = People(60) people.play()
可以看出子类重写父类的方法后在,子类的方法按照自己的定义执行。
八、类的多继承
Python和Java的一个很大的区别就是Python支持多继承,而Java是单继承。
例子1:类的多继承--当继承的多个类有同种方法的时候,只会继承前面一个的方法-
class Animal: #父类
eye = 2
leg = []
""" animal class"""
def __init__(self,name,food,color = 'yellow'):
self.name = name
self.food = food
def play(self):
print('Animals are strong')
class People(Animal):
def __init__(self,weight):
super().__init__(self,'xiaoming','apple')
self.weight = weight
def play(self):
print('People is playing')
class Asian(Animal):
def play(self):
print('Asian is playing')
class Chinese(People,Asian):
def play(self):
super().play()
xmb = Chinese (89)
xmb.play()
输出:People is saying
多继承的特性:
特性1:当继承的类有同种方法的时候,只会继承前面一个的方法---实例1
特性2:多继承时,默认的初始化函数是继承的第一个父类,如果第一个父类没有定义自己的初始化函数,那么将按照第二个父类的初始化函数。如果两个父类都没初始化函数,会调用父类的父类的初始化函数。
特性3:super 会对继承树做排序,保证每个父类只会被调用一次,在多继承里面:子类永远在父类前面,如果有多个父类,会根据它们在列表中的顺序被检查,前面的父类的方法优先于后面父类的方法,super保证所有的类只被调用一次,不会被重复调用
下面这个例子要注意:
class Animal: #父类 eye = 2 leg = [] """ animal class""" def __init__(self,name,food,color = 'yellow'): self.name = name self.food = food def play(self): print('Animals are strong') class People(Animal): def __init__(self,weight): super().__init__(self,'xiaoming','apple') self.weight = weight def play(self): super(People,self).__init__('xiaoming','apple') super(People ,self).play() print('People is playing') class Asian(Animal): def play(self): super().play() print('Asian is playing') class Chinese(People,Asian): def play(self): super().play() print('Chinese is No.1') xmb = Chinese (89) xmb.play() 输出: Animals are strong Asian is playing People is playing Chinese is No.1
Animal--People--Chinese
Animal--Asian---Chinese
对于Chinese.play(),由于Chinese继承了People,Asian,并且都继承了Animal.并且People和Asian的play方法中都调用了super.play()。按理说如果层级输出的时候Animal.play方法应该被对多次输出,但是super做了特性3处理,只输出一次。因此在Chinses的super.play()会首先输出所有父类的方法,并且父类中的super方法不会被重复输出。