基本理论
面向对象在python中的实践
类的创建
通过类实例化对象
对象属性的增删改查
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> python属性相关.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
"""
类的定义,通过类实例化对象,给对象动态的增加新属性
"""
# 1. 定义一个类
class Person:
pass
# 2. 根据类实例化一个对象
p = Person()
# 3. 直接通过对象名.属性名的方式给对象添加新的属性
p.age = 18 # 通过对象名.属性名的方式给当前对象p新增一个属性age并给其赋值
p.name = "zs"
p.age = 20 # 通过对象名.属性名的方式访问对象的属性,并修改对象的属性
p.pets = [1, 2, 3] # 给对象增加一个属性,值的类型是列表
p.pets.append(4) # 访问对象的可变类型的属性,调用该属性类型自己的方法来修改对象属性的值,此时对象的属性名不会改变指向
p.pets = [1,2] # 通过访问对象的属性,并给该属性赋予新的值,此时该属性名的指向发生了改变,指向了新的值的内存地址
del p.age # 通过del语句删除对象的属性
# 4. 直接通过对象名.属性名的方式调用对象的属性
print(p.name)
类属性的增删改查
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> python类属性相关.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
"""
给类添加属性
"""
class Money:
pass
# 方法1,当类已经定义好了,直接通过类名.属性名的方式给类添加新的属性值
Money.count = 1
print(Money.count) # 访问类属性
print(Money.__dict__)
# 方式二,在创建类的时候,在类的内部给类添加属性
class Person:
name = "zs" # 在创建类的时候,给类创建类属性
age = 18
print(Person.age) # 访问类的属性
print(Person.__dict__)
peter = Person() # 通过类实例化一个对象
print(peter.name) # 通过类实例化的对象访问类的属性
# 对象是通过__class__属性来表示他的父类的
print(peter.__class__) # 打印Peter对象的父类
peter.__class__ = Money # 通过__class__修改peter对象的父类
print(peter.__class__) # 打印Peter对象的父类
# 对象属性的访问和修改顺序。原则就是查询(访问)的时候是就近原则,先找自己在找父类。修改(赋值)只操作自己本身,不会去影响父类,自己没有就创建新的并赋值,自己有就直接修改
peter = Person()
print(peter.age) # 对象继承了父类的age属性 18
print(peter.__dict__) # 查看Peter对象的属性集合中包含了那些属性 结果为{}空,也就是说peter自身并没有直接拥有任何属性,都是从父类继承来的
peter.age = 20 # 修改对象的age值 ,此时在Peter对象中没有查询到Peter自己直接的age属性,只有其父类有,而需要给age属性赋值,所以程序会直接给Peter对象创建一个age属性并对其赋值,而不是去修改其父类的age属性
print(peter.__dict__) # 查看peter对象的直接属性列表,可以发现此时多了一个age属性
print(peter.age) # 查看对象被修改后的age 20
print(Person.age) # 验证对象修改从父类继承来的属性的修改会不会影响到父类原来的属性值 age 还是18 显然对象修改从父类继承来的属性值不会影响到父类原来的值
Person.age = 30 # 类名.属性名 修改属性值
print(Person.age) # 查看修改后的属性值age =30 这充分说明,子类是不能直接修改父类的属性值的
实例方法
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> python实例方法.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
"""
类创建实例方法
"""
class Person:
# 创建实例方法,要求实例方法的第一次参数要传递一个实例对象,来标识该方法是哪个实例的,类似于java的this
def eat(self, food):
print("在吃饭" + food)
p = Person()
p.eat("饺子")
类方法
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> python类方法.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
class Person:
# 实例方法
def eat(self):
print(self)
# # 创建一个类方法,用装饰器classmethod来装饰一个函数,那么该函数就是一个类函数,需要给该函数的第一个参数传递一个类对象参数
@classmethod
def class_func(cls):
print(cls, "这是一个类方法")
Person.class_func() # 通过类调用
one = Person()
one.class_func() # 通过实例调用类方法,通过实例的__class__属性找打该实例继承的类传递给类方法的第一个参数cls
# 衍生类,子类,也就是继承该类的子类
class A(Person):
pass
A.class_func() # 子类调用父类的类方法,此时传递给类方法的第一个参数cls指示的就是当前这个子类
静态方法
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> python静态方法.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
class Person:
# 类的静态方法,使用staticmethod装饰器来装饰类中的一个函数,那么该函数就是一个静态函数,不许要传递第一个默认参数
@staticmethod
def static_func():
print("这是一个静态方法")
Person.static_func() # 通过类直接调用
one = Person()
one.static_func() # 通过类的实例来调用
class A(Person):
pass
A.static_func() # 通过子类调用
私有变量和构造方法
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> python私有化属性.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
"""
__init__(self,必须的变量列表)构造方法,用来初始化实例的时候需要创建必须的变量的时候使用的
私有变量的作用,主要就是只能在类的内部来访问,目的是为了对数据的验证和过滤,保住数据的合法性
为了保证数据的合法性,所以在修改或者访问数据的时候,需要先验证数据是合法的在对数据进行修改和访问
所以需要getter 和setter方法 目的就是不让直接访问数据,在访问之前进行合法性验证
方法一:直接写getter setter方法,显示调用这两个方法
方法二:property (getter, setter),把getter 和setter方法传递个property函数,来实现不需要显示地调用getter和setter方法,而是通过实例名.属性名的方式调用
方法三:@property装饰器 装饰“变量名方法” @变量名方法.setter装饰器装饰setter方法
"""
class Animal:
def __init__(self, name):
self.__name = name
def get_name(self):
print("数据有效性验证")
return self.__name
def set_name(self, name):
print("数据合法性验证")
self.__name = name
name = property(get_name, set_name) # 传递getter 和setter 方法给 property(),实现直接通过实例名.属性名的方式访问私有变量
dog = Animal("小花")
print(dog.get_name())
print(dog.name) # 直接通过变量名.属性名的方式访问变量,此时的属性名就是接收这个property函数返回值的变量
dog.set_name("小黄")
print(dog.name)
dog.name = "小白"
print(dog.name)
私有方法二
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> python私有化方法装饰器.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
"""
__init__(self,必须的变量列表)构造方法,用来初始化实例的时候需要创建必须的变量的时候使用的
私有变量的作用,主要就是只能在类的内部来访问,目的是为了对数据的验证和过滤,保住数据的合法性
为了保证数据的合法性,所以在修改或者访问数据的时候,需要先验证数据是合法的在对数据进行修改和访问
所以需要getter 和setter方法 目的就是不让直接访问数据,在访问之前进行合法性验证
方法一:直接写getter setter方法,显示调用这两个方法
方法二:property (getter, setter),把getter 和setter方法传递个property函数,来实现不需要显示地调用getter和setter方法,而是通过实例名.属性名的方式调用
方法三:@property装饰器 装饰“变量名方法” @变量名方法.setter装饰器装饰setter方法
用方法三,也就是用装饰器的方式,则不能显示的调用getter和setter方法了
"""
class Person:
def __init__(self, name):
self.__name = name
@property
def name(self):
print("数据有效性验证")
return self.__name
@name.setter # 被修饰的setter方法的方法名name必须与被getter方法装饰器property装饰的函数同名,也就是这里两个装饰器撞死的函数必须同名 也就是 都是装饰 name这个函数
def name(self, name):
print("数据合法性验证")
self.__name = name
dog = Person("小花")
print(dog.name
dog.name = "小黄" # 虽然类内部使用了setter方法,但是用了装饰器,所以这里对变量的访问可以直接用"实例名.变量名"的方式来访问,而此时的变量名就是被装饰器装饰的getter setter函数名,要求此时的getter setter必须同名
print(dog.name)
格式化打印和允许被调用函数 str与call
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> python内置方法.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self): # __str__类的格式化打印方法,与java的toString一样,重写这个函数可以实现想要的输出,默认是打实例的类型和地址
return "这个人的姓名是%s,这个人的年龄是%s" % (self.name, self.age)
def __call__(self, *args, **kwargs): # __call__就是当实例被调用的时候要去执行的方法
print("这是一个人叫%s,并且是一个%s" % (self.name, *args))
one = Person("张三", 18)
print(one) # 验证__str__ 方法
one("男人") # 验证__call__方法,该方法使得实例对象可以被调用,在调用的时候传递的参数会直接传递给__call__函数
one("女人") # 可以实现直接给实例对象传参调用,就跟类对象的构造函数一样
索引操作对应字典和切片操作对应列表
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> python实例索引操作.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
"""
实例对象索引操作,就是可以像操作列表一样操作实例
必须实现__setitem__ __getitem__ __delattr__ 这三个内置方法
"""
class Person:
def __init__(self):
self.cache_dict = {}
def __setitem__(self, key, value):
self.cache_dict[key] = value
def __getitem__(self, item):
return self.cache_dict[item]
def __delattr__(self, item):
del self.cache_dict[item]
def __str__(self):
return self.cache_dict.__str__()
one = Person()
one["name"] = "张三"
print(one)
对象之间的比较 eq 、ne
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> python实例比较.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
"""
通过内置的__eq__方法实现两个对象的比较
"""
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.age == other.age # 通过年龄来比较两个类
one = Person("张三", 18)
two = Person("李四", 20)
print(one == two)
python描述器
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> python描述器.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
"""
描述器,就是用来做数据合法性验证的,类似java中的getter和setter方法
描述器必须包含三个方法:__get__ __set__ __delete__
描述器的实现有两种方法
第一种方法:用property装饰器来装饰一个操作私有变量的三个方法,这种方法会在类里面写很多代码 然程序很臃肿,可读性差
第二种方法:在一个类中只写这三个方法,解释器会自动将该类解释为一个描述器,当对实例中的变量进行操作时 解释器会自动将这三种操作转移到描述器对象中去操作
"""
# 直接通过property装饰器实现描述器功能
class Person:
def __init__(self, name, age):
self.name = name
self.__age = age # 直接用property 实现描述器 __age是实例成员比变量
# 描述器实现方法一 直接对私有变量进行get set delet 装饰
@property # get 方法
def age(self):
print("数据有效性验证")
return self.__age
@age.setter # set 方法
def age(self, age):
print("输入数据合法性检查:%d" % (age))
self.__age = age
@age.deleter # delete 方法
def age(self):
print("属性存在性检查")
del self.__age
# one = Person("张三", 18)
# print(one.age) # get方法验证
#
# one.age = 20 # set 方法验证
#
# print(one.age)
# 类描述器方法
# 方法二,单独定义一个描述器类
class Name:
# def __init__(self, name):
# print(type(self))
# self.name = name # name 是实例属性
def __get__(self, instance, owner):
# self 是描述器这个类Name的实例的实例属性 在例中指的就是Dog类的中name属性 和dog实例中的name属性他们是不一样的
# instance 是调用Name这个描述器的类中的实例属性 在此列中指的就是 下面的 dog这个实例
# owner 是调用Name这个描述器的类 在此列中 指的就是下面的Dog类
# return instance.name
print("数据有效性验证")
print(type(self))
print(type(instance))
print(type(owner))
return self.name
def __set__(self, instance, value):
# value 是传递进来的值
print("数据合法性验证")
self.name = value
def __delattr__(self, item):
pass
class Dog:
name = Name()
# Dog.name # 验证在类属性中使用 描述器
dog = Dog()
dog.name = 5 # 验证在 实例属性中 使用 描述器
dog.name
类装饰器
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> python类装饰器.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
"""
装饰器的实现方式有两种
方式一:基于函数的闭包形式
方式二:基于类的形式,实现__init__和__call__方法
"""
# 方式一:闭包形式,把原函数,传递个装饰器函数
def check(func):
def inner(*args, **kwargs):
print("装饰器一:登录验证") # 给原函数增加新的操作
return func(*args, **kwargs) # 执行原函数
return inner # 返回新的函数
# 方式二:类装饰器,实现类的__init__函数和__call__函数
class Check:
# 需要将原函数传递个 类的构造方法,将原函数保存成为类的一个方法
def __init__(self, func):
self.func = func
# 为了使装饰器类能被调用,需要实现__call__方法,同时在__call__方法中给原函数添加新的功能,并调用原函数
def __call__(self, *args, **kwargs):
print("装饰器二:登录验证") # 增加新的功能
return self.func(*args, **kwargs) # 执行原函数
@check
def base(a, b):
print(a + b)
base(1, 2)
@Check
def base1(a, b):
print(a + b)
base1(1, 2)
python对象的生命周期,以及周期方法
面向对象编程-综合案例
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
==================================================
@Project -> File :learning-python -> 01-综合案例.py
@IDE :PyCharm
@Author :jhong.tao
@Date :2021/6/7 10:34
@Desc :
==================================================
"""
from win32com import client
# 计算器,实现加减运算
class Caculator():
def __speaker(self, word):
"""
声音播报器
:param word:
:return:
"""
speaker = client.Dispatch("SAPI.SpVoice")
speaker.speak(word)
def __say(*args):
"""
根据不同的计算法方法播报不同的语言的装饰器生成器
:param args:
:return:
"""
def speaker(func):
"""
语音装饰器
:param func:
:return:
"""
def inner(self, *iargs):
self.__speaker(str(*args) + str(*iargs))
return func(self, *iargs)
return inner
return speaker
def __check(func):
"""
输入数字检查装饰器
:return:
"""
def inner(self, num):
if not isinstance(num, int):
raise TypeError("数据类型错误")
return func(self, num)
return inner
@__check
@__say()
def __init__(self, num):
self.__res = num
@__check
@__say("加")
def jia(self, num):
self.__res += num
return self # 链式调用方式
@__check
@__say("减")
def jian(self, num):
self.__res -= num
return self
def show(self):
self.__speaker(str("等于%d" % self.__res))
print(self.__res)
return self
def clear(self):
self.__speaker(str("清除"))
self.__res = 0
@property # get 方法
def result(self):
return self.__res
c1 = Caculator(1).jia(3)
c1.show()
c1.clear()
面向对象三大特性
继承的搜索
MRO
MRO即method resolution order,用于判断子类调用的属性来自于哪个父类。在Python2.3之前,MRO是基于深度优先算法的,自2.3开始使用C3算法,定义类时需要继承object,这样的类称为新式类,否则为旧式类
从图中可以看出,旧式类查找属性时是深度优先搜索,新式类则是广度优先搜索
C3算法最早被提出是用于Lisp的,应用在Python中是为了解决原来基于深度优先搜索算法不满足本地优先级,和单调性的问题。
- 本地优先级:指声明时父类的顺序,比如C(A,B),如果访问C类对象属性时,应该根据声明顺序,优先查找A类,然后再查找B类。
- 单调性:如果在C的解析顺序中,A排在B的前面,那么在C的所有子类里,也必须满足这个顺序
关于super
从mro就能知道,super指的是 MRO 中的下一个类,而不是父类。super所做的事如下面代码所示:
Python类分为两种,一种叫经典类,一种叫新式类。两种都支持多继承。def super(cls, inst):
mro = inst.__class__.mro()
return mro[mro.index(cls) + 1]
考虑一种情形,B继承于A,C继承于A和B, 但C需要调用父类的init()函数时,前者会导致父类A的init()函数被调用2次,这是不希望看到的。而且子类要显式地指定父类,不符合DRY原则。 ```python经典类
class A(): def init(self):print 'A'
class B(A): def init(self): A.init(self) print ‘B’
class C(B, A): def init(self): A.init(self) B.init(self) print ‘C’
采用新式类,要求最顶层的父类一定要继承于object,这样就可以利用super()函数来调用父类的**init**()等函数,每个父类都执行且执行一次,并不会出现重复调用的情况。而且在子类的实现中,不用到处写出所有的父类名字,符合DRY原则。
```python
# 新式类
class A(object):
def __init__(self):
print 'A'
class B(A):
def __init__(self):
super(B, self).__init__()
print 'B'
class C(B, A):
def __init__(self):
super(C, self).__init__()
print 'C'
采用super()方式时,会自动找到第一个多继承中的第一个父类,但是如果还想强制调用其他父类的init()函数或两个父类的同名函数时,就要用老办法了。
class Person(object):
name = ""
sex = ""
def __init__(self, name, sex='U'):
print 'Person'
self.name=name
self.sex=sex
class Consumer(object):
def __init__(self):
print 'Consumer'
class Student(Person, Consumer):
def __init__(self, score,name):
print Student.__bases__
super(Student, self).__init__(name, sex='F')
Consumer.__init__(self)
self.score=score
s1 = Student(90, 'abc')
print s1.name, s1.score, s1.sex
多继承的缺点
当类很多,继承复杂的情况下,继承路径太多,很难说清什么样的继承路径。Python语法是允许多继承,但Python代码是解释执行,只有执行到的时候,才发现错误。团队协作开发,如果引入多继承,那代码很有可能不可控。不管编程语言是否支持多继承,都应当避免多继承。Python的面向对象,我们看到的太灵活了,太开放了,所以要团队守规矩。