面向对象
语言的分类

面向机器
抽象成机器指令,机器容易理解
代表:汇编语言
面向过程
问题规模小,可以步骤化,按部就班的进行
代表:C语言
面向对象OOP
什么是面向对象呢?
一种认识世界、分析世界的方法论,将万事万物抽象为各种对象
类(class):类是抽象的概念,是万事万物的抽象。是一类事物的共同特征的集合。
对象(instance、object):对象是类的具象,是一个实体。
属性:它是对象状态的抽象,用数据结构来描述。
操作:它是对象行为的抽象,用操作名和实现该操作的方法来描述。
哲学
一切皆对象
对象是数据和操作的封装
对象是独立的,但是对象之间可以相互作用
目前OOP是最接近人类认知的编程范式

面向对象的3要素

封装

  • 组装:将数据和操作组装到一起
  • 隐藏数据:对外只暴露一些接口,通过接口访问对象。
    继承
  • 多复用,继承来的就不同自己写了
  • 多继承少修改,OCP(open-closed Principle),使用继承来改变,来体现个性
    多态
  • 面向对象变成最灵活的地方,动态绑定
Python的类

定义

class ClassName:
      语句块
  • 必须使用class关键字
  • 类名必须是用大驼峰命名
  • 类定义完成后,就产生了一个类对象,绑定到了便是福ClassName上
class MyClass:
    """A example class"""
    x='abc'#类属性
    def foo(self): #类属性,也是方法
        return 'My Class'
print(1,MyClass.x)
print(2,MyClass.foo)
print(3,MyClass.foo(1))
print(4,MyClass.__name__)
print(5,MyClass.__doc__)

在这里插入图片描述

类对象及类属性
  • 类对象:类的定义执行后会生成一个类对象
  • 类的属性:类定义中的变量和类中定义的方法都是类的属性
  • 类变量:上例中的X是MyClass的变量

MyClass中,x、foo都是类的属性,__doc__也是类的特殊属性
foo方法是类的属性,也是方法method,本质上就是普通的函数对象function,它一般要求至少有一个参数,第一个形式参数可以是self。
self指代的是当前实例本身

实例化
a=MyClass() #实例化

使用上面的语法,在类对象名称后面加上一个括号,就调用类的实例化对象,完成实例化。
每次实例化后获得的实例,是不同的实例,即使使用相同的参数实例化,也得到了不一样的对象,Python类实例化后,会自动调用__init__方法,这个方法第一个形式参数必须留给self,其它参数随意

__init__方法

MyClass()实际上调用的是__init__()方法,可以不定义,如果没有定义会在实例化后隐式调用
作用:对实例完成初始化

class Person:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def show(self):
        print(self.name,self.age)
tom=Person('tom',20) #实例化,会调用__init__方法并为实例进行属性的初始化
jerry=Person('jerry',25)
print(tom.name,jerry.age)
jerry.age+=1
print(jerry.age)
jerry.show()

注意:init()方法不能有返回值,也就是只能是return None

实例对象instance

类实例化后一定会获得一个类的实例,就是实例对象
上例中的tom、jerry就是Person类的实例
__init__方法的第一参数self就是指代某一个实例本身。
类实例化后,得到一个实例对象,调用时采用jerry.show()方式,实例对象会绑定到方法上。但是该函数是showage(self),必须函数参数self
这个self就是Jerry,Jerry.showage()调用时,会把方法的调用者Jerry实例作为第一参数self的实参传入。
self.name就是jerry对象的name,name是保存在了Jerry对象上,而不是Person类上。所以被称为实例变量

self
class MyClass:
    def __init__(self):
        print(1,id(self))
    def show(self):
        print(2,id(self))
 c=MyClass() #会调用__init__
print(id(c))
c.show()

在这里插入图片描述
上例说明,self就是调用者,就是c对应的实例对象
self这个名字只是一个管理,它可以修改,但是尽量不要修改

实例变量和类变量
class Person:
    age=3
    def __init__(self,name):
        self.name=name
tom=Person('tom')#实例化、初始化
jerry=Person('jerry')
print(jerry.name,jerry.age)
print(Person.age)
Person.age=30
print(Person.age,tom.age,jerry.age)

在这里插入图片描述

print(Person.name)

在这里插入图片描述
实例变量是每一个实例自己的变量,是自己独有的;类变量是类的变量,是类的所有实例共享的属性和方法

特殊属性 含义
name 对象名
class 对象的类型
dict 对象的属性的字典
qualname 类的限定名

注意:
Python中的每一种对象都拥有不同的属性,函数、类都是对象,类的实例也是对象

class Person:
    age=3
    def __init__(self,name):
        self.name=name
print(Person.__class__,type(Person))

在这里插入图片描述

print(sorted(Person.__dict__.items()),end='\n\n')

在这里插入图片描述

tom=Person('tom')
print(tom.__class__,type(tom))
print(sorted(tom.__dict__.items()),end='\n\n')

在这里插入图片描述

print(tom.__class__.__name__)
print(sorted(tom.__class__.__dict__.items()))

在这里插入图片描述
上例中,可以看到类属性保存在类的__dict__中,实例属性保存在实例的__dict__中,如果从实例访问类的属性,也可以借助__class__找到所属的类,再通过类来访问类属性。

class Person:
    age=3
    height=170
    def __init__(self,name,age=18):
        self.name=name
        self.age=age
tom=Person('tom')#实例化、初始化
jerry=Person('jerry',20)

在这里插入图片描述
总结:
是类的,也是这个类所有实例的,其实例都可以访问到;
是实例的,就是这个实例自己的,通过类访问不到
类变量是属于类的变量,这个类的所有实例可以共享这个变量
对象(实例或类)可以动态的给自己增加一个属性(赋值即定义一个新属性)
实例.dict[变量名]和实例.变量名都可以访问到自己的属性(但两种访问有本质上的区别的)
实例的同名变量会隐藏掉类变量,或者说是覆盖这个类变量,但是类变量依旧存在,并不是真正的覆盖
实例属性的查找顺序
指的是实例使用.点号来访问属性,先找到自己的__dict__,如果没有,通过属性__class__找到自己的类,再去类的__dict__中找
注意:如果实例使用__dict__[变量名]访问变量,将不会按照上面的查找顺序找变量了,这是指明使用字典的key查找,不是属性查找。
一般来说,类变量可使用全大写来命名

类方法和静态方法

前面的例子中定义的__init__等方法,这些方法本身都是类的属性,第一个参数必须是self,而self必须指向一个对象,也就是类实例化之后,由实例来调用这个方法

  • 普通函数
class Person:
    def n_method():
        print('n')
Person.n_method()
Person().n_method()        

在这里插入图片描述
在这里插入图片描述
Person.n_method()
可以放在类中定义,因为这个方法只是被Person这个名词空间管理的一个普通的方法,n_method是Person的一个属性而已
由于n_method在定义的时候没有指定self,所以不能完成实例对象的绑定,不能使用Person().n_method()调用

  • 类方法
class Person:
    @classmethod
    def class_method(cls):
        print(cls)
        cls.h=170
Person.class_method()
print(Person.__dict__)

在这里插入图片描述
1、在类定义中,使用@classmethod装饰器的方法
2、必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象本身
3、cls这个标识符可以是任意合法名称,但是为了易读,不要修改
4、通过cls可以直接操作类的属性

  • 静态方法
class Person:
    @classmethod
    def class_method(cls):
        print(cls)
        cls.h=170
    @staticmethod
    def static_method():
        print(Person.h)
Person.class_method()
Person.static_method('__doc__')
print(Person.__dict__)

在这里插入图片描述
静态方法
1、在类定义中,使用staticmethod装饰器修饰的方法
2、调用时,不会隐式的传入参数,静态方法,只是表明这个方法属于这个名词空间。函数归在一起,方便组织管理。

方法的调用
class Person:
    def method(self):
        print(self)
    @classmethod
    def class_method(cls):
        print(cls)
        cls.h=170
    @staticmethod
    def static_method():
        print(Person.h)

在这里插入图片描述
在这里插入图片描述
类几乎可以调用所有内部定义的方法,但是调用普通方法会出错,原因是第一参数必须是类的实例。实例也几乎可以调用所有的方法,普通的函数的调用一般不可能出现,因为不允许这么定义
总结:
类除了普通方法都可以调用,普通方法需要对象的实例作为第一参数。
实例可以调用所有类中定义的方法,普通方法传入实例自身、静态方法和类方法需要找到实例的类。

访问控制

私有属性

class Person:
    def __init__(self,name,age=18):
        self.name=name
        self.age=age
    def growup(self,i=1):
        if i>0 and i<150:
            self.age+=1
p1=Person('tom')
p1.growup(20)
p1.age=160
print(p1.age)

在这里插入图片描述
上例,本来是想通过方法控制属性,但是由于属性在外部可以访问,或者说可见,就可以直接绕过方法,直接修改这个属性。
Python提供了私有属性可以解决这个问题
私有属性:使用双下划线开头的属性名,就是私有属性

class Person:
    def __init__(self,name,age=18):
        self.name=name
        self.__age=age
    def growup(self,i=1):
        if i>0 and i<150:
            self.__age+=1
p1=Person('tom')
p1.growup(20)
print(p1.__age)

在这里插入图片描述
通过实验可以看出,外部已经访问不到__age了,age根本就没有定义,更是访问不到

class Person:
    def __init__(self,name,age=18):
        self.name=name
        self.__age=age
    def growup(self,i=1):
        if i>0 and i<150:
            self.__age+=1
    def getage(self):
        return self.__age
print(Person('tom').getage())

在这里插入图片描述

私有变量的本质
class Person:
    def __init__(self,name,age=18):
        self.name=name
        self.__age=age
    def growup(self,i=1):
        if i>0 and i<150:
            self.__age+=1
    def getage(self):
        return self.__age
p1=Person('tom')
p1.growup(20)
print(p1.getage())
print(p1.__dict__)

在这里插入图片描述
在这里插入图片描述
私有变量本质:
类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器会将其改名,转换名称为_类名_变量名的名称,所以用原来的名字访问不到了。

保护变量

在变量名前使用一个下划线,称为保护变量。

class Person:
    def __init__(self,name,age=18):
        self.name=name
        self._age=age
tom=Person('tom')
print(tom._age)
print(tom.__dict__)

在这里插入图片描述
可以看出,这个_age属性根本没有改变名称,和普通的属性一样,解释器不会做任何特殊处理。
这是开发者共同的约定,看见这种变量,就如同私有变量,不能直接使用。

私有方法
class Person:
    def __init__(self,name,age=18):
        self.name=name
        self._age=age
    def _getname(self):
        return self.name
    def __getage(self):
        return self._age
tom=Person('tom')
print(tom._getname())
print(tom.__dict__)
print(tom.__class__.__dict__)
print(tom._Person__getage())

在这里插入图片描述
在这里插入图片描述
私有变量的本质
单下划线的方法只有开发者之间的约定,解释器不做任何改变。
双下划线的方法,是私有方法,解释器会更名,改变策略和私有变量相同,_类名__变量名。
方法变量都在类的__dict__中可以找到

私有变量的总结

在Python中使用_单下划线或者__双下划线来标识一个成员被保护或者被私有化隐藏起来。
但是,不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员,Python中没有绝对的安全的成员保护或者私有成员。
因此,前导的下划线只是一种警告或者提醒,请遵守这个约定,除非真有必要,不要修改或者使用保护成员或者私有成员。

属性装饰器

一般好的设计:把实例的某些属性保护起来,不让外部直接访问,外部使用getter读取属性和setter方法设置属性。

class Person:
    def __init__(self,name,age=18):
        self.name=name
        self.__age=age
    def age(self):
        return self.__age
    def set__age(self,age):
        self.__age=age
tom=Person('Tom')
print(tom.age())
tom.set__age(20)
print(tom.age())

在这里插入图片描述

通过age和set_age方法操作属性
Python提供了属性proper装饰器

class Person:
    def __init__(self,name,age=18):
        self.name=name
        self.__age=age
    @property
    def age(self):
        return self.__age
    @age.setter
    def age(self,age):
        self.__age=age
    @age.deleter
    def age(self):
        print('del')
tom=Person('tom')
print(tom.age)
tom.age=20
print(tom.age)
del tom.age

在这里插入图片描述
特别注意:使用property装饰器装饰器的时候这三个方法同名
property装饰器:后面跟的是函数名就是以后的函数名。它就是getter。它就是getter。这个必须有,有了它至少是只读属性
setter装饰器:与属性名同名,且接受2个参数,第一个数self,第二个是将要赋值的值,有了它,属性科协
deleter装饰器:可以控制是否删除属性,很少用
property装饰器必须在前,setter、deleter装饰器在后
property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到一定隐藏效果
其它写法

class Person:
    def __init__(self,name,age=18):
        self.name=name
        self.__age=age
        
    def getage(self):
        return self.__age
    def setage(self,age):
        self.__age=age
    def delage(self):
        print('del')
    age=property(getage,setage,delage,'age property')
tom=Person('tom')
print(tom.age)
tom.age=28
print(tom.age)

在这里插入图片描述

对象的销毁

类中可以定义__del__方法,称为析构函数。
作用:销毁类的实例的时候调用,以释放占用的资源,其实就放些清理资源的代码。
注意这个方法不能引起对象的真正销毁,只是对象销毁的时候会自动调用它。
使用del语句删除实例,引用计数减1.当引用计数为0时,会自动调用__del__方法,由于Python是实现了垃圾回收机制,不能确定对象何时执行垃圾回收。

import time
class Person:
    def __init__(self,name,age=18):
        self.name=name
        self.__age=age
    def __del__(self):
        print(self.name)
def test():
    tom=Person('tom')
    tom.__del__()
    tom.__del__()
    tom.__del__()
    tom.__del__()
    print('===============')
    tom2=tom
    tom3=tom2
    print(1,'del')
    del tom
    print(2,'del')
    del tom2
    print('------------')
    del tom3
    print('==========')
test()

在这里插入图片描述
由于垃圾回收对象销毁时,才是真正的清理对象,还会在回收对象之前自动调用__del__方法,除非你明确知道自己的目的。

方法重载(overload)

其他面向对象的高级语言中,会有重载的概念
所谓的重载,就是同一个方法名,但是参数个数、类型不一样,就是同一个方法的重载。
Python没有重载,也不需要重载,Python中,方法定义中,形参非常灵活,不需要指定类型,参数个数也不确定,一个函数的定义可以实现很多种不同形式实参的调用。
####### 封装
面向对象的三要素之一。封装Encapsulation
封装
将数据和操作自知道类中,即属性和方法
将数据隐藏起来,给使用者提供操作(方法),使用者通过操作就可以获取或修改数据,getter和setter通过访问控制,暴露适当的数据和操作给用户,该隐藏的隐藏起来。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐