文章目录
1. 封装
1.1 封装的引入
我们先写个案例:
class Car:
def __init__(self,name,color):
self.name=name
self.color=color
def run(self):
print('会跑')
def loudspeaker(self):
print('滴滴')
c=Car('大奔','白色')
print(c.color)
c.run()
我们可以再修改一下
class Car:
def __init__(self,name,color):
self.name=name
self.color=color
def run(self):
print('%s会跑'%self.name)
def loudspeaker(self):
print('%s滴滴'%self.name)
c=Car('大奔','白色')
print(c.color)
c.run()
c.loudspeaker()
我们可以通过实例对象修改属性值:
在编程中有时候不想让人随意修改代码,所以这里需要封装一下,使属性不能随意修改。
1.2 封装的使用
封装是面向对象的三大特性之一(封装、继承、多态),指隐藏一些不希望被外界访问到的属性或方法。
案例:
class Dog:
def __init__(self,name):
self.name=name
def speak(self):
print('大家好,我是%s'%self.name)
d=Dog('二哈')
d.speak()
这是一个简单的程序,我们可以更改狗的名字:
class Dog:
def __init__(self,name):
self.name=name
def speak(self):
print('大家好,我是%s'%self.name)
d=Dog('二哈')
d.name='德牧'
d.speak()
我们看到结果被改变了,我们可以简单的标志性的封装一下,就不是那么容易改了。
class Dog:
def __init__(self,name):
self.hidden_name=name
def speak(self):
print('大家好,我是%s'%self.hidden_name)
d=Dog('二哈')
d.name='德牧'
d.speak()
大家看到了,我尝试用d.name='德牧’去改属性,但是属性并没有被改变。当然,你会说如果用d.hidden_name='德牧’不久能修改了吗?答案是肯定的,你是对的,可以修改的。所以任何事情都没有绝对的,不是绝对的封装,但是这里当你发现属性名为hidden_name的时候,你是不是考虑一些作者的感受,哦,这里是不建议修改的代码,如果修改了将会有某种后果。所以,你就要慎重考虑是不是要修改了。当然,你认为有必要,还是可以修改的。
1.3 getter和setter方法来访问或修改属性
仍用上面的案例:
class Dog:
def __init__(self,name):
self.hidden_name=name
def speak(self):
print('大家好,我是%s'%self.hidden_name)
def get_name(self):
# get_name()这个方法用来获取对象的name属性
return self.hidden_name
d=Dog('二哈')
print(d.get_name())
这样外部就可以轻松访问内部属性了。入果我们想修改属性,可以添加一个setter()方法。
class Dog:
def __init__(self,name):
self.hidden_name=name
def speak(self):
print('大家好,我是%s'%self.hidden_name)
def get_name(self):
# get_name()这个方法用来获取对象的name属性
return self.hidden_name
def set_name(self,name):
self.hidden_name=name
return self.hidden_name
d=Dog('二哈')
d.set_name('德牧')
大家看到通过set_name()方法我们修改了属性,其中56-61行代码是封装代码。平时不一定要写,必要时才用。
总结:
- 使用封装隐藏了属性名,使得调用者无法随意修改对象的属性
- 设置getter和setter方法很好的控制了属性的读和改
- 使用setter方法设置属灵,可以增加数据的验证,确保数据的值是正确的
- 可以在读取和修改属性的时候,做一些其他的处理
下面对第三和第四点做一下说明,我将上述案例添加一个年龄属性:
class Dog:
def __init__(self,name,age):
self.hidden_name=name
self.hidden_age=age
def speak(self):
print('大家好我是%s,今年我%s岁'%(self.hidden_name,self.hidden_age))
def get_age(self,age):
if age>0:
self.hidden_age=age
return self.hidden_age
d=Dog('二哈',8)
d.speak()
d.get_age(9)
d.speak()
d.get_age(-9)
d.speak()
可以看到,当我调用set_age()方法修改年龄的时候,输入数据为-9的时候就没有能够改动数据。
下面对第四点进行说明,我们可以在读取和修改属性的时候,做一些其他的处理:
class Dog:
def __init__(self,name,age):
self.hidden_name=name
self.hidden_age=age
def speak(self):
print('大家好我是%s,今年我%s岁'%(self.hidden_name,self.hidden_age))
def set_age(self,age):
if age>0:
self.hidden_age=age
print('年龄已经被修改')
else:
print('年龄没有被修改')
return self.hidden_age
def get_age(self):
print('年龄已经被读取')
return self.hidden_age
d=Dog('二哈',8)
d.speak()
print(d.get_age())
d.set_age(9)
d.speak()
print(d.get_age())
d.set_age(-9)
d.speak()
print(d.get_age())
输出结果:
D:\Python38\python.exe D:/work/基础/Day14/Demo02.py
大家好我是二哈,今年我8岁
年龄已经被读取
8
年龄已经被修改
大家好我是二哈,今年我9岁
年龄已经被读取
9
年龄没有被修改
大家好我是二哈,今年我9岁
年龄已经被读取
9
Process finished with exit code 0
做了这些处理以后,一旦有人修改了封装的数据,输出的时候就会有提醒,这在某些场合是很有用的。
2. Property装饰器(拓展)
2.1 引入
当我们写爬虫程序的时候,有这样的代码:
import requests
response=requests.get('https://www.baidu.com/')
print(response.text)
按照经验,我们认为.text应该是属性。但是如果我们看源码,发现它却是方法。
我们按住ctrl直接点击text就可以查看源码。
我们发现源码里面是方法。而且在该方法的上方有一行代码:@property,这是一个装饰器,只要被它装饰的方法,调用时如果加括号反而会报错。
2.2 @property装饰器的作用
- 用来创建只读属性,会将方法转换称相同名称的只读属性。
- 可以防止属性被修改
例题
class Person:
def __init__(self,name):
self._name=name
def name(self):
print('get方法执行了')
return self._name
p=Person('葫芦娃')
print(p.name())
结果
get方法执行了
葫芦娃
这里我们发现,我们调用了方法,但是却打印了一个名字,一般情况下,我们应该时调用并打印属性。
那么我们可以用@property装饰器装饰一下这个方法就可以了。
如果我们再像刚才那样调用方法,就会报错。
File "D:/work/基础/Day14/Demo02.py", line 107, in <module>
print(p.name())
TypeError: 'str' object is not callable
Process finished with exit code 1
那么这个属性能改吗?答案时否定的。
报错了。因为这是用@property装饰器修改的一个方法。如果我们要改,用@name.setter
class Person:
def __init__(self,name):
self._name=name
@property
def name(self):
print('get方法执行了')
return self._name
@name.setter
def name(self,name):
self._name=name
p=Person('葫芦娃')
p.name='黑猫警长'
print(p.name)
可以看到结果已经可以改了。
但是,一般我们用了@peroperty装饰器就没必要再用@name.setter装饰器了。
3. 继承
- 继承提高了代码的复用性
- 让类与类之间产生了关系,有了这个关系才有了后面的多态特性
例题:
class Animal:
def run(self):
print('动物会跑')
def sleep(self):
print('动物会睡觉')
a=Animal()
a.run()
a.sleep()
结果
动物会跑
动物会睡觉
我们想再创建一个狗类,但够类具有动物的属性,所以不需要重复写代码了,直接继承就可以了。
class Animal:
def run(self):
print('动物会跑')
def sleep(self):
print('动物会睡觉')
class Dog(Animal):
def t_home(self):
print('狗会看家')
d=Dog()
d.run()
d.sleep()
d.t_home()
结果
动物会跑
动物会睡觉
狗会看家
3.1 检验实例是否为类的实例的方法
我们看到。够类继承了动物类的方法。我们可以用isinstance()方法检查一下够类是否是动物类的实例:
class Animal:
def run(self):
print('动物会跑')
def sleep(self):
print('动物会睡觉')
class Dog(Animal):
def t_home(self):
print('狗会看家')
d=Dog()
r=isinstance(d,Animal)
print(r)
结果
True
3.2 检验某类是否为另一个类的子类的方法
还有一种方法可以检验某个类是否是另一个类的子类:issubclass()
class Animal:
def run(self):
print('动物会跑')
def sleep(self):
print('动物会睡觉')
class Dog(Animal):
def t_home(self):
print('狗会看家')
r=issubclass(Dog,Animal)
print(r)
结果
True
3.3 方法的重写
如果子类和父类出现了相同名字的属性和方法怎么办呢?那么自然,子类的同名属性和方法会替代父类的同名属性和方法。这个叫着方法的重写(或者叫着方法的覆盖)。
例题:
class Animal:
def run(self):
print('动物会跑')
def sleep(self):
print('动物会睡觉')
class Dog(Animal):
def run(self):
print('狗跑')
结果
狗跑
3.4 类的属性的继承遵循就近原则
如果子类调用一个方法,子类没有这个方法,就会去父类去找,如果父类也没有,就会去父类的父类去找,以此类推,如果都没有就会报错。
例题
class A(object):
def test(self):
print('AAA')
class B(A):
pass
class C(B):
pass
c=C()
c.test()
结果
AAA
如果代码改为:
class A(object):
def test(self):
print('AAA')
class B(A):
def test(self):
print('BBB')
class C(B):
pass
c=C()
c.test()
结果就成了
BBB