Python中的OOP

1. 面向对象概述

类和对象的概念
- 类:抽象名词,代表一个集合,共性的事物
- 对象:具象的事物,单个个体
类跟对象的关系
- 一个具象,代表一类事物的某一个个体
- 一个是抽象,代表的是一大类事物
- 表明事物的特征,叫做属性(变量)
- 表明事物功能或动作, 称为成员方法(函数)

2. 类的基本实现

如何声明一个类:使用class关键字

如何实例化类:变量 = 类名()

可以通过默认内置变量检查类和对象的所有成员
- 对象所有成员检查:obj.__dict__

- 类所有的成员检查:class_name.__dict__

3. 类和对象的成员分析

- 对象访问一个成员时,如果对象中没有该成员,尝试访问类中的同名成员,如果对象中有此成员,一定使用对象中的成员
- 创建对象的时候,类中的成员不会放入对象当中,而是得到一个空对象,没有成员
- 通过对象对类中成员重新赋值或者通过对象添加成员时,对应成员会保存在对象中,而不会修改类成员

- 类实例的属性和其对象的实例的属性在不对对象的实例属性赋值的前提下,指向同一个变量
4. 关于self

- self在对象的方法中表示当前对象本身,类似于C++中的this指针,如果通过对象调用一个方法,那么该对象会自动传入到当前方法的第一个参数中
- self并不是关键字,只是一个用于接受对象的普通参数,理论上可以用任何一个普通变量名代替
- 方法中有self形参的方法成为非绑定类的方法,可以通过对象访问, 没有self的是绑定类的方法,只能通过类访问
- 使用类访问绑定类的方法时, 如果类方法中需要访问当前类的成员,可以通过 __class__成员名来访问,实例调用__class__属性时会指向该实例对应的类,然后可以再去调用其它类属性

class Teacher():
    name = "dana"
    age = 19
    
    def say(self):
        self.name = "yaona"
        self.age = 17
        print("My name is {0}".format(self.name))
        # 调用类的成员变量需要用 __class__
        print("My age is {0}".format(__class__.age))
    def sayAgain():
        print(__class__.name)
        print(__class__.age )
        
t = Teacher()
t.say()
# 调用绑定类函数使用类名
Teacher.sayAgain()
#My name is yaona
#My age is 19
#dana
#19
# 关于self的案例

class A():
    name = "liuying"
    age = 18
    
    def __init__(self):
        self.name = "aaaa"
        self.age = 200
        
    def say(self):
        print(self.name)
        print(self.age)
        
class B():
    name = "bbbb"
    age = 90
    
a = A()
# 此时,系统会默认把a作为第一个参数传入函数
a.say()
   
# 此时,self被a替换
A.say(a)
# 同样可以把A作为参数传入
A.say(A)

# 此时,传入的是类实例B,因为B具有name和age属性,所以不会报错
A.say(B)
# 以上代码,利用了鸭子模型

#aaaa
#200
#aaaa
#200
#liuying
#18
#bbbb
#90

5. 面向对象的三大特性

5.1 封装

- 封装就是对对象的成员进行访问限制
- 封装的三个级别:
公开的,public:

- 公开的封装实际对成员没有任何操作,任何地方都可以访问,默认为public
受保护的,protected:

- 受保护的封装是将对象成员进行一定级别的封装,在类中或者子类中都可以进行访问,但在外部不可以
- 封装方法:在成员前面添加一个下划线即可

私有的,private:

- 私有成员是最高级别的封装,只能在当前类或对象中访问
- 封装方法:在成员前面添加两个两个下划线即可,可以使用对象._classname_attributename访问 

Python中的下划线约定:

- 在python中,双下划线开头的类变量就用到了name mangling技术,会自动加上类名前缀
- 作用:可以防止意外在子类或者类外面被修改到,虽然单下划线开头的变量也有private的意味,但是更多的只是个约定,可以直接访问到,而双下划线开头的变量更彻底

class A:
    def __init__(self):
        self.__foo = 'foo'
        self._bar = 'bar'
a = A()
print a.__dict__
print a._bar
print a.__foo #这句会报错

其中print a.__dict__输出{'_A__foo': 'foo', '_bar': 'bar'}
可以看到,__foo加上类名前缀变成了_A__foo.
汇总python中的下划线约定:

_xxx:表示内部使用,不能被from M imoprt *导入
xxx_:表示避免和关键字冲突,如Tkinter.Toplevel(master, class_='ClassName')
__xxx:更彻底的private.用到了name mangling技术,会自动加上类名前缀.不能被子类和类外访问
__xxx__:魔术方法或用户控制的命名空间

5.2 继承

- 继承就是一个类可以获得另外一个类中的成员属性和成员方法,父类写在当前类的括号内
- 作用:减少代码,增加代码的复用功能,同时可以设置类与类直接的关系
继承与被继承的概念:
- 被继承的类叫父类,也叫基类,也叫超类
- 用于继承的类,叫子类,也叫派生类
- 继承与被继承一定存在一个is-a 关系
继承的特征:
- 所有的类都继承自object类,即所有的类都是object类的子类
- 子类一旦继承父类,则可以使用父类中除私有成员外的所有内容
- 子类继承父类后并没有将父类成员完全赋值到子类中,而是通过引用关系访问调用
- 子类中可以定义独有的成员属性和方法
- 子类中定义的成员和父类成员如果相同,则优先使用子类成员
- 子类如果想扩充父类的方法,可以在定义新方法的同时访问父类成员来进行代码重用,可以使用 [父类名.父类成员] 的格式来调用父类成员,也可以使用super().父类成员的格式来调用
继承变量函数的查找顺序问题:
- 优先查找自己的变量
- 没有则查找父类
- 构造函数如果本类中没有定义,则自动查找调用父类构造函数
- 如果本类有定义,则不在继续向上查找
构造函数:
- 在C++中是一类特殊的函数,在类进行实例化之前进行调用,__init__魔术方法在Python中就是构造函数
- 如果定义了构造函数,则实例化时使用构造函数,不查找父类构造函数
- 如果没定义,则自动查找父类构造函数
- 如果子类没定义,父类的构造函数带参数,则构造对象时的参数应该按父类参数构造
super:
- super不是关键字, 而是一个类
- super的作用是获取MRO(MethodResolustionOrder)列表中的第一个类
- super和父类直接没任何实质性关系,但通过super可以调用到父类

# 子类扩充父类功能的案例
# 人由工作的函数, 老师也由工作的函数,但老师的工作需要讲课
class Person():
    name = "NoName"
    age = 18
    __score = 0 # 考试成绩是秘密,只要自己知道
    _petname = "sec" #小名,是保护的,子类可以用,但不能公用
    def sleep(self):
        print("Sleeping ... ...")
    def work(self):
        print("make some money")
        
#父类写在括号内
class Teacher(Person):
    teacher_id = "9527"
    name = "DaNa"
    def make_test(self):
        print("attention")
        
    def work(self):
        # 扩充父类的功能只需要调用父类相应的函数
        #Person.work(self)
        # 扩充父类的另一种方法
        # super代表得到父类
        super().work()
        self.make_test()
        
t = Teacher()
t.work()

#make some money
#attention

单继承和多继承
- 单继承:每个类只能继承一个类
- 多继承,每个类允许继承多个类

# 子类可以直接拥有父类的属性和方法,私有属性和方法除外
class Fish():
    def __init__(self,name):
        self.name = name
    def swim(self):
        print("i am swimming......")
        
class Bird():
    def __init__(self, name):
        self.name = name
    def fly(self):
        print("I am flying.....")

class Person():
    def __init__(self, name):
        self.name = name
    def work(self):
        print("Working........")
        
# 单继承的例子      
class Student(Person):
    def __init__(self, name):
        self.name = name
stu = Student("yueyue")
stu.work()
        
# 多继承的例子  
class SuperMan(Person, Bird, Fish):
    def __init__(self, name):
        self.name = name

s = SuperMan("yueyue")
s.fly()
s.swim()

#Working........
#I am flying.....
#i am swimming......

多继承的MRO:
- MRO就是多继承中,用于保存继承顺序的一个列表
- MRO列表的计算原则:
- 子类永远在父类前面,如果多个父类,则根据继承语法中括号内类的书写顺序存放,如果多个类继承了同一个父类,孙子类中只会选取继承语法括号中第一个父类的父类(参考资料:https://www.cnblogs.com/whatisfantasy/p/6046991.html)

5.3 多态

- 多态就是同一个对象在不同情况下有不同的状态出现,多态不是语法,是一种设计思想
- 多态和多态性的区别:多态指的是一类事物有多种形态(一个抽象类有多个子类,因而多态的概念依赖于继承);多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数(参考资料:https://www.cnblogs.com/luchuangao/p/6739557.html)

Mixin设计模式:

- 我们使用多继承语法来实现Minxin
- 使用Mixin实现多继承的时候非常小心,首先他必须表示某一单一功能,而不是某个物品,职责必须单一,如果有多个功能,则写多个Mixin
- Mixin不能依赖于子类的实现
- 子类及时没有继承这个Mixin类,也能照样工作,只是缺少了某个功能
- Mixin的优点:
- 使用Mixin可以在不对类进行任何修改的情况下,扩充功能
- 可以方便的组织和维护不同功能组件的划分
- 可以根据需要任意调整功能类的组合
- 可以避免创建很多新的类,导致类的继承混乱

# Mixin案例
class Person():
                name = "liuying"
                age = 18

                def eat(self):
                    print("EAT.......")
                    
                def drink(self):
                    print("DRINK......")
                    
                def sleep(self):
                    print("SLEEP.....")
                
class Teacher(Person):
                def work(self):
                    print("Work")

class Student(Person):
                def study(self):
                    print("Study")
                    

class Tutor(Teacher, Student):
                pass

t = Tutor()
             
print(Tutor.__mro__)
print(t.__dict__)
print(Tutor.__dict__)

print("*"*20)
class TeacherMixin():
                def work(self):
                    print("Work")

class StudentMixin():
                def study(self):
                    print("Study")
                    
class TutorM(Person, TeacherMixin, StudentMixin):
                pass

tt = TutorM()
print(TutorM.__mro__)
print(tt.__dict__)
print(TutorM.__dict__)

结果表明Mixin其实就是多继承的一种:

(<class '__main__.Tutor'>, <class '__main__.Teacher'>, <class '__main__.Student'>, <class '__main__.Person'>, <class 'object'>)
{}
{'__module__': '__main__', '__doc__': None}
********************
(<class '__main__.TutorM'>, <class '__main__.Person'>, <class '__main__.TeacherMixin'>, <class '__main__.StudentMixin'>, <class 'object'>)
{}
{'__module__': '__main__', '__doc__': None}

6.类的相关函数

- issubclass:检测一个类是否是另一个类的子类
- isinstance:检测一个对象是否是一个类的实例
- hasattr:检测一个对象是否由成员xxx
- getattr: get attribute
- setattr: set attribute
- delattr: delete attribute
- dir: 获取对象的成员列表

7.类的成员描述符

- 类的成员描述符是为了在类中对类的成员属性进行相关操作而创建的一种方式
- get:获取属性的操作
- set:修改或者添加属性操作
- delete:删除属性的操作
- 如果想使用类的成员描述符,大概有三种方法
1)使用类实现描述器
2)使用属性修饰符
3)使用property函数:property(fget, fset, fdel, doc)

# peroperty案例
# 对于任意输入的姓名,我们希望都用大写方式保存
class Person():
    # 函数的名称可以任意
    def fget(self):
        return self._name * 2
    
    def fset(self, name):
        # 所有输入的姓名以大写形式保存
        self._name = name.upper()
        
    def fdel(self):
        self._name = "NoName"
    
    name = property(fget, fset, fdel, "对name进行upper操作")

p1 = Person()
p1.name = "wjw"
print(p1.name)

#WJWWJW

8.类的内置属性

__dict__:以字典的方式显示类的成员组成
__doc__: 获取类的文档信息
__name__:获取类的名称,如果在模块中使用,获取模块的名称
__bases__: 获取某个类的所有父类,以元组的方式显示

9.类常用的魔术方法

- 魔术方法就是不需要人为调用的方法,基本是在特定的时刻自动触发,方法名被前后各两个下滑线包裹
操作类:
- `__init__`: 所谓的构造函数

# init 举例
class A():
    def __init__(self, name = 0):
        print("哈哈,我被调用了")
        
a = A()

#哈哈,我被调用了

- `__call__`: 对象当函数使用的时候触发

# __call__举例
class A():
    def __init__(self, name = 0):
        print("哈哈,我被调用了")
        
    def __call__(self):
        print("我被调用了again")
        
a = A()
a()

#哈哈,我被调用了
#我被调用了again

- `__str__`: 当对象被当做字符串使用的时候调用

# __str__举例
class A():
    def __init__(self, name = 0):
        print("哈哈,我被调用了")
        
    def __call__(self):
        print("我被调用了again")
       
    def __str__(self):
        return "例子"
a = A()
print(a)

#哈哈,我被调用了
#例子

属性操作类:
- `__getattr__`: 访问一个不存在的属性时触发

# __getattr__举例

class A():
    name = "NoName"
    age = 18
   
    def __getattr__(self, name):
        print("没找到呀没找到")
        print(name)
        
a = A()
print(a.name)
print(a.addr)

#NoName
#没找到呀没找到
#addr
#None

- `__setattr__`: 对成员属性进行设置的时候触发

# __setattr__举例
class Person():
    def __init__(self):
        pass
    
    def __setattr__(self, name, value):
        print("设置属性: {0}".format(name))
        # 下面语句会导致问题,死循环
        #self.name = value
        # 此种情况,为了避免死循环,规定统一调用父类魔法函数
        super().__setattr__(name, value)
        
p = Person()
print(p.__dict__)
p.age = 18

#{}
#设置属性: age

10.类和对象的三种方法

- 实例方法:需要实例化对象才能使用的方法,使用过程中可能需要截止对象的其他对象的方法完成
- 静态方法:不需要实例化,通过类直接访问
- 类方法:不需要实例化

# 三种方法的案例
class Person:
    # 实例方法
    def eat(self):
        print("Eating.....")
    
    #类方法
    # 类方法的第一个参数,一般命名为cls,区别于self
    @classmethod
    def play(cls):
        print("Playing.....")
        
    # 静态方法
    # 不需要用第一个参数表示自身或者类
    @staticmethod
    def say():
        print("Saying....")
        
yueyue = Person()

# 实例方法
yueyue.eat()
# 类方法
Person.play()
yueyue.play()
#静态方法
Person.say()
yueyue.say()

#Eating.....
#Playing.....
#Playing.....
#Saying....
#Saying....

11.抽象类

- 抽象方法:没有具体实现内容的方法成为抽象方法
- 抽象方法的主要意义是规范了子类的行为和接口

- 抽象类:包含抽象方法的类叫抽象类,通常称为ABC类,使用需要借助abc模块:import abc
- 抽象类可以包含抽象方法,也可以包含具体方法
- 抽象类中可以有方法也可以有属性
- 抽象类不允许直接实例化
- 必须继承才可以使用,且继承的子类必须实现所有继承来的抽象方法
- 假定子类没有是现实所有继承的抽象方法,则子类也不能实例化
- 抽象类的主要作用是设定类的标准,以便于开发的时候具有统一的规范

# 抽象类的实现

import abc

#声明一个类并且指定当前类的元类
class Human(metaclass=abc.ABCMeta):

    # 定义一个抽象的方法
    @abc.abstractmethod
    def smoking(self):
        pass
    
    # 定义类抽象方法
    @abc.abstractclassmethod
    def drink():
        pass
    
    # 定义静态抽象方法
    @abc.abstractstaticmethod
    def play():
        pass
    
    def sleep(self):
        print("Sleeping.......")

12.自定义类

- 类其实是一个类定义和各种方法的自由组合
- 可以定义类和函数,然后自己通过类直接赋值

class A():
    pass

def say(self):
    print("Saying... ...")
    
    
class B():
    def say(self):
        print("Saying......")
        
say(9)
A.say = say

a = A()
a.say()

b = B()
b.say()

#Saying... ...
#Saying... ...
#Saying......

- 可以借助于MethodType实现

from types import MethodType

class A():
    pass

def say(self):
    print("Saying... ...")
    
a = A()
a.say = MethodType(say, A)
a.say()

#Saying... ...

- 借助于type实现

# 先定义类应该具有的成员函数
def say(self):
    print("Saying.....")
    
def talk(self):
    print("Talking .....")
    
#用type来创建一个类
A = type("AName", (object, ), {"class_say":say, "class_talk":talk})

# 然后可以像正常访问一样使用类
a = A()

a.class_say()
a.class_talk()

#Saying.....
#Talking .....

- 利用元类实现- MetaClass:元类是用来创造别的类的类

# 元类写法是固定的,必须继承自type
# 元类一般命名以MetaClass结尾
class TulingMetaClass(type):
    # 注意以下写法
    def __new__(cls, name, bases, attrs):
        #自己的业务处理
        print("哈哈,我是元类呀")
        attrs['id'] = '000000'
        attrs['addr'] = "北京海淀区公主坟西翠路12号"
        return type.__new__(cls, name, bases, attrs)
    
# 元类定义完就可以使用,使用注意写法
class Teacher(object, metaclass=TulingMetaClass):
    pass

t = Teacher()
t.id

#哈哈,我是元类呀
#'000000'



 

猜你喜欢

转载自www.cnblogs.com/wjw2018/p/10493106.html