2.3 面向对象2 私有权限,继承,多态
2.3.1 私有权限
1.面向对象的特性
- 面向对象三大特性:封装,继承,多态(函数由于参数不同实现不同效果)
- 面对对象的封装特性:将属性和方法封装成一个整体,通过实例化来处理。对类的属性和方法增加权限控制。
python下一切皆对象
1.变量是对象 a = 10
2.容器也是对象 list_my = [1,2,3,4]
3.函数也是对象
def func():
print(func) ==> class function
python里函数都为多态
2.私有属性
如果在属性名前面加了2个下划线__
,则表明该属性为私有属性,否则为公有属性。
私有属性只能在类的内部访问。外部创建的实例无法访问。
class Dog(object):
def __init__(self):
self.__baby_count =0
self.age = 1
def print_info(self):
print(self.__baby_count)
dog1 = Dog()
print(dog1.age)
dog1.print_info()
print(dog1.__baby_count)
输出
1
0
Traceback (most recent call last):
File "E:/Project/pythonProject/002.py", line 697, in <module>
print(dog1.__baby_count)
AttributeError: 'Dog' object has no attribute '__baby_count'
可以看出,我们创建的实例dog1不能直接访问自己的隐藏属性,用print打印不出来。
但是可以通过类内部定义的方法来访问。
3.私有方法
在方法名的前面加2个下划线__
,一样,只能在类的内部使用,不能在外部使用。一样需要用self来访问。
class Dog(object):
def __init__(self):
self.__baby_count =0
self.age = 1
def print_info(self):
print(self.__baby_count)
self.__bite_dog()
def __bite_dog(self):
print("不许咬人")
dog1 = Dog()
print(dog1.age)
dog1.print_info()
dog1.__bite_dog()
输出
Traceback (most recent call last):
File "E:/Project/pythonProject/002.py", line 700, in <module>
dog1.__bite_dog()
AttributeError: 'Dog' object has no attribute '__bite_dog'
1
0
不许咬人
可以看出我们没法用实例直接调用私有方法__bite_dog
但是我们可以用可以访问的类的方法,通过类的内部访问。
2.3.2 继承
1.继承是什么
继承描述的是类与类的关系,父类派生出子类,子类继承父类。父类也叫基类,子类也叫派生类。
2.继承的作用
子类直接具备父类的能力,包括属性和能力,解决代码重用问题。
3.代码实例
class Animal(object):
def __init__(self, _type, _name):
self.type = _type
self.name = _name
self.__goss = '这是父类的隐藏属性'
def __str__(self):
return f"这是一种名为{self.name}的{self.type}类型的动物"
def __goff(self):
print("这是父类的隐藏方法")
def print_info(self):
# print(self.__goss)
self.__goff()
class Dog(Animal):
def __init__(self, _type, _name):
self.type = _type
self.name = _name
def __str__(self):
return f"这是一只叫{self.name}的{self.type}的狗"
def print_finfo(self):
# print(self.__goss)
self.__goff()
dog1 = Dog('金毛', '小黑')
print(dog1)
# dog1.print_info() # 查看子类的实例是否能继承了父类的隐藏属性和隐藏方法
dog1.print_finfo() #查看是否子类能直接调用父类的隐藏属性和方法
print(Dog.__base__) # 查看子类的父类名
print(dir(dog1)) # 查看该类的所有隐藏方法
ad = Animal("熊", "大熊")
ad.print_info()
可以看出son1是Son对象,却实际上可以使用父类的属性和对象。拓展,有个内置的魔法属性可以查看类的父类。类名.__bases__
可以查到这个类的父类。
子类对象调用方法有一个就近原则
- 如果本类能找到方法,直接调用本类的方法
- 如果本类找不到,则调用父类继承过来的方法
4.单继承和多层继承
1.单继承
子类只继承一个父类
2.多层继承
继承关系为多层传递
3.重写父类方法
1.子类重写父类同名方法
父类的方法不能满足需求,重写父类的方法目的是为了拓展功能
在子类中定义和一个父类同名方法,即为对父类方法重写,子类调用同名方法默认是子类的
class Animal(object):
def __init__(self, _type, _name):
self.type = _type
self.name = _name
def __str__(self):
return f"这是一种名为{self.name}的{self.type}类型的动物"
class Dog(Animal):
def __init__(self, _type, _name):
self.type = _type
self.name = _name
def __str__(self):
return f"这是一只叫{self.name}的{self.type}的狗"
dog1 = Dog('金毛', '小黑')
print(dog1)
2.子类调用父类同名方法
def print_info(self, _type, _name):
print(f"这是{self.type}的狗")
print("="*20)
# 方法1 父类名.同名方法(self,形参1 ····)
# Animal.__init__(self, _type, _name)
Animal.print_info(self)
# 方法2 super(子类名,self).同名方法(参数1····) 这个方法调用的是该类名下一级的顺序找,按__mro__得到的顺序列表
super(Dog, self).print_info()
# 方法3:super().同名方法(形参1, ……) # 是 4.2 方法的简写
# 推荐使用的方法
# super().__init__(_)
super().print_info()
输出
这是一只叫小黑的金毛的狗
这是金毛的狗
====================
这是金毛的动物
这是金毛的动物
这是金毛的动物
5.多继承
1.多继承的定义和格式
一个子类有多个父类,并且具有他们的特征。
class BigDog(object):
def eat(self):
print("大口吃肉")
class SmallDog(object):
def drink(self):
print("小口喝水")
class SuperDog(BigDog,SmallDog):
def eat(self):
print("吃罐头")
print("="*30)
BigDog.eat(self)
print("="*30)
super().eat()
sd = SuperDog()
sd.drink()
sd.eat()
输出如下
小口喝水
吃罐头
==============================
大口吃肉
==============================
大口吃肉
2.多继承的继承顺序
查看类的继承顺序 类名.__mro__
是一种默认的魔法属性,输出的就是实例里寻找方法属性的优先顺序。
print(SuperDog.__mro__)
输出
(<class '__main__.SuperDog'>, <class '__main__.BigDog'>, <class '__main__.SmallDog'>, <class 'object'>)
如果多个父类顺序有相同方法,而且你想调用最上级的而不是自己上一级的。用那种父类名.方法名的就行。
super()函数不能套套。
6.私有和继承
父类的私有方法和私有属性,子类的实例也不能直接继承使用,也是需要在父类的内部使用。不能使用子类的方法去读取。得使用父类的方法,才能使用父类的隐藏方法和隐藏属性
**通过dir()
可以看出查看也就是方法继承了,属性没有继承。**这是因为没有调用父类的__init__()
方法,并没有定义。
class Animal(object):
def __init__(self, _type, _name):
self.type = _type
self.name = _name
self.__goss = '这是父类的隐藏属性'
def __str__(self):
return f"这是一种名为{self.name}的{self.type}类型的动物"
def __goff(self):
print("这是父类的隐藏方法")
def print_info(self):
# print(self.__goss)
self.__goff()
class Dog(Animal):
def __init__(self, _type, _name):
self.type = _type
self.name = _name
def __str__(self):
return f"这是一只叫{self.name}的{self.type}的狗"
def print_finfo(self):
# print(self.__goss)
self.__goff()
dog1 = Dog('金毛', '小黑')
print(dog1)
dog1.print_info() # 查看子类的实例是否能通过父类去使用隐藏方法
# dog1.print_finfo() #查看是否子类的实例能否通过子类去使用隐藏方法(不行,报错)
print(Dog.__base__) # 查看子类的父类名
print(dir(dog1)) # 查看该类的所有隐藏方法
7.拓展
实际上python里的私有权限私有属性是给更改名字了。
我们通过更改名,就可以直接访问。
格式
print(对象._类名隐藏属性名)
对象._类名隐藏方法名()
这样的话对应类的实例都是可以直接访问的,但是一样的,子类无法通过此访问父类的隐藏属性,因为没有继承。
2.3.3 多态
调用同一函数,不同表现。
因为python本身是动态语言,本身就是多态。
实现多态的补轴
- 实现继承关系
- 子类重写父类方法
- 通过对象调用该方法
class Animal(object):
def eat(self):
print("吃东西")
class Dog(Animal):
def eat(self):
print("啃骨头")
class Cat(Animal):
def eat(self):
print("吃小鱼")
def func(temp):
temp.eat()
cat1 = Cat()
dog1 = Dog()
func(cat1)
func(dog1)
输出
吃小鱼
啃骨头
可以看出,同一个函数,因为传递进去的实例不同,因为都有重写父类方法,所实现的方法也不同。python的多态是常态。
2.3.4 实例属性,类属性
1.实例属性和类属性
1.1 专业名词说明
- 在python中,万物皆对象。
- 通过类创建的对象,又称为实例对象,对象属性又称为实例属性。
- 类本身也是一个对象,执行class语句的时候被创建出来的,我们称之为类对象,为了区分实例对象,我们也简称为类。
1.2 实例属性
- 通过在
__init__()
方法里给实例添加的属性。 - 在类的外面直接通过实例对象添加 的属性。
- 实例属性必须通过实例对象才能访问。
# 定义类
class 类名(object):
def __init__(self):
self.实例属性变量1 = 数值1
self.实例属性变量2 = 数值3
# 创建实例对象
实例对象名 = 类名()
# 添加属性
实例对象名.实例属性变量3 = 数值3
1.3 类属性
- 类属性就是 类对象 所拥有的属性,它被 该类的所有实例对象所拥有。
- 定义在类里面,类方法外面的变量就是类属性。
- 类属性可以用类名或实例对象访问,推荐使用类名访问。
class Dog(object):
# 类属性
count = 0
def __init__(self):
# 实例属性
self.name = '大黄狗'
# 类名访问类属性 推荐使用这种方法
print(Dog.count)
# 创建实例对象
dog1 = Dog()
# 实例对象访问类属性
print(dog1.count)
# 实例对象访问实例属性
print(dog1.name)
1.4 类属性和实例属性区别
- 类属性就是 类对象 所拥有的属性,他被该类所有的实例对象共有。
- 实例属性要求每个对象为其开辟一份独立内存空间,只属于某个实例对象。
# 定义一个count的类属性来计算实例对象初始化的次数
class Dog(object):
# 类属性
count = 0
def __init__(self):
# 实例属性
self.name = '大黄狗'
Dog.count += 1
# 类名访问类属性
print(Dog.count)
# 创建实例对象
dog1 = Dog()
dog2 = Dog()
dog3 = Dog()
dog4 = Dog()
dog5 = Dog()
dog6 = Dog()
print(Dog.count)
print(dog1.count,dog2.count·····)
1.5 类属性更改
1)类属性只能通过类对象访问更改,不能通过实例对象更改
如果对实例对象企图对类属性更改,系统会默认操作给你赋值一个新的实例属性,而非更改了类属性。
class Dog(object):
# 类属性
count = 0
def __init__(self):
# 实例属性
self.name = '大黄狗'
Dog.count += 1
# 类名访问类属性
print(Dog.count)
# 创建实例对象
dog1 = Dog()
dog2 = Dog()
print(Dog.count)
Dog.count = 1
print(Dog.count)
dog1.count = 250
print(Dog.count, dog1.count)
输出
0
2
1
1 250
可以看出实例对象企图更改导致创建了一个实例属性
这导致了另外一个问题
2)如果实例属性和类属性名一致,那么实例对象只能访问实例属性
此时实例对象无法访问类属性
3)类属性也能更改为私有类属性,加2个下划线即可
私有类属性也一样不能在类的外部使用访问,但是可以在内部使用。
2. 类方法,静态方法,实例方法
1.1类方法
- 类对象所拥有的方法,主要在没有创建实例对象的前提下,处理类属性
- 需要用装饰器
@classmethod
来标志其为类方法 - 对于类方法,第一个参数必须是类对象(代表类),一般以
cls
作为第一个参数,这个参数不需要手动传参,处理器会自动传。 - 类方法的调用
- 类名.类方法() 推荐用法
- 实例对象.类方法()
class Dog(object):
count = 0
@classmethod
def print_count(cls):
print(type(cls),cls)
print("cls.count = ", cls.count)
Dog.print_count()
输出
cls.count = 0
dog1 = Dog()
dog1.print_count()
输出
<class '__main__.Dog'> <class 'type'>
cls.count = 0
<class '__main__.Dog'> <class 'type'>
cls.count = 0
# 可以看出即使是使用实例对象调用类方法,实际上传参传进去的依旧是类对象,而非实例对象。
1.2 静态方法
- 需要通过装饰器
@staticmethod
来进行修饰,静态方法默认情况下, 既不传递类对象也不传递实例对象(形参没有self/cls)。 - 当方法中 既不需要使用实例对象,也不需要使用类对象时,定义静态方法
- 作用:取消不需要的参数传递,有利于 减少不必要的内存占用和性能消耗
- 静态方法 也能够通过 实例对象 和 类对象(类名) 去访问。
class Dog(object):
count = 0
@classmethod
def print_count(cls):
print(cls, type(cls))
print("cls.count = ", cls.count)
@staticmethod
def print_static():
print("这是一个不需要传递任何参数的静态方法")
dog1 = Dog()
Dog.print_static()
dog1.print_static()
1.3 实例方法与类方法,静态方法的区别总结
- 定义区别
class Dog(object):
def print1(self): # 实例方法
pass
@classmethod # 类方法
def print2(cls):
pass
@staticmethod # 静态方法
def print3():
pass
-
调用方法区别
- 实例方法必须通过实例对象调用
- 类方法,实例方法均可通过类对象,实例对象调用,但是推荐类对象调用
-
定义原则
- 当方法中需要实例属性,我们定义为实例方法
- 当方法中只需要使用类属性,定义为类方法
- 当不需要类属性和实例属性,使用静态方法