day18 面向对象

今日内容:

  1、面向对象编程

  2、类与对象

     属性查找

  3、封装

一、面向对象编程

面向对象编程:

  核心是对象二字,对象就是一个用来盛放数据与功能的容器

  基于该思想编写程序就是创造一个个的容器

  优点:扩展性强

  缺点:编程的复杂度提升  

在程序中,先定义类,后产生对象

 

二、类与对象

类与对象

  对象是"容器",用来存放数据与功能的

  类也是"容器",用来存放同类对象共有的数据与功能

 

我们可以总结出一个学生类,用来存放学生们相同的数据与功能

# 学生类
    相同的特征:
        学校=清华大学
    相同的功能:
        选课

 

定义类:

class Student: # 类的命名应该使用'驼峰体'
    school = '清华大学'  #数据

    def chose(self):    # 功能
        print('%s is chosing a curse' )
print(Student.__dict__)
# {'__module__': '__main__', 'school': '清华大学', 'chose': <function Student.chose at 0x0000022647CF90D0>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}

 

调用类的过程称为将类实例化,拿到的返回值就是程序中的对象,或称之为一个实例

stu1 = Student()
print(stu1)  # 每一次实例化Student类就得到一个学生对象
# <__main__.Student object at 0x0000027A0DBECFD0>

 

在实例化的过程中为学生定制各自独有的数据,姓名,性别,年龄

需要我们在类内部新增一个__init__方法

class Student:
    school = '清华大学'
    # 该方法会在对象产生之后自动执行,专门为对象进行初始化操作,可以有任意代码
    # 但是一定不能返回None的值
    def __init__(self,name,sex,age):
        self.name = name
        self.sex = sex
        self.age = age
    def choose(self):
        print('%s is choosing a course' %self.name)

 

然后我们重新实例化学生

stu1 = Student('王大力', '男', 28)
stu2 = Student('李建刚', '女', 18)
stu3 = Student('牛嗷嗷', '男', 38)
# 会产生对象的名称空间,用__dict__查看
print(stu1.__dict__)
print(stu2.__dict__)
print(stu3.__dict__)
# {'name': '王大力', 'sex': '男', 'age': 28}
# {'name': '李建刚', 'sex': '女', 'age': 18}
# {'name': '牛嗷嗷', 'sex': '男', 'age': 38}

 

 单拿stu1的产生过程来分析,调用类会先产生一个空对象stu1,然后将stu1连同调用类时括号内的参数一起传给Student.__init__(stu1,'王大力','男',28)

def __init__(self, name, sex, age):
    self.name = name  # stu1.name = '王大力'
    self.sex = sex    # stu1.sex = '男'
    self.age = age    # stu1.age = 28

 

会产生对象的名称空间,同样可以用__dict__查看

print(stu1.__dict__)
# {'name': '王大力', 'sex': '男', 'age': 28}

 

至此,我们造出来了三个对象与一个类,对象存放各自独有的数据,类中存放对象们共有到的内容

day18 面向对象

 

 

 

属性的查找

1、类属性与对象属性

在勒种定义的名字,都是类的属性,细说的话,类有两种属性:数据属性和函数属性,可以通过__dict__访问属性的值,比如Student.__dict__['school'],但是Python提供了专门的属性访问语法:

print(Student.school)   #  访问数据属性,等同于:Student.__dict__['school']
print(Student.__dict__['school'])
# 清华大学
# 清华大学
print(Student.choose)   #  访问函数属性,等同于:Student.__dict__['choose']
print(Student.__dict__['choose'])
# <function Student.choose at 0x000001BB76179310>
# <function Student.choose at 0x000001BB76179310>

2、操作对象的属性也是一样的

# 查看
print(stu1.name)  #  等同于stu1.__dict__['name']
# 王大力

# 新增
stu1.course = 'python' #  等同于 stu1.__dict__['course'] = 'python'

# 修改
stu1.age = 38  # 等同于stu1.__dict__['age'] = 38

# 删除
del stu1.course  # 等同于del stu1.__dict__['course']

 

3、属性查找顺序与绑定方法

对象的名称空间里只存放着对象独有的属性,而对象们相似的属性是存放于类中的,对象在访问属性时,会优先从对象本身的__dict__中查找,未找到的,则去类的__dict__中查找。

1、类中定义的变量是类的数据属性,是共享给所有对象用的,指向相同的内存地址

# 1、id都一样
print(id(Student.school))  # 2051291861136

print(id(stu1.school))  # 2051291861136
print(id(stu2.school))  # 2051291861136
print(id(stu3.school))  # 2051291861136

 

2、类中定义的函数时类的函数属性,类可以使用,但是必须遵循函数的参数规则,有几个参数需要传几个参数

Student.choose(stu1)
Student.choose(stu2)
Student.choose(stu3)
# 王大力 is choosing a course
# 李建刚 is choosing a course
# 牛嗷嗷 is choosing a course

 

但其实类中定义的函数主要是给对象使用的,内存地址都不一样

ps:id是python的实现机制,并不能真实的反映内存地址,如果有内存地址,还是以内存地址为准

print(id(stu1.choose))
print(id(stu2.choose))
print(id(stu3.choose))
# 2200856354304
# 2200856354368
# 2200856354304

 

绑定对象的方法特殊之处在于,绑定给谁就应该由谁来调用,谁来调用,就会将'谁'本身当做第一个参数自动传入。(方法__init__也是一样的道理)

stu1.choose()
stu2.choose()
stu3.choose()
# 等同于:
Student.choose(stu1)
Student.choose(stu2)
Student.choose(stu3)
# 王大力 is choosing a course
# 李建刚 is choosing a course
# 牛嗷嗷 is choosing a course

 

绑定到不同对象的choose技能,虽然都是选课,但是王大力选的课,不会选给李建刚,这正是"绑定"二字的精髓

注意:绑定到对象方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是命名为self是约定俗称的。

三、封装

一、引入

面向对象编程有三大特征:封装、继承、多态。

封装指的就是把数据与功能整合到一起。

针对封装到对象或类中的属性,我们还可以严格的控制对它们的访问,分两步实现:隐藏于开放接口

二、隐藏属性

Python的class机制采用双下划线的方式将隐藏属性隐藏起来(设置成私有的),但其实这仅仅只是一种变形操作,类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成 " _类名__属性名 " 的形式。

class Foo:
    __N = 0 # 变形为_Foo__N

    def __init__(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形
        self.__x = 10  # 变形为_Foo_x

    def __f1(self): # 变形为_Foo__f1
        print('__f1 run')

    def f2(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形
        self.__f1() # 变形为self._Foo__f1

print(Foo.__N) # 报错 # AttributeError: type object 'Foo' has no attribute '__N' obj = Foo() print(obj.__x) # 报错 # AttributeError: 'Foo' object has no attribute '__x'

 

这种变形需要注意的问题是:

1、在类外部无法直接访问双下滑线的属性,但是知道了类名和属性名就可以拼出名字:_类名__属性。然后就可以访问了,例如:Foo._A_N。

     所以说这种操作并没有严格意义上的限制外部访问,仅仅是语法意义上的变形。

2、在类内部是可以直接访问双下滑线开头的属性的,因为在类定义阶段内部双下划綫开头的属性同一发生了变形。

3、变形操作值在类定义阶段发生一次,在类定义之后的赋值操作,不会变形

 

三、开放接口

定义属性就是为了使用,所以隐藏并不是目的

3.1隐藏数据属性

将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接的操作数据,接口之上可以附加额外的逻辑来对数据的操作进行严格的控制。

class Teacher:
    def __init__(self, name, age):  # 将名字和年纪都隐藏起来
        self.__name = name
        self.__age = age

    def tell_info(self):  # 对外提供访问老师信息的接口
        print('姓名:%s,年龄:%s' % (self.__name, self.__age))

    def set_info(self, name, age):  # 对外提供设置老师信息的接口,并附加类型检查的逻辑
        if not isinstance(name, str):
            print('姓名必须是字符串类型')
            return
        if not isinstance(age, int):
            print('年龄必须是整型')
            return
        self.__name = name
        self.__age = age


t = Teacher('lili',18)
t.set_info('Lili','19')  # 年龄必须是整型
# 年龄必须是整型
t.tell_info() # 查看老师的信息
# 姓名:lili,年龄:18

3.2隐藏函数属性

目的是为了隔离复杂度,例如ATM程序的取款功能,该功能有好多其他功能组成,比如插卡、身份认证、输入金额、打印小票、取钱等,而对于使用者来说,只需要开发取款这个功能接口即可,取余功能我们都可以隐藏起来。

class ATM:
    def __card(self):  # 插卡
        print('插卡')

    def __auth(self):  # 身份认证
        print('用户认证')

    def __input(self):  # 输入金额
        print('输入取款金额')

    def __print_bill(self):  # 打印小票
        print('打印账单')

    def __take_money(self):  # 取钱
        print('取款')

    def withdraw(self):  # 取款功能
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()
obj = ATM()
obj.withdraw()

四、property

property:可以将类中的函数"伪装成"对象的数据属性,对象在访问该特殊属时会触发功能的执行,然后将返回值作为本次访问的结果。

class People:
    def __init__(self, name, weight, height):
        self.name = name
        self.weight = weight
        self.height = height

    @property
    def bmi(self):
        return self.weight / (self.height ** 2)


obj = People('lili', 75, 1.85)
print(obj.bmi ) # 触发方法bmi的执行,将obj自动传给self,执行后返回值作为本次引用的结果
# 21.913805697589478

 

使用property有效的保证了属性访问的一致性

class Foo:
    def __init__(self, val):
        self.__NAME = val  # 将属性隐藏起来
    @property
    def name(self):
        return self.__NAME
    @name.setter
    def name(self, value):
        if not isinstance(value, str):  # 在设定值之前进行类型检查

            print('%s must be str' %value)
            return
        self.__NAME = value  # 通过类型检查后,将值value存放到真实的位置self.__NAME
    @name.deleter
    def name(self):
        print('Can not delete')
f = Foo('lili')
f.name
# lili
f.name = 'LiLi'  # 触发name.setter装饰器对应的函数name(f,’Egon')
f.name = 123  # 触发name.setter对应的的函数name(f,123),抛出异常TypeError
del f.name  # 触发name.deleter对应的函数name(f),抛出异常PermissionError

 

上一篇:成绩排序


下一篇:python练习题11