Python 初学笔记 - 第四章-类

目录

面向过程

核心是过程,过程是解决问题的步骤,机械式的思维方式,不需要经常改动。

优点:复杂的问题流程化,进而简单化。
缺点:可扩展性差

面向对象

核心就是对象二字,对象就是特征与技能的结合体。

优点: 可扩展性强。
缺点: 编程复杂度高。
应用场景:用户需求经常变化,互联网应用,游戏,企业内部应用。

需要注意的是面向对象与面向过程是根据实际情况选择的,如果一般流程问题就能解决的问题使用面向对象无疑是将解决问题复杂化,造成过度设计的问题。

类就是一系列对象相似的特征与技能的结合体,在程序中,一定要先定义类,后再调用对象。
在生活中我们是将一个个的对象拥有相似的特征或者技能分为一类,但在程序中是先定义类,类就像是一个模板,不断的用模板生成对象。

类的定义

使用 class Name 定义一个类,name 是自定义的,使用驼峰命名法,这是编码规范。

# 定义一个Student的模板也就是类,这个模板属性有名字是ailisi,性别是male,年龄是18的特征,技能是eat和sleep。
class Student:
    name = 'ailisi'
    sex = 'male'
    age = 18

    def eat(self):
        print('我会吃饭')


    def sleep(self):
        print('我会睡觉')


print(Student)
<class '__main__.Student'>

类的名称空间

定义了一个函数,函数体并不会直接执行,只有在调用的时候才会执行并产生局部名称空间,但是类不一样,类在定义的时候就已经运行了,并产生了名称空间。

查看类的名称空间:classname.__dict__,会得到一个字典,里面是定义时候产生的名称空间。

class Student:
    name = 'ailisi'
    sex = 'male'
    age = 18
    
    def eat(self):
        print('我会吃饭')
    
    def sleep(self):
        print('我会睡觉')
    
    print('调用 Student')  # 会在一开始就会运行


print(Student.__dict__)
print(Student.__dict__['name'])  # 查看name的属性

调用 Student
{'__module__': '__main__', 'name': 'ailisi', 'sex': 'male', 'age': 18, 'eat': <function Student.eat at 0x02C554F8>, 'sleep': <function Student.sleep at 0x02C55858>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
ailisi

类属性调用

在类中的特征也就是定义的变量称之为数据属性,对象的技能也就是定义的函数称之为函数属性。

类属性的调用虽然可以向上面这样 Student.__dict__['name'] 调用,但是 Python 有专门的调用方法,使用 类.类属性 的方式调用,使用点表示类下的某某属性。

class Student:
    name = 'ailisi'
    sex = 'male'
    age = 18
    
    def eat(self):
        print('我会吃饭')
    
    def sleep(self):
        print('我会睡觉')
    

print(Student.name)  # 调用类下的name
print(Student.sex)   # 调用类下的sex
print(Student.age)   # 调用类下的age
ailisi
male
18

类属性操作

通过类的名称空间可以看到,类的名称空间是类属性的名称作为健的字典,当我们需要对类的属性增删改查的时候,其实就是是对这个字典进行操作。

在字典中是 dict[key] 进行查询,在类中 Student.key 进行属性调用。
在字典中是 dict[key] = value 增加或修改,在类中 Student.key = value 增加或修改。
在字典中是 dict.pop 或者 del dict[key] 删除,在类中 del Student.key 进行属性删除。

class Student:
    name = 'ailisi'
    sex = 'male'
    age = 18
    
    def eat(self):
        print('我会吃饭')
    
    def sleep(self):
        print('我会睡觉')
    

Student.name = 'xiaohong'  # name属性的修改操作
print(Student.name)
xiaohong

对象

上面已经说到,在程序中需要先有类才会有对象,因为对象是类实例化出来的,实例化就像是工厂使用模板(类)造出一个个的商品,每一个商品就是一个对象。

实例化对象

在函数中,执行函数是函数名加括号,但是在类中不是,类在定义的时候就执行了,类的实例化是类名加括号,与函数不同。
比如说 Student 实例化出 xiaohong,xiaoming 两个Student对象出来。

class Student:
    name = 'ailisi'
    sex = 'male'
    age = 18
    
    def eat(self):
        print('我会吃饭')
    
    def sleep(self):
        print('我会睡觉')


xiaohong = Student()  # 实例化
xiaoming = Student()  # 实例化
print(xiaohong)
print(xiaoming)
print(xiaohong.name)
print(xiaoming.name)
<__main__.Student object at 0x037CEE70>
<__main__.Student object at 0x037CEEB0>
ailisi
ailisi

# 可以看到实例化的对象拥有不同的内存地址, 也就证明了这是两个不同的对象 .

定制化对象

通过上面的实例化对象我们可以发现 xiaohong 与 xiaoming 虽然是两个不同的对象,但是他们的特征都相同,name=ailisi,sex=male,age=18,
xiaohong 的名字是小红才对,xiaoming 的名字是小明才行,而其他的特征也应该是不相同的,那如何修改不同的特征属性呢? 是通过一个一个对象
的属性进行修改吗? 如果生成很多对象怎么办?

这个时候就需要定制化对象,我们知道在通过函数传参的时候是 函数名(参数)的形式得到不同结果,在类中我们定制化对象的时候我们也可以通过
传递参数定制不同的对象。在类中定义一个 __init__() 的函数,这个函数只有在实例化对象的时候才会执行,而我们通过这个函数传递特征参数。

class Student:
    def __init__(self, name, sex, age):  # 实例化对象的时候执行,执行的时候需要接收self, name, sex, age参数
        self.name = name
        self.sex = sex
        self.age = age

    def eat(self):
        print('我会吃饭')

    def sleep(self):
        print('我会睡觉')


xiaohong = Student('小红', 'woman', '18')  # 定制不同的实例化对象
xiaoming = Student('小明', 'man', '19')    # 定制不同的实例化对象
print(xiaohong.name)
print(xiaoming.name)

print(xiaohong.__dict__)
print(xiaoming.__dict__)
小红
小明
{'name': '小红', 'sex': 'woman', 'age': '18'}
{'name': '小明', 'sex': 'man', 'age': '19'}

# 可以看到两个对象的特征是不同的

self

上面代码为什么会出现一个self,什么是self?为什么需要传递self?

在函数实例化过程是这样的例如 xiaohong = Student('小红', 'woman', '18')
1.比如说生成一个对象 xh 传递给xiaohong这个变量
2.然后再调用__init__函数,传递参数的时候self就是 xh 这个实例化产生的对象,其实一共传递了四个参数 (xh, '小红', 'woman', '18')

传递参数xiaohong = Student('小红', 'woman', '18') 实例化相当于不传递参数的:
xiaohong = Student() # 实例化对象赋值给xiaohong
xiaohong.name = '小红' # 修改xiaohong的name属性
xiaohong.sex = 'woman' # 修改xiaohong的sex属性
xiaohong.age = 18 # 修改xiaohong的age属性

传参后就像是对象批量属性生成的意思,把传递的属性绑定在刚刚生成的对象上,不需要我们自己一个一个属性的去修改。

class Student:
    
    def __init__(*args):  # 查看传入参数
        print(args)
    
    def eat(self):
        print('我会吃饭')
    
    def sleep(self):
        print('我会睡觉')


xiaohong = Student('小红', 'woman', '18')  # 定制不同的实例化对象
xiaoming = Student('小明', 'man', '19')  # 定制不同的实例化对象

print(xiaoming)
print(xiaohong)
(<__main__.Student object at 0x02CDEE70>, '小红', 'woman', '18')
(<__main__.Student object at 0x02CDEED0>, '小明', 'man', '19')
<__main__.Student object at 0x02CDEED0>
<__main__.Student object at 0x02CDEE70>

# 我们可以看到 __init__ 是默认需要实例化对象传入的,因为这样可以在实例过程中批量生成属性。

类中的数据属性

在类中的数据属性是所有实例化对象共有的,也就是说对象访问的都是同一个数据属性,这个属性存在类的名称空间里。

class Student:
    school = 'python'  # 数据属性

    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age

    def eat(self):
        print('我会吃饭')

    def sleep(self):
        print('我会睡觉')


xiaohong = Student('小红', 'woman', '18')
xiaoming = Student('小明', 'male', '19')
print(Student.__dict__, id(Student.school))
print(xiaohong.__dict__, id(xiaohong.school))
print(xiaoming.__dict__, id(xiaoming.school))
{'__module__': '__main__', 'school': 'python', '__init__': <function Student.__init__ at 0x0375C738>, 'eat': <function Student.eat at 0x037A54F8>, 'sleep': <function Student.sleep at 0x037A5810>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None} 21950368
{'name': '小红', 'sex': 'woman', 'age': '18'} 21950368
{'name': '小明', 'sex': 'male', 'age': '19'} 21950368

# 可以看到都是指向 21950368 这个内存地址。

类中的函数属性

类中的函数属性,是绑定给对象使用的,绑定到不同的对象是不同的绑定方法,对象调用绑定方法时,会把对象本身当作第一个传入,传给self。
也就是说不同对象使用的其实是一个内存地址的相同代码,但是不同对象使用的时候,对象会把自身传入给self,表示这个对象在调用,这样不同对象使用会得到不同的结果。

class Student:
    school = 'python'
    
    def __init__(self, name, sex, age):  # 函数属性
        self.name = name
        self.sex = sex
        self.age = age
    
    def eat(self):  # 函数属性
        print(self.name, '我会吃饭')  # 根据self传递不同的实例对象会得到不同的值
    
    def sleep(self):  # 函数属性
        print(self.name, '我会睡觉')


xiaohong = Student('小红', 'woman', '18')
xiaoming = Student('小明', 'male', '19')

print(xiaoming.eat)
print(xiaohong.eat)

print()
xiaohong.eat()  # 调用相同的方法得到不同的特定对象的结果
xiaoming.eat()

# Student.eat 方法绑定到各自不同的实例对象
<bound method Student.eat of <__main__.Student object at 0x034E6290>>
<bound method Student.eat of <__main__.Student object at 0x034BDF30>>

小红 我会吃饭
小明 我会吃饭

属性查找

我们知道函数在定义的时候就运行并生成名称空间,对象的实例化后也有对象的名称空间,当我们在调用属性的时候也像函数一样如果本地不存在就去上层找,
对象属性查找的顺序是:对象自身的名称空间 --> 类的名称空间 --> 父类的名称空间,注意并不会去全局里查找。

class Student:    
    school = 'python'
    
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age

    def eat(self):  # 函数属性
        print(self.name, '我会吃饭')

    def sleep(self):  # 函数属性
        print(self.name, '我会睡觉')


a = 'haha'
xiaohong = Student('小红', 'woman', '18')

xiaohong.h = 165  # 对象和类同时定义相同名称的属性
Student.h = 180

print(xiaohong.h)
print(xiaohong.a)  # 查看一个不存在但是全局存在相同名称的属性
165
# 可以看到根据优先级, 本地存在就不会向上查找

AttributeError: 'Student' object has no attribute 'a'
# 可以看到如果对象和类都不存在也不会在全局查找,直接报错

最后总结

  • 站的角度不同,定义出的类是不同的。
  • 现实中的类并不完全等于程序中的类。
  • 为了编程需求,程序也可以定义现实中不存在的类,比如策略类。

在 Python3 中一切皆对象, 在 Python3 中统一了类与类型的概念。

例如列表(list)类型,它其实也是一种类(列表类),在我们创建列表的时候可以这样 a = list([1,2,3]),对应类的概念,那 a = list() 就
是实例化对象,[1,2,3]传入是参数,a.append 就是使用对象的绑定方法,那除了列表还有字符串、元组、字典和 集合等等,每个定义的时候就是生成了一个个对象,所以一切皆对象。

# 我们可以这样
a = list([1,2,3,4])
list.append(a, 5)  # 绑定方法是给实例对象使用的,当类使用绑定方法时需要手动传入实例化对象。
print(a)
[1, 2, 3, 4, 5]
上一篇:列表的查


下一篇:mysql 获取单个科目的平均分