(一) 面向对象编程概念
1.1 面向对象编程概括:
- 面向对象编程是一种程序设计思想
- 面向对象把类和对象作为程序的基本单元
- 对象包含属性和方法
- 面向过程编程为:函数的调用集合
- 面向对象编程为:对象之间传递信息的集合
- 处处皆对象
1.2 名字重整:
Test类定义 一个私有属性 __name 实例化一个对象 a ,无法调用该属性,打印 a.dict(可以检查一个对象的所有属性)查看 ,发现__name存在并且名字变为 _Test__name (无法调用的原因,名字被改变)
改变规则:私有属性前添加类名,再在类名前添加一个下划线 (名字重整)
1.3 魔法属性、方法:
.__doc__
:表示类的描述信息,help()方法也可以输出类的描述信息
class Test():
"""
This is a description of a class
"""
pass
print(Test.__doc__)
print("*" * 15)
print(help(Test))
.__module__
:表示当前操作在哪个模块.__class__
:表示当前操作的对象的类是什么
# -*- coding:utf-8 -*-
# test.py
class Person(object):
def __init__(self):
self.name = 'laowang'
from test import Person
# main.py
obj = Person()
print(obj.__module__) # 输出创建该对象的对应模块 test
print(obj.__class__) # 输出创建该对象的对应模块中的类 test.Person
__init__
:构造函数,初始化方法,通过类创建对象时,自动触发执行__del__
:析构函数,当对象在内存中被释放时,自动触发执行
注:此方法无需定义,因为python是一门高级语言,程序员在使用时,无需关系内存的分配和释放,因为此工作都是交给Python的解释器来执行,所以,__del__的调用是有解释器在进行垃圾回收时自动触发执行的。
class Foo:
def __del__(self):
pass
__call__
:对象后面加括号,触发执行 ,即函数调用(用于装饰器)
注: __ init__方法的执行是由创建对象触发的,即 对象 = 类名() ; 而对于__call__ 方法执行是由对象后加括号触发的,即对象() 或 类()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
.__dict__
:类或对象中的所有属性
类名.dict:获取类属性,方法
属性名.dict:获取对象的属性,方法__str__
:如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值
注:如果没有覆写该函数,则默认输出一个对象名称和内存地址。
class Foo:
def __str__(self):
return 'laowang'
obj = Foo()
print(obj)
# 输出:laowang
__getitem__
:索引操作,获取,按照索引获取值:当访问不存在的属性时会调用该方法__setitem__
:设置 ,按照索引赋值:每当属性被赋值的时候都会调用该方法:self.dict[name] = value
注:每当属性被赋值的时候都会调用__setitem__
方法,因此不能再在该方法内赋值 self.name = value ,会死循环__delitem__
:删除
# -*- coding:utf-8 -*-
class Foo(object):
def __getitem__(self, key):
print('__getitem__', key)
def __setitem__(self, key, value):
print('__setitem__', key, value)
def __delitem__(self, key):
print('__delitem__', key)
obj = Foo()
result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = 'laowang' # 自动触发执行 __setitem__
del obj['k1'] # 自动触发执行 __delitem__
__getslice__
:切片操作,获取__setslice__
:设置__delslice__
:删除
# -*- coding:utf-8 -*-
class Foo(object):
def __getslice__(self, i, j):
print('__getslice__', i, j)
def __setslice__(self, i, j, sequence):
print('__setslice__', i, j)
def __delslice__(self, i, j):
print('__delslice__', i, j)
obj = Foo()
obj[-1:1] # 自动触发执行 __getslice__
obj[0:1] = [11,22,33,44] # 自动触发执行 __setslice__
del obj[0:2] # 自动触发执行 __delslice__
__len__
:获取长度__cmp__
:比较运算__add__
:加运算__sub__
:减运算__mul__
:乘运算__div__
:除运算__mod__
:求余运算__pow__
:乘方运算
(二) 三大特点:
2.1 继承
2.1.1 基本概念
1、继承:**
继承就是让类和类之间产生父子关系,子类可以拥有父类的静态属性和方法。
[继承就是可以获取另外一个类中的静态属性和普通方法。(并非所有成员)]
2、 子类,父类的概念:
父类:用于被继承的类,称之为父类,也叫做基类,或者超类。
子类:继承其他类的类,称之为子类,也叫做派生类。
3、 继承的作用:
提高代码的重用率
4、 继承的特点:
- 在继承中,基类的构造方法(init()方法)不会自动调用,需要在子类的构造方法中专门调用。
- 在调用基类的方法时需要加上基类的类名前缀,并带上 self 参数变量。区别于在类中调用普通函数时不需要带 self 参数。
- 在 Python 中,首先查找对应类型的方法,如果在子类中找不到对应的方法,才到基类中逐个查找。
子类获得了父类全部非私有的功能。
子类不能继承父类中的私有方法,也不能被调用父类的私有方法。
对于父类中扩展的非私有方法,子类可以拿来即用。
>>> class Animal(object):
... def run(self):
... print('animal is running ...')
... def __run(self):
... print(' I am a private method.')
>>> class Dog(Animal):
... def eat(self):
... print(' dog is eating ')
>>> dog = Dog()
>>> dog.run()
animal is running ...
>>> dog.eat()
dog is eating
>>> dog.__run()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Dog' object has no attribute '__run'
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))
zth = student('zth',10,40,5)
zth.speak()
2.1.2 基本方法
2.1.2.1 继承的定义:
class DerivedClassName派生类(BaseClassName1基类名):
<statement-1>
.
.
<statement-N>
class DerivedClassName(modname.BaseClassName):
2.1.2.2 查看继承的父类 __ bases__
新式类: 如果一个类没有继承任何类,默认继承object类。所有类都是从object类中继承下来的。
查看方法:类名.__bases__
2.1.2.3 方法的复写
子类中定义了和父类中相同的方法,我们叫做方法的复写(派生方法)。实例对象调用此方法 的时候就会调用自己类中的方法了。
2.1.2.4 super()方法
- 子类和父类有相同的方法,如果子类想调用父类相同的的方法,可以使用super()方法。
- super默认省略了两个参数 :第一个参数是类名,第二个参数是self。两个参数可以省略不传递。例如 super(Student,self)
- super()还可以从类的外部使用 ,需要传递类名(本类的名称)和对象名。super(Student,student)
调用父类方法的格式:父类类名.方法名称(self) 或者 super().方法名称()或者super(本类类名,对象名)
2.1.2.5 构造函数的继承
子类的构造方法的写法:
- 经典写法:父类名.__ init __(self,参数1,参数2)
- 新式写法:super(子类,self). __init __(参数1,参数2)
先继承在重构——才能获取父类的属性
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
self.weight = 'weight'
def talk(self):
print("person is talking....")
class Chinese(Person):
def __init__(self, name, age, language): # 先继承,在重构
Person.__init__(self, name, age) #继承父类的构造方法,也可以写成:super(Chinese,self).__init__(name,age)
#self相当于实例
self.language = language # 定义类的本身属性
def walk(self):
print('is walking...')
class American(Person):
pass
c = Chinese('bigberg', 22, 'Chinese')
子类构造函数基于父类构造函数过程如下:
实例化对象c ----> c 调用子类__init__() ---- > 子类__init__()继承父类__init__() ----- > 调用父类 init()
2.1.2.6 继承中的__ init __方法
- 如果子类没有定义自己的初始化函数,父类的初始化函数会被默认调用。如果要实例化子类的对象,则只能传入父类的初始化函数对应的参数,否则会出错。
#定义父类:Parent
class Parent(object):
def __init__(self, name):
self.name = name
print("create an instance of:", self.__class__.__name__)
print("name attribute is:", self.name)
#定义子类Child ,继承父类Parent
class Child(Parent):
pass
#子类实例化时,由于子类没有初始化,此时父类的初始化函数就会默认被调用
#且必须传入父类的参数name,如果不传入则会报错
c = Child("init Child")
#实例化父类Parent的结果
create an instance of: Parent
name attribute is: tom
#实例化子类Child的结果
call __init__ from Child class
#super首先会先使得父类初始化的参数进行实例化
create an instance of: Child
name attribute is: data from Child
data from Child
- 如果子类定义了自己的初始化函数,而在子类中没有显示调用父类的初始化函数,则父类的属性不会被初始化。
- 如果子类定义了自己的初始化函数,在子类中显示调用父类,子类和父类的属性都会被初始化。
2.1.2.7 派生属性
子类自己定义的属性,子类所独有的,不会影响到父类。当子类定义的属性与父类同名时,当调用新增属性时,调用的是子类的——属性覆盖。
2.1.3 多继承
概念: 一个子类可以继承多个父类,并拥有所有父类的属性和方法。
书写格式: class 子类名(父类名1,父类名2……)
- 需要注意圆括号中父类的顺序,若是几个父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
- 注意子类如果没有构造方法时,按括号内父类的继承顺序去继承父类构造方法,只继承一个。
- 子类从多个父类派生,而子类又没有自己的构造函数时,
(1)按顺序继承,哪个父类在最前面且它又有自己的构造函数,就继承它的构造函数;
(2)如果最前面第一个父类没有构造函数,则继承第2个的构造函数,第2个没有的话,再往后找,以此类推。
#1.多继承子类对父类构造方法的调用
class Human:
def __init__(self,sex):
self.sex = sex
def p(self):
print("这是Human的方法")
def str1(self):
print("this si"+str(self.sex))
class Person:
def __init__(self,name):
self.name = name
def p(self):
print("这是Person的方法")
def person(self):
print("这是我person特有的方法")
def str2(self):
print( "this is:" + str(self.name))
class Student(Human,Person): #注意子类如果没有构造方法时,按括号内父类的继承顺序去继承父类构造方法,只继承一个
def prin(self):
print("student")
#------创建对象 -------------
#stu1=Studnent("男","tom")报错。
stu = Student("sex") #这里继承的是Huma的构造方法。
stu.p()
stu.str1()
#stu.str2()报错,因为即使human和person都是一个参数的构造方法,但是这里继承调用的是第一个Human的构造方法
====================================================================================
这是Human的方法
this sisex
多继承的super方法:
1、直接使用父类名.方法名形式:
class Parent(object):
def __init__(self, name):
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age):
print('Son1的init开始被调用')
self.age = age
Parent.__init__(self, name) #直接使用父类名.方法名的方式调用父类的__init__方法
print('Son1的init结束被调用')
class Son2(Parent):
def __init__(self, name, gender):
print('Son2的init开始被调用')
self.gender = gender
Parent.__init__(self, name) #
print('Son2的init结束被调用')
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
Son1.__init__(self, name, age) # 单独调用父类的初始化方法
Son2.__init__(self, name, gender)
print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
'''执行结果如下:
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
'''
parent被执行了两次
2、使用super调用父类中的方法:
class Parent(object):
def __init__(self, name, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init开始被调用')
self.age = age
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init结束被调用')
class Son2(Parent):
def __init__(self, name, gender, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init开始被调用')
self.gender = gender
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init结束被调用')
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
# 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
# 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
# super(Grandson, self).__init__(name, age, gender) 效果和下面的一样
super().__init__(name, age, gender)
print('Grandson的init结束被调用')
print(Grandson.__mro__) #搜索顺序
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
'''结果如下:
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
Grandson的init开始被调用
Son1的init开始被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
'''
parent被执行了一次
super调用过程:上面gs初始化时,先执行grandson中init方法, 其中的init有super调用,每执行到一次super时,都会从__mro__方法元组中顺序查找搜索。所以先调用son1的init方法,在son1中又有super调用,这个时候就就根据__mro__表去调用son2的init,然后在son2中又有super调用,这个就根据mro表又去调用parent中的init,直到调用object中的init. 所以上面的打印结果如此,要仔细分析执行过程。
super().__init__相对于类名.init,在单继承上用法基本无差。但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次
多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错。
单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
class Parent(object):
def __init__(self, name):
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age):
print('Son1的init开始被调用')
self.age = age
super().__init__(name) # 单继承不能提供全部参数
print('Son1的init结束被调用')
class Grandson(Son1):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
super().__init__(name, age) # 单继承不能提供全部参数
print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
2.2 封装 (Encapsulation)
2.2.1 概念
在程序设计中,封装 Encapsulation是将类中的某些部分(某些属性或者方法)隐藏起来,对象不能直接使用隐藏起来的属性或者方法,具有保护功能。
目的:隐藏对象的属性和方法实现细节,仅对外提供公共访问方式,从而保护隐私。
本质:属性的私有化
2.2.2 封装格式
__属性或者方法名称。(我们也叫做私有属性或者方法
2.2.3 私有
1、私有属性:
- 格式:__属性名=值
- 特点:在类的外部不能使用(对象不能调用私有属性)
2、私有方法: - 格式:__方法名()
- 特点:在类的外部不能使用(对象不能调用私有方法)
3、私有函数:
只能在当前类中被直接调用
#私有函数
class Site():
def __init__(self,name):
self.name = name
def func(self):
print("这是一个普通【公共】函数")
#在公共函数中将私有函数调用,调用格式:self.__私有的函数名称(参数列表)
self.__func1()
def __func1(self):
print("这是一个私有函数")
s = Site("临潼")
s.func()
2.2.4 对私有属性的操作
1、get、set函数:
-
get、set函数不是内置函数,而是自定义的
-
命名:getXxx() 和 setXxx()
-
get函数:获取值——对象名.属性名
-
set函数:赋值——对象名.属性名=值
-
语法:
def getXxx(self): return 私有化属性 def setXxx(self,私有化属性的名称): 私有化属性 = 私有化属性的名称
#1.通过自定义get,set方法,调用方法的形式去对私有属性进行操作
class Person(object):
def __init__(self, name, age):
self.name = name
self.__age = age
#定义对私有属性的get方法,获取私有属性
def getAge(self):
return self.__age
#定义对私有属性的重新赋值的set方法,重置私有属性
def setAge(self,age):
self.__age = age
person1 = Person("tom",19)
person1.setAge(20)
print(person1.name,person1.getAge()) #tom 20
2、@Property装饰器:
-
Python的内置装饰器@property的作用:将一个函数转换为属性进行使用【简化get函数和set函数】
-
使用:@property装饰器的作用相当于get函数,同时,会自动生成一个新的装饰器 @属性名.setter相当于set函数
-
@property本质:将函数在转换为属性使用
-
获取值:print(P.age)
#相当于调用了get函数【@property修饰的函数】,将被 私有化的属性的值返回。
-
设置:P.age = 12 print(P.age)
#相当于调用了set函数【@age.setter修饰的函数】,将 12作为参数传值。
常用方法如下:
class Test(object):
def __init__(self,name):
#私有的属性
self.__name=name
@property
def name(self): #get
return self.__name
@name.setter
def name(self,name): #set
self.__name=name
@name.deleter #注意这个deleter修饰器生效的前提是在该方法体内增加了del删除属性的操作,属性才会真的被删除。
def name(self):
del self.name #这个才是实际删除属性的操作。
test=Test('11')
test.name='22'
print(test.name)
提示:
1、.@property成为属性函数,可以对属性赋值时做必要的检查,比如在setter方法里加过滤判断条件。此外相比自定义的get和set方法,property保证了代码的短小精悍,对私有属性的操作代码格式化模块化。
2、@property只有一个参数self,且要有返回值,所以一般定义属性的get方法。
3、注意@property调用方式,没有括号,而是直接调用属性名即可。
3、property属性封装:
#2.通过调用property属性对set,get方法进行了封装。然后操作私有属性
class Student(object):
def __init__(self, name, age):
self.name = name
self.__age = age
#定义对私有属性的get方法,获取私有属性
def getAge(self):
return self.__age
#定义对私有属性的重新赋值的set方法,重置私有属性
def setAge(self,age):
self.__age = age
p = property(getAge,setAge) #注意里面getAge,setAge不能带()
s1 = Student("jack",22)
s1.p = 23 #如果使用=,则会判断为赋值,调用setAge方法。
print(s1.name,s1.p) #jack 23 ,直接使用s1.p会自动判断会取值,调用getAge
print(s1.name,s1.getAge()) #jack 23,这个时候set,get方法可以单独使用。
2.3 多态
2.3.1 多态的概念:
定义时的类型和运行时的类型不一样,此时就成为多态 ,多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。
python的多态,就是弱化类型,重点在于对象参数是否有指定的属性和方法,如果有就认定合适,而不关心对象的类型是否正确。
python是有限的的支持多态性,主要是因为python中变量的使用不用声明,所以不存在父类引用指向子类对象的多态体现。
2.3.2 鸭子类型 (duck typing)
鸭子类型的概念: 动态类型的一种风格,不是由继承特定的类或实现特定的接口,而是当前的方法和属性的集合决定,鸭子类型中关注的不是对象的类型本身,而是它如何使用。
概念来源: James Whitcomb Riley提出的鸭子测试:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
鸭子类型的特点:
1、变量绑定的类型具有不确定性
2、函数可以接收任意类型的参数
3、调用方法时不检查提供的参数类型
4、调用是否成功由参数的方法和属性确定
5、调用不成功抛出错误(也就是对象中没有实现该功能的方法)
代码演示如下:
#coding=utf-8
class Duck:
def quack(self):
print "Quaaaaaack!"
class Bird:
def quack(self):
print "bird imitate duck."
class Doge:
def quack(self):
print "doge imitate duck."
def in_the_forest(duck):
duck.quack()
duck = Duck()
bird = Bird()
doge = Doge()
for x in [duck, bird, doge]:
in_the_forest(x)
class duck(): #duck类型
def walk(self):
print('i am a duck,and i can walk')
def swim(self):
print('i am a duck,and i can swim')
class cat(): #cat类型
def walk(self):
print('i am a cat,and i can walk')
def swim(self):
print('i am a cat,and i can swim')
def walk_swim(animal):
animal.walk()
animal.swim()
>>> d=duck()
>>> c=cat()
>>> walk_swim(d)
i am a duck,and i can walk
i am a duck,and i can swim
>>> walk_swim(c)
i am a cat,and i can walk
i am a cat,and i can swim
函数walk_swim的参数animal是任意类型,它可以接收任意类型的参
数,当传入duck的对象d的时候,它会直接调用对象里面的walk和swim方
法,如果对象里面没有该功能的方法,则会报错。
2.3.3 多态的作用:
让具有不同功能的函数可以使用同样的函数名,这样就可以用同一函数名,调用具有不同功能的函数。
好处:增加了程序的灵活性和可扩展性。
2.3.4 多态的特点:
1、只关心对象的实例方法是否同名,不关心对象所属的类型;
2、对象所属的类之间,继承关系可有可无;
3、多态的好处可以增加代码的外部调用灵活度,让代码更加通用,兼容性比较强;
4、多态是调用方法的技巧,不会影响到类的内部设计。
2.3.5 多态的应用场景:
1、对象所属的类之间没有所属关系:
class Duck(object): # 鸭子类
def fly(self):
print("鸭子沿着地面飞起来了")
class Swan(object): # 天鹅类
def fly(self):
print("天鹅在空中翱翔")
class Plane(object): # 飞机类
def fly(self):
print("飞机隆隆地起飞了")
def fly(obj): # 实现飞的功能函数
obj.fly()
duck = Duck()
fly(duck)
swan = Swan()
fly(swan)
plane = Plane()
fly(plane)
===运行结果:===================================================================================
鸭子沿着地面飞起来了
天鹅在空中翱翔
飞机隆隆地起飞了
2、对象所属的类之间有所属关系:
class gradapa(object):
def __init__(self,money):
self.money = money
def p(self):
print("this is gradapa")
class father(gradapa):
def __init__(self,money,job):
super().__init__(money)
self.job = job
def p(self):
print("this is father,我重写了父类的方法")
class mother(gradapa):
def __init__(self, money, job):
super().__init__(money)
self.job = job
def p(self):
print("this is mother,我重写了父类的方法")
return 100
#定义一个函数,函数调用类中的p()方法
def fc(obj):
obj.p()
gradapa1 = gradapa(3000)
father1 = father(2000,"工人")
mother1 = mother(1000,"老师")
fc(gradapa1) #这里的多态性体现是向同一个函数,传递不同参数后,可以实现不同功能.
fc(father1)
print(fc(mother1))
===运行结果:===================================================================================
this is gradapa
this is father,我重写了父类的方法
this is mother,我重写了父类的方法
100