面向对象编程(进阶)
目录
- 类的继承
- 什么是继承
- 为什么用继承
- 对象的继承
- 类的分类
- 新式类
- 经典类
- 继承与抽象
- 继承的应用
- 属性查找练习
- 类的派生
- 派生
- 派生方法一
- 派生方法二
- 派生
- 类的组合
- 什么是组合
- 为什么用组合
- 如何用组合
- 菱形继承问题
- 类的分类
- 新式类
- 经典类
- 菱形继承问题
- C3算法与mro()方法介绍
- 类的分类
- 类的多态与多态性
- 多态
- 动物的多种形态
- 文件的多种形态
- 多态性
- 动物形态多态性的使用
- 文件形态多态性的使用
- 序列数据类型多态性的使用
- 多态性的好处
- 小结
- 为什么用多态
- 多态的应用
- 数据类型中多态的应用
- 鸭子类型
- 多态
- 类的封装
- 什么是封装
- 封装什么
- 为什么要封装
- 两个层面的封装
- 第一个层面
- 第二个层面
- 私有模块
- 类的property特性
- 什么是property特性
- 简单示例
- property属性的两种方式
- 装饰器
- 类属性方式
- property + 类的属性
- 应用
- 私有属性添加getter和setter方法
- 使用property升级getter和setter方法
- 使用property取代getter和setter方法
- 练习
- 类与对象的绑定方法和非绑定方法
- 绑定方法
- 对象的绑定方法
- 类的绑定方法
- 非绑定方法
- 练习
- 绑定方法小结
- 非绑定方法小结
- 绑定方法
类的继承
什么是继承
继承是一种新建类的方式,新建的类称为子类,被继承的类称为父亲
继承的特性是:子类会遗传父亲的属性
继承是类与类之间的关系
为什么用继承
- 使用继承可以减少代码的冗余
对象的继承
- Python中支持一个类同时继承多个父类
class Parent1:passclass Parent2:passclass Sub1(Parent1, Parent2):pass
- 使用
__bases__
方法可以获取对象继承的类
print(Sub1.__bases__)
(<class '__main__.Parent1'>, <class '__main__.Parent2'>)
在Python3中如果一个类没有继承任何类,则默认继承object类
在Python2中如果一个类没有继承任何类,不会继承object类
print(Parent1.__bases__)
(<class 'object'>,)
类的分类
新式类
继承了object的类以及该类的子类,都是新式类
Python3中所有的类都是新式类
经典类
没有继承object的类以及该类的子类,都是经典类
只有Python2中才有经典类
继承与抽象
继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承,抽象即抽取类似或者说比较像的部分。
继承:基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类。
继承的应用
- 牢记对象是特征与功能的集合体
class PekingPeople:'''由于学生和老师都是人,因此人都有姓名、年龄、性别'''school = 'peking'def __init__(self, name, age, gender):self.name = nameself.age = ageself.gender = genderclass PekingStudent(PekingPeople):def choose_course(self):print(f'{self.name} is choosing course')class PekingTeacher(PekingPeople):def score(self, stu_obj, num):print(f'{self.name} is scoring')stu_obj.score = numstu1 = PekingStudent('tom', 18, 'male')
tea1 = PekingTeacher('jerry', 18, 'male')
- 对象查找属性的顺序:对象自己 --> 对象的类 --> 父类 --> 父类。。。
print(stu1.school)
peking
print(tea1.school)
peking
print(stu1.__dict__)
{'name': 'tom', 'age': 18, 'gender': 'male'}
tea1.score(stu1, 99)
jerry is scoring
print(stu1.__dict__)
{'name': 'tom', 'age': 18, 'gender': 'male', 'score': 99}
属性查找练习
class Foo:def f1(self):print('Foo.f1')def f2(self):print('Foo.f2')self.f1()class Bar(Foo):def f1(self):print('Bar.f1')# 对象查找属性的顺序:对象自己 $rightarrow$ 对象的类 --> 父类 --> 父类。。。
obj = Bar() # self是obj本身,即找到Bar的f1()
obj.f2()
Foo.f2
Bar.f1
类的派生
派生
- 派生:子类中新定义的属性的这个过程叫做派生,并且需要记住子类在使用派生的属性时始终以自己的为准
派生方法一
- 指名道姓访问某一个类的函数:该方式与继承无关
class PekingPeople:'''由于学生和老师都是人,因此人都有姓名、年龄、性别'''school = 'peking'def __init__(self, name, age, gender):self.name = nameself.age = ageself.gender = genderclass PekingStudent(PekingPeople):'''由于学生类没有独自的__init__()方法,因此不需要声明继承父类的__init__()方法,会自动继承'''def choose_course(self):print(f'{self.name} is choosing course')class PekingTeacher(PekingPeople):'''由于老师类有独自的__init__()方法,因此需要声明继承父类的__init__()'''def __init__(self, name, age, gender, level):PekingPeople.__init__(self, name, age, gender)self.level = level # 派生def score(self, stu_obj, num):print(f'{self.name} is scoring')stu_obj.score = numstu1 = PekingStudent('tom', 18, 'male')
tea1 = PekingTeacher('jerry', 18, 'male', 10)
print(stu1.__dict__)
{'name': 'tom', 'age': 18, 'gender': 'male'}
print(tea1.__dict__)
{'name': 'jerry', 'age': 18, 'gender': 'male', 'level': 10}
派生方法二
严格以继承属性查找关系
super()会得到一个特殊的对象,该对象就是专门用来访问父类中的属性的(按照继承的关系)
super().__init__(不用为self传值)
super的完整用法是super(自己的类名,self),在Python2中需要写完整,而Python3中可以简写为super()
class PekingPeople:school = 'peking'def __init__(self, name, age, gender):self.name = nameself.age = ageself.gender = genderclass PekingStudent(PekingPeople):def __init__(self, name, age, gender, stu_id):super().__init__(name, age, gender)self.stu_id = stu_iddef choose_course(self):print(f'{self.name} is choosing course')stu1 = PekingStudent('tom', 19, 'male', 1)
print(stu1.__dict__)
{'name': 'tom', 'age': 19, 'gender': 'male', 'stu_id': 1}
类的组合
什么是组合
- 组合就是一个类的对象具备某一个属性,该属性的值是指向另外一个类的对象
为什么用组合
- 组合是用来解决类与类之间代码冗余的问题
class PekingPeople:school = 'peking'def __init__(self, name, age, gender):self.name = nameself.age = ageself.gender = genderclass PekingStudent(PekingPeople):def __init__(self, name, age, gender, stu_id):PekingPeople.__init__(self, name, age, gender)self.stu_id = stu_iddef choose_course(self):print(f'{self.name} is choosing course')class PekingTeacher(PekingPeople):def __init__(self, name, age, gender, level):PekingPeople.__init__(self, name, age, gender)self.level = leveldef score(self, stu, num):stu.score = numprint(f'老师{self.name}为学生{stu.name}打分{num}')stu1 = PekingStudent('tom', 19, 'male', 1)
tea1 = PekingTeacher('jerry', 18, 'male', 10)
stu1.choose_course()
tom is choosing course
tea1.score(stu1, 100)
老师jerry为学生tom打分100
print(stu1.__dict__)
{'name': 'tom', 'age': 19, 'gender': 'male', 'stu_id': 1, 'score': 100}
- 上面这个选课系统需要修改、扩展,我们需要修改上述的代码
如何用组合
需求:假如我们需要给学生增添课程属性,但是又不是所有学生一进学校就有课程属性,课程属性时学生来学校后选出来的,也就是说课程需要后期学生们添加进去的
实现思路:如果直接在学生中添加课程属性,那么学生刚被定义就需要添加课程属性,这就不符合我们的要求,因此我们可以使用组合能让学生未来添加课程属性
class Course:def __init__(self, name, period, price):self.name = nameself.period = periodself.price = pricedef tell_info(self):msg = f'''课程名:{self.name}课程周期:{self.period}课程价钱:{self.price}'''print(msg)class PekingPeople:school = 'peking'def __init__(self, name, age, gender):self.name = nameself.age = ageself.gender = genderclass PekingStudent(PekingPeople):def __init__(self, name, age, gender, stu_id):PekingPeople.__init__(self, name, age, gender)self.stu_id = stu_iddef choose_course(self):print(f'{self.name} is choosing course')class PekingTeacher(PekingPeople):def __init__(self, name, age, gender, level):PekingPeople.__init__(self, name, age, gender)self.level = leveldef score(self, stu, num):stu.score = numprint(f'老师{self.name}为学生{stu.name}打分{num}')
# 创造课程
python = Course('python全栈开发', '5mons', 3000)
python.tell_info()
课程名:python全栈开发课程周期:5mons课程价钱:3000
linux = Course('linux运维', '5mons', 800)
linux.tell_info()
课程名:linux运维课程周期:5mons课程价钱:800
# 创造学生与老师
stu1 = PekingStudent('tom', 19, 'male', 1)
tea1 = PekingTeacher('jerry', 18, 'male', 10)
- 组合
# 将学生、老师与课程对象关联/组合
stu1.course = python
tea1.course = linux
stu1.course.tell_info()
课程名:python全栈开发课程周期:5mons课程价钱:3000
tea1.course.tell_info()
课程名:linux运维课程周期:5mons课程价钱:800
- 组合可以理解成多个人去造一个机器人,有的人造头、有的人造脚、有的人造手、有的人造驱赶,大家都完工后,造躯干的人把头、脚、手拼接到自己的躯干上,因此一个机器人便造出来了
菱形继承问题
类的分类
新式类
继承了object的类以及该类的子类,都是新式类
Python3中所有的类都是新式类
经典类
没有继承object的类以及该类的子类,都是经典类
只有Python2中才有经典类
菱形继承问题
在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)
如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性
如果继承关系为菱形结构,即子类的父类最后继承了同一个类,那么属性的查找方式有两种:
经典类:深度优先
新式类:广度优先
经典类:一条路走到黑,深度优先
新式类:不找多个类最后继承的同一个类,直接去找下一个父类,广度优先
class G(object):# def test(self):# print('from G')passprint(G.__bases__)class E(G):# def test(self):# print('from E')passclass B(E):# def test(self):# print('from B')passclass F(G):# def test(self):# print('from F')passclass C(F):# def test(self):# print('from C')passclass D(G):# def test(self):# print('from D')passclass A(B, C, D):def test(self):print('from A')obj = A()
(<class 'object'>,)
obj.test() # A->B->E-C-F-D->G-object
from A
C3算法与mro()方法介绍
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,如:
print(A.mro()) # A.__mro__
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
for i in A.mro():print(i)
<class '__main__.A'>
<class '__main__.B'>
<class '__main__.E'>
<class '__main__.C'>
<class '__main__.F'>
<class '__main__.D'>
<class '__main__.G'>
<class 'object'>
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
类的多态与多态性
多态
- 多态是指同一种事物的多种形态(一个抽象类有多个子类,因而多态的概念依赖于继承)
* 水 --> 冰、水蒸气、液态水* 序列数据类型有多种形态:字符串,列表,元组* 动物 --> 人、狗、猪
动物的多种形态
# 动物有多种形态:人类、猪、狗(在定义角度)
class Animal:def run(self): # 子类约定俗成的必须实现这个方法raise AttributeError('子类必须实现这个方法')class People(Animal):def run(self):print('人在走')class Pig(Animal):def run(self):print('pig is walking')class Dog(Animal):def run(self):print('dog is running')peo1 = People()
pig1 = Pig()
dog1 = Dog()peo1.run()
pig1.run()
dog1.run()
人在走
pig is walking
dog is running
import abcclass Animal(metaclass=abc.ABCMeta): # 同一类事物:动物@abc.abstractmethod # 上述代码子类是约定俗成的实现这个方法, 加上@abc.abstractmethod装饰器后严格控制子类必须实现这个方法def speak(self):raise AttributeError('子类必须实现这个方法')class People(Animal): # 动物的形态之一:人def speak(self):print('say hello')class Dog(Animal):def speak(self):print('say wangwangwang')class Pig(Animal):def speak(self):print('say aoao')peo2 = People()
pig2 = Pig()
dog2 = Dog()peo2.speak()
pig2.speak()
dog2.speak()
say hello
say aoao
say wangwangwang
文件的多种形态
# 文件有多种形态:文件、文本文件、可执行文件(在定义角度)import abc# 同一类事物:文件
class File(metaclass=abc.ABCMeta):@abc.abstractmethoddef click(self):passclass Text(File): # 文件的形态之一:文本文件def click(self):print('open file')class ExeFile(File): # 文件的形态之二:可执行文件def click(self):print('execute file')text = Text()
exe_file = ExeFile()text.click()
exe_file.click()
open file
execute file
多态性
注意:多态与多态性是两种概念
多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表达多态性:向不同的对象发送同一条消息,不同的对象在就收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
动物形态多态性的使用
# 多态性:一种调用方式,不同的执行效果(多态性)
def func(obj):obj.run()func(peo1)
func(pig1)
func(dog1)
人在走
pig is walking
dog is running
# 多态性依赖于:继承
# 多态性:定义统一的接口def func(obj): # obj这个参数没有类型限制,可以传入不同类型的值obj.speak() # 调用的逻辑都一样,执行的结果却不一样func(peo2)
func(pig2)
func(dog2)
say hello
say aoao
say wangwangwang
文件形态多态性的使用
def func(obj):obj.click()func(text)
func(exe_file)
open file
execute file
序列数据类型多态性的使用
def func(obj):print(len(obj))func('hello')
func([1, 2, 3])
func((1, 2, 3))
5
3
3
综上可以说,多态性是一个接口(函数func)的多种实现,如obj.run(),obj.speak(),obj.click(),len(obj)
多态性的好处
Python本身就是支持多态性的,这么做的好处是什么:
增加了程序的灵活性:以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
增加了程序的可扩展性:通过继承Animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
class Cat(Animal): # 属于动物的另一种形态:猫def speak(self):print('say miao')def func(animal): # 对于使用者来说,自己的代码根本无需改动animal.speak()cat1 = Cat() # 实例出一只猫
func(cat1) # 甚至连调用方式也无需改变,就能调用猫的speak功能
say miao
- 上述代码新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的speak方法,即func(cat1)
小结
多态:同一种事物的多种形态,动物分为人类,猪类(在定义角度)
多态性:一种调用方式,不同的执行效果(多态性)
为什么用多态
- 多态性:
* 继承同一个类的多个子类中有相同的方法名* 那么子类产生的对象就可以不用考虑具体的类型而直接调用功能
多态的应用
Animal() --> 强调是用来指定标准的,不能被实例化,也就是说父类中被
@abc.abcstractmethod
装饰的方法,子类也必须要有,如果没有的话则会报错,这个拥有被装饰的方法的类称为抽象类由于动物都叫,如果人的叫使用speak();狗的叫使用bark();也就是说对于不同的动物有不同的叫方法,那么对于使用者来说,使用起来非常麻烦。因此我们可以规定人、狗的叫都为speak(),那么后面使用某个动物叫的方法,只需要调用speak即可
import abcclass Animal(metaclass=abc.ABCMeta): # 同一类事物:动物@abc.abstractmethoddef speak(self):print('1111')@abc.abstractmethoddef eat(self):passclass People(Animal):def speak(self):print('say hello')def eat(self):passclass Dog(Animal):def speak(self):print('汪汪汪')def eat(self):passclass Pig(Animal):def speak(self):print('哼哼哼')def eat(self):passpeo = People()
dog = Dog()
pig = Pig()
peo.speak()
say hello
dog.speak()
汪汪汪
pig.speak()
哼哼哼
def my_speak(animal):animal.speak()my_speak(peo)
my_speak(dog)
my_speak(pig)
say hello
汪汪汪
哼哼哼
数据类型中多态的应用
- 我们一一直在使用多态,列表、元组、字符串的len()方法其实就是一种多态的应用,我们不需要关心len()的数据类型,我们只要记住如果需要求一个容器类型的长度,使用len()方法就行了
l = [1, 2, 3]
s = 'hello'
t = (1, 2, 3)print(l.__len__())
print(s.__len__())
print(t.__len__())# 列表、字符串、元组规定了计算这些数据类型的长度就必须使用len()方法
# def len(obj):
# return obj.__len__()
3
5
3
print(len(l))
print(len(s))
print(len(t))
3
5
3
鸭子类型
Python推崇的是鸭子类型,只要你叫声像鸭子,并且你走路的样子像鸭子,那么你就是鸭子
Linux中一切皆文件,只要我们规定硬盘、进程、文件都是文件,都拥有读read()写write()方法,那我们就没必要定义一个抽象文件类,更没必要使用
@abc.abstractmethod
装饰器规定文件需要拥有这些方法,因为这不符合Python的风格
class Disk:'''硬盘'''def read(self):print('disk read')def write(self):print('disk write')class Process:'''进程'''def read(self):print('process read')def write(self):print('process write')class File:'''文件'''def read(self):print('file read')def write(self):print('file write')obj1 = Disk()
obj2 = Process()
obj = File()obj1.read()
obj1.write()
disk read
disk write
类的封装
什么是封装
封:类的属性对外是隐藏的,但是对内是开放的,类似于一个封闭的容器
装:定义类时会申请一个名称空间,往里装入一系列名字/属性
封装什么
你钱包的有什么钱(数据的封装)
你吃饭具体怎么实现的(方法的封装)
为什么要封装
封装数据的主要原因是:保护隐私
封装方法的主要原因是:隔离复杂度
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),就是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
两个层面的封装
封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)
第一个层面
第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名,或者obj.的方法去访问里面的名字,这本身就是一种封装
注意:对于这一层面的封装(隐藏),类名和实例名就是访问隐藏属性的接口
第二个层面
第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
在python中用双下划线的方式实现隐藏属性(设置成私有的)
类中所有双下划线开头的名称如__x
都会自动变形成:_类名__x
的形式:
class A:__N = 0 # 类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形成_A__Ndef __init__(self):self.__X = 10 # 变形为self._A__Xdef __foo(self): # 变形为_A__fooprint('form A')def bar(self):self.__foo() # 只有在类内部才可以通过__foo的形式访问到
这种自动变形的特点:
- 类中定义的
__x
只能在内部使用,如self.__x
,引用的就是变形的结果。 - 这种变形其实正是针对内部的变形,在外部是无法通过
__x
这个名字访问到。 - 在子类定义的
__x
不会覆盖在父类定义的__x
,因为子类中变形成了:_子类名__x
,而父类中形成了:_父类名__x
,即双下划线开头的属性在继承给子类时,子类是无法覆盖的。
注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了
这种变形需要注意的问题是:
- 这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类属性名就可以拼出名字:
_类名__属性
,然后就可以访问了,如a._A__N
a = A()
print(a._A__N)
0
print(a._A__X)
10
print(A._A__N)
0
- 变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形
a = A()
print(a.__dict__)
{'_A__X': 10}
a.__Y = 1
print(a.__dict__)
{'_A__X': 10, '__Y': 1}
- 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
# 正常情况
class A:def fa(self):print('from A')def test(self):self.fa()class B(A):def fa(self):print('from B')b = B()
b.test()
from B
# 把fa定义成私有的,即__fa
class A:def __fa(self): # 在定义时就变形为_A__faprint('from A')def test(self):self.__fa() # 只会与自己所在的类为准,即调用_A__faclass B(A):def __fa(self):print('from B')b = B()
b.test()
from A
私有模块
python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块中的变量名_private_module
以单下划线开头,那么from module import *
时不能被导入该变量,但是你from module import _private_module
依然是可以导入该变量的
其实很多时候你去调用一个模块的功能时会遇到单下划线开头的socket._socket,sys._home,sys._clear_type_cache
,这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的
python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__
注:__名字
,这种语法只在定义的时候才有变形的效果,如果类或对象已经产生了,就不会有变形得效果了。
类的property特性
什么是property特性
- property装饰器用于将被装饰的方法伪装成一个数据属性,在使用时可以不用加括号而直接使用
####### 定义 #######
class Foo:def func(self):pass# 定义property属性@propertydef prop(self):pass####### 调用 #######
foo_obj = Foo()
foo_obj.func() # 调用实例方法
foo_obj.prop # 调用property属性
如下的例子用于说明如何定一个简单的property属性:
class Goods(object):@propertydef size(self):return 100g = Goods()
print(g.size)
100
property属性的定义和调用要注意一下几点:
- 定义时,在实例方法的基础上添加@property装饰器,并且仅有一个self参数
- 调用时,无需括号
简单示例
对于京东商城中显示电脑主机的列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据 这个分页的功能包括:
- 根据用户请求的当前页和总数据条数计算出 m 和 n
- 根据m 和 n 去数据库中请求数据
############ 定义 ##########
class Pager:def __init__(self, current_page):# 用户当前请求的页码(第一页、第二页...)self.current_page = current_page# 每页默认显示10条数据self.per_items = 10@propertydef start(self):val = (self.current_page - 1)*self.per_itemsreturn val@propertydef end(self):val = self.current_page * self.per_itemsreturn val########### 调用 #############
p = Pager(1)
p.start # 就是起始值,即:m
p.end # 就是结束值,即:n
10
从上述可见Python的property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。
property属性的两种方式
- 装饰器 即:在方法上应用装饰器(推荐使用)
- 类属性 即:在类中定义值为property对象的类属性(Python2历史遗留)
装饰器
在类的实例化方法上应用@property装饰器
Python中的类有经典类和新式类,新式类的属性比经典类的属性丰富。(如果类继承object,那么该类是新式类)
经典类,具有一种@property装饰器:
########## 定义 ##########
class Goods:@propertydef price(self):return 'laowang'########## 调用 ########
obj = Goods()
result = obj.price # 自动执行 @property 修饰的price方法,并获取方法的返回值
print(result)
laowang
新式类,具有三种@property装饰器:
# coding = utf-8
############ 定义 ##########class Goods:'''python3中默认继承object类以Python2,3执行此程序的结果不同,因为只有在Python3中才有@xxx.setter @ xxx.deleter'''@propertydef price(self):print('@property')@price.setterdef price(self, value):print('@price.setter')@price.deleterdef price(self):print('@price.deleter')########## 调用 ############
obj = Goods()
obj.price # 自动执行@property修饰price方法,并获取方法的返回值
obj.price = 123 # 自动执行@price.setter修饰的price方法,并将123赋值给方法的参数
del obj.price # 自动执行@price.deleter修饰的price方法
@property
@price.setter
@price.deleter
注意:
经典类中的属性只有一种访问方式,其对应被@property修饰的方法
新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法
由于新式类中具有三种访问方式,我们可以根据它们几个属性访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
class Goods(object):def __init__(self):self.original_price = 100self.discount = 0.8@propertydef price(self):new_price = self.original_price * self.discountreturn new_price@price.setterdef price(self, value):self.original_price = value@price.deleterdef price(self):del self.original_priceobj = Goods()
obj.price
obj.price = 200
del obj.price
类属性方式
创建值为property对象的类属性
注意:当使用类属性的方式创建property属性时,经典类和新式类无区别
class Foo:def get_bar(self):return 'laowang'BAR = property(get_bar)obj = Foo()
result = obj.BAR # 自动调用get_bar方法,并获取方法的返回值
print(result)
laowang
property方法中有四个参数
第一个参数是方法名,调用
对象.属性
时自动触发执行方法第二个参数是方法名,调用
对象.属性 = xxx
时自动触发执行方法第三个参数是方法名,调用
del 对象.属性
时自动触发执行方法第四个参数是字符串,调用
对象.属性.__doc__
,此参数是属性的描述信息
# coding = utf-8
class Foo(object):def get_bar(self):print('getter...')return 'laowang'def set_bar(self, value):'''必须两个参数'''print('setter...')return 'set value' + valuedef del_bar(self):print('deleter...')return 'laowang'BAR = property(get_bar, set_bar, del_bar, 'description...')obj = Foo()obj.BAR
obj.BAR = 'alex'
desc = Foo.BAR.__doc__
print(desc)
del obj.BAR
getter...
setter...
description...
deleter...
定义property属性共有两种方式,分别是【装饰器】和【类属性】,而【装饰器】方法针对经典类和新式类又有所不同。
通过使用property属性,能够简化调用者在获取数据的流程
property + 类的属性
class People:def __init__(self, name):self.__name = name@property # 查看obj.namedef name(self):return F'<名字是{self.__name}>'peo1 = People('tom')
print(peo1.name)
<名字是tom>
try:peo1.name = 'jerry'
except Exception as e:print(e)
can't set attribute
应用
私有属性添加getter和setter方法
class Money(object):def __init__(self):self.__money = 0def getMoney(self):return self.__moneydef setMoney(self, value):if isinstance(value, int):self.__money = valueelse:print('error:不是整型数据')
使用property升级getter和setter方法
class Money(object):def __init__(self):self.__money = 0def getMoney(self):return self.__moneydef setMoney(self, value):if isinstance(value, int):self.__money = valueelse:print('error:不是整型数据')# 定义一个数据,当对这个money设置值时调用setMoney,当获取值时调用getMoneymoney = property(getMoney, setMoney)a = Money()
a.money = 100 # 调用setMoney方法
print(a.money) # 调用getMoney方法
100
使用property取代getter和setter方法
重新实现一个属性的设置和读取方法,可做边界判定
class Money(object):def __init__(self):self.__money = 0# 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法@propertydef money(self):return self.__money# 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法@money.setterdef money(self, value):if isinstance(value, int):self.__money = valueelse:print("error:不是整型数字")a = Money()
a.money = 100
print(a.money)
100
练习
计算圆的周长和面积
import mathclass Circle:def __init__(self, radius): # 圆的半径radiusself.radius = radius@propertydef area(self):return math.pi * self.radius**2@propertydef perimeter(self):return 2 * math.pi * self.radiusc = Circle(10)
print(c.radius)
10
print(c.area) # 可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
314.1592653589793
print(c.perimeter)
62.83185307179586
类与对象的绑定方法和非绑定方法
类中定义的方法大致可以分为两类:绑定方法和非绑定方法。其中绑定方法又可以分为绑定到对象的方法和绑定到类的方法。
绑定方法
对象的绑定方法
在类中没有被任何装饰器修饰的方法就是 绑定到对象的方法,这类方法专门为对象定制。
class People:country = 'China'def __init__(self, name, age):self.name = nameself.age = agedef speak(self):print(self.name + ',' + str(self.age))p = People('Kitty', 18)
print(p.__dict__)
{'name': 'Kitty', 'age': 18}
print(People.__dict__['speak'])
<function People.speak at 0x000001C6F66D7B70>
speak即为绑定到对象的方法,这个方法不在对象的名称空间中,而是在类的名称空间中。
通过对象调用绑定到对象的方法,会有一个自动传值的过程,即自动将当前对象传递给方法的第一个参数(self,一般都叫self,也可以写成别的名称);若是使用类的调用,则第一个参数需要手动传值。
peo = People('tom', 18)
peo.speak() # 通过对象调用
tom,18
People.speak(p) # 通过类调用
Kitty,18
类的绑定方法
类中使用@classmethod修饰的方法就是绑定到类的方法。这类方法专门为类定制。通过类名调用绑定到类的方法时,会将类本身当作参数传给类方法的第一个参数。
class Operate_database():host = '192.168.0.5'port = '3306'user = 'abc'password = '123456'@classmethoddef connect(cls):print(cls)print(cls.host + ':' + cls.port + ' ' + cls.user + '/' + cls.password)Operate_database.connect()
<class '__main__.Operate_database'>
192.168.0.5:3306 abc/123456
通过对象也可以调用,只是默认传递的第一个参数还是这个对象对应的类。
Operate_database().connect() # 输出结果一致
<class '__main__.Operate_database'>
192.168.0.5:3306 abc/123456
非绑定方法
在类内部使用@staticmethod修饰的方法即为非绑定方法,这类方法和普通定义的函数没有区别,不与类或对象绑定,谁都可以调用,且没有自动传值的效果。
import hashlibclass Operate_database():def __init__(self, host, port, user, password):self.host = hostself.port = portself.user = userself.password = password@staticmethoddef get_password(salt, password):m = hashlib.md5(salt.encode('utf8')) # 加盐处理m.update(password.encode('utf8'))return m.hexdigest()hash_password = Operate_database.get_password('lala', '123456') # 通过类调用
print(hash_password)
f7a1cc409ed6f51058c2b4a94a7e1956
p = Operate_database('192.168.0.5', '3306', 'abc', '123456')
hash_password = p.get_password(p.user, p.password) # 也可以通过对象调用
print(hash_password)
0659c7992e268962384eb17fafe88364
非绑定方法就是将普通方法放到了类的内部。
练习
假设我们现在有一个需求,需要让Mysql实例化的对象可以从文件setting.py中读取数据。
# settings.pyIP = '1.1.1.10'
PORT = 3306
NET = 27
# test.py
import uuidclass Mysql:def __init__(self, ip, port, net):self.uid = self.create_uid()self.ip = ipself.port = portself.net = netdef tell_info(self):"""查看ip地址和端口号"""print('%s:%s' % (self.ip, self.port))@classmethoddef from_conf(cls):return cls(IP, NET, PORT)@staticmethoddef func(x, y):print('不与任何人绑定')@staticmethoddef create_uid():"""随机生成一个字符串"""return uuid.uuid1()# 默认的实例化方式:类名()
obj = Mysql('10.10.0.9', 3307, 27)
obj.tell_info()
10.10.0.9:3307
绑定方法小结
如果函数体代码需要用外部传入的类,则应该将该函数定义成绑定给类的方法
如果函数体代码需要用外部传入的对象,则应该将函数定义成绑定给对象的方法
# 一种新的实例化方式:从配置文件中读取配置完成实例化
obj1 = Mysql.from_conf()
obj1.tell_info()
1.1.1.10:27
print(obj.tell_info)
<bound method Mysql.tell_info of <__main__.Mysql object at 0x000001C6F686E080>>
print(obj.from_conf)
<bound method Mysql.from_conf of <class '__main__.Mysql'>>
非绑定方法小结
如果函数体代码既不需要外部传入的类也不需要外部传入的对象,则应该将函数定义成非绑定方法/普通函数
obj.func(1, 2)
不与任何人绑定
Mysql.func(3, 4)
不与任何人绑定
print(obj.func)
<function Mysql.func at 0x000001C6F672D598>
print(Mysql.func)
<function Mysql.func at 0x000001C6F672D598>
print(obj.uid)
05a7846c-94bc-11e9-8895-20898491368f
转载于:https://www.cnblogs.com/WilliamKong94/p/11123185.html
面向对象编程(进阶)相关推荐
- Python开发系列课程(11) - 面向对象编程进阶
面向对象编程进阶 在前面的章节我们已经了解了面向对象的入门知识,知道了如何定义类,如何创建对象以及如何给对象发消息.为了能够更好的使用面向对象编程思想进行程序开发,我们还需要对Python中的面向对象 ...
- Day7 - 面向对象编程进阶及其他相关
参考文章:http://www.cnblogs.com/alex3714/articles/5213184.html 本节内容: 面向对象高级语法部分 静态方法.类方法.属性方法 类的特殊成员方法 反 ...
- Python学习笔记——基础篇【第七周】———FTP作业(面向对象编程进阶 Socket编程基础)...
FTP作业 本节内容: 面向对象高级语法部分 Socket开发基础 作业:开发一个支持多用户在线的FTP程序 面向对象高级语法部分 参考:http://www.cnblogs.com/wupeiqi/ ...
- Day7 - Python基础7 面向对象编程进阶 --转自金角大王
本节内容: 面向对象高级语法部分 经典类vs新式类 静态方法.类方法.属性方法 类的特殊方法 反射 异常处理 Socket开发基础 作业:开发一个支持多用户在线的FTP程序 面向对象高级语法部分 经典 ...
- 第07章_面向对象编程(进阶)
1. 关键字:this 1.1 this是什么? 在Java中,this关键字不算难理解,它的作用和其词义很接近. 它在方法(准确的说是实例方法或非static的方法)内部使用,表示调用该方法的对象 ...
- Python 面向对象编程(进阶部分)
静态方法: 通过 @staticmethod 装饰器即可把其装饰的方法变为一个静态方法.普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例变量或类变量,但静态方法是不可以访问实 ...
- Python|装饰器|执行时间|递归|动态属性|静态方法和类|继承和多态|isinstance类型判断|溢出|“魔法”方法|语言基础50课:学习记录(6)-函数的高级应用、面向对象编程、进阶及应用
文章目录 系列目录 原项目地址: 第16课:函数的高级应用 装饰器(记录执行时间的例子) 递归调用 简单的总结 第17课:面向对象编程入门 类和对象 定义类 创建和使用对象 初始化方法 打印对象 面向 ...
- php控制器面向对象编程,PHP 面向对象编程(2)
一些内建方法: class Person { public $isAlive = true; function __construct($name) { //这里我们创建了一个name的属性 $thi ...
- Scala进阶之路-面向对象编程之类的成员详解
Scala进阶之路-面向对象编程之类的成员详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Scala中的object对象及apply方法 1>.scala 单例对象 ...
- JavaScript 面向对象编程(三) —— 函数进阶 / 严格模式 / 高阶函数 / 闭包 / 浅拷贝和深拷贝
本篇为 JavaScript 进阶 ES6 系列笔记第三篇,将陆续更新后续内容.参考:JavaScript 进阶面向对象 ES6 :ECMAScript 6 入门 系列笔记: JavaScript 面 ...
最新文章
- Android编程获取网络连接状态及调用网络配置界面
- golang defer 关闭文件 报错file may have nil or other unexpected value as its corresponding error
- Linux是否兼容windows跨区卷,简单卷与跨区卷的区别介绍
- Java类的加载过程详解 面试高频!!!值得收藏!!!
- c# 通过内存映射实现文件共享内存
- 华科计算机课程设计,华中科大操作系统课程设计报告(附源码).doc
- 【PKUWC2018】随机游走【Min-Max容斥】【树形dp】【FWT】
- cnpm不是内部或外部命令 cnpm: command not found 解决方案 cnpm
- matlab地址数据类型uns,使用matlab生成sine波mif文件
- shell命令一览表
- C++ vector
- Django中的form模块的高级处理
- python函数chr_python函数之chr(i)
- 一个百分号%引起的事故
- 幂次方计算_4.初中数学:怎么求n的m次方的值?幂的运算,单项式相乘,基础常见考试题...
- 爬取飞猪IP免费代理练习
- python一维列表变二维列表_使用Python轻松应对一维表与二维表相互转换
- Word——如何给公式自动编号插入题注交叉引用时不出现公式本身||公式行距变大怎么办
- 电子书寻找方法汇总2
- java使用flex生成swf_flex动态生成矢量swf字体--java动态生成swf文件