Python+大数据-Python学习-面向对象(一)
1. 面向对象的思维方式
- 面向对象,是一个编程思想,并不是一项技术,重在理解
- 面向过程:一步一步的完成功能:自上而下,逐步细化
- 面向对象:找到或者构造一个可以完成功能的主体:找到实体,功能完备
2.类和对象
- 类就是一系列拥有相同或相似功能的对象的集合,或者说类就是一系列事物的统称
- 对象就是类的具体的表现形式
1、手机是对象还是类?
2、苹果手机,是对象还是类?
3、iPhonex 手机是对象还是类?
4、我手里的苹果手机,是对象还是类
3.类的定义
- 经典类
- class 类名:
- 新式类
- class 类名(父类名):
# 定义一个类:
'''
格式:
# 经典类
class 类名:
# 新式类
class 类名(父类名):
'''
# 经典类
# 不由任何类派生,或者说不继承任何类
class student:
pass # 为了保证代码结构完整,在类下边必须书写表达式,如果没有使用pass占位
# 新式类
# 括号内就是我们的父类,也就是存在一定的继承关系
# 有些地方称其为object的派生类
class teacher(object):
# pass
... # 为了保障代码结构完整,也可以使用...来进行占位
4. 类的实例化
- 类的实例化又叫做创建对象
- 类中实例化后的对象可以调用类中的方法
- 一个类理论上可以实例化无数个对象
- 格式: 类名()
# 类的实例化又称为创建对象
# 定义一个类
class Student(object):
# 定义方法.定义方式和函数定义类似
def study(self):
print('我在听直播课,贼有意思,就是学习非常不努力我也能听懂')
def eat(self):
print('我在吃脑白金,补补脑子继续学习')
# 类的实例化(创建对象)
# 格式: 类名()
s1 = Student()
# 我们可以直接打印对象,得到的是其所对应的类和所在的内存地址
print(s1) # <__main__.Student object at 0x7f9be20848e0>
# 也可以打印对象的类型,其实就是我们创建对象所使用的类
print(type(s1)) # <class '__main__.Student'>
# 实例可以调用实例方法
s1.study()
s1.eat()
# 理论上类可以创建无数个实例对象
s2 = Student()
print(s2)
# 类名的定义要使用大驼峰命名法
# 类名严格区分大小写,类名遵循标识符的命名规则
# class ChineseStudent():
# pass
#
# s3 = Student()
# s4 = student()
# s3.eat()
# s4.eat()
5. self
- self就是讲调用方法的实例传入方法内部,在方法内部可以调用实例的属性和方法
# 在类的内部定义方法的时候,自动传入一个self
# 在调用实例方法时,不需要对self 进行传值
# self到底是什么?有什么用?
class Student(object):
def study(self):
# 由于s1和self指向同一块内存空间,所以其必为同一个对象
# 也就是说在对象调用方法时会将对象本身传入方法内部进行使用
print(self) # <__main__.Student object at 0x7fa2654848e0>
print('我要学习了,谁也不要打扰我,我知道你们为了超过我不择手段,但是没有用')
def eat(self):
# self 有什么作用呢?
# 可以在方法内部调用实例所拥有的属性或者方法
print('我要吃饭了吃完就学习')
self.study()
# 实例化对象
s1 = Student()
print(s1) # <__main__.Student object at 0x7fa2654848e0>
s1.study()
s1.eat()
# 我们为什么要讲对象传入进去呢?
# 方法时定义在类的内部的,所有的对象共有一个类,所以我们再调用方法的时候,需要传入我们调用方法所使用的类
# s2 调用study方法时所指向的空间和s1无关所以两个对象指向不同的内存空间,修改一个,另一个不发生变化
s2 = Student()
s2.study()
6. 实例属性的添加和获取
- 在类的外部添加和获取实例属性
- 添加:对象名.属性名 = 值
- 获取:对象名.属性名
- 创建对象后,我们对其中一个对象添加实例属性,其他对象不发生变化
# 在类的外部可以添加或获取实例属性
# 格式:
# 实例属性添加:对象.属性名 = 值
# 实例属性获取:对象.属性名
# 定义类
class Person(object):
def eat(self):
print('早饭吃了油条和包子,血糖110')
# 实例化属性
p1 = Person()
# 给p1添加实例属性
p1.name = 'xiaoming'
# 调用实例属性
print(p1.name) # xiaoming
# 如果我们再创建一个对象,其实例属性name是否存在? 不存在
p2 = Person()
p2.age = 18
# AttributeError: 'Person' object has no attribute 'name'
# print(p2.name)
# print(p1.age)
# 结论:对象被创建后,添加实例属性,对其他的对象不产生影响
# 如果我们对同一个实例属性添加两次值会怎样?
p1.name = 'Rose'
print(p1.name)
# 对象名.属性名 = 值, 如果当前对象的该属性名存在,则重新赋值,如果不存在,则新建一个属性
# 类似于dict
# 扩展 __dict__
# 可以通过__dict__去查询对象的属性,该属性以字典形式保存
# 在计算机底层,对象的属性,保存在一个字典结构的空间内,多以多次赋值会覆盖原来的值,给新的属性赋值,会增加属性数量
print(p1.__dict__)
# 对象属性删除del(扩展)
del p1.name
# AttributeError: 'Person' object has no attribute 'name'
print(p1.name)
在类的内部添加和获取实例属性
- 添加:self.属性名 = 值
- 获取:self.属性名
- 一般实例属性写在实例方法中,调用该方法才能获取实例属性,对象创建后,其中一个实例调用该方法,获取实例属性,其余对象不发生变化
# 实例属性在类的内部添加或获取的格式
# self.属性名 = 值
class Person(object):
def add_attr(self):
self.name = 'xiaoming'
self.age = 18
self.gender = '女'
# 实例化对象
p1 = Person()
# 创建完成后,p1的实例属性添加了么?
# AttributeError: 'Person' object has no attribute 'name'\
# print(p1.name, p1.age, p1.gender)
# 为什么没有属性呢? 我们定义的添加实例属性的方法没有被调用
# 所以需要先调用添加实例属性的方法,才能使用实例属性
p1.add_attr()
print(p1.name, p1.age, p1.gender) # xiaoming 18 女
p2 = Person()
# AttributeError: 'Person' object has no attribute 'name'
# 哪怕是在类的内部添加实例属性,两个对象之间没有任何关系,也就是执行方法在第一个对象中添加了实例属性,对第二个对象不产生影响,如果想要添加实例属性,需要再次调用方法
# print(p2.name)
# 在类的外部可以修改类内部添加的实例属性么? 可以
p1.name = 'Rose'
print(p1.name) # Rose
# 同一个对象在类的内部和外部添加实例属性 本质上是一样的
# 在类的外部使用对象名,其实使用的是对象的引用地址,在其引用地址位置添加了对应的实例属性
# 在类的内部使用self,其实也代表该应用地址,也是在其应用地址位置添加了对饮的实例属性
# 为什么在类的内部要使用self 而不使用对象名? 简便,灵活.复用性高
# 1.我们每次使用的对象不一致,如果使用对象名,需要每次都传入不同的对象名,或者每个对象定义一个方法,这样不利于代码的复用.
# 2.在某些时刻,我们在没有将对象赋值给变量的时候,就需要添加其属性,这个时候,没有办法获取对象的名称.
7. 魔术方法
7.1 __init__()
方法
__init__()
方法在对象创建完成后,初始化对象时,自动调用- 在init方法中添加的属性,由于每个对象都会执行该方法,所以都包含该属性,被称之为共有属性
- 在init方法之外添加的属性,由于不是每个对象都拥有,所以被称之为独有属性
# __init__():在对象创建完成后,初始化对象过程中自动调用的方法
# class Person(object):
# def __init__(self):
# print('我被调用了')
# init方法在什么时候被调用
# p1 = Person() # 只需要实例化对象,不需要手动调用,init方法即可自动调用
# 在类的内部和外部都可以轻松调用init方法,但是不要调用
# p1.__init__()
# 既然init方法可以在对象被创建后自动调用,那我们讲添加实例属性的代码写入init方法中,是不是可以每个对象被创建后,都自动添加实例属性呢?
class Person(object):
def __init__(self):
# 由于init方法在对象被赋值之前就已经调用了,多以此时不能使用对象名.属性名进行属性添加,只能使用self
self.name = 'xiaoming'
self.age = 18
p1 = Person()
# 由于其自动调用init,所以对象被创建后,自动拥有name和age属性
print(p1.name) # xiaoming
# 创建多个对象,每个对象都包含init中的属性名
p2 = Person()
print(p2.age) # 18
# 结论: 在init方法中添加的属性,每一个由该类创建的对象,都包含,这种属性,我们称之为公有属性
# 在init之外添加的属性,只有被添加属性的对象本身才拥有,这种属性被称为独有属性
7.2 带参数的__init__()
方法
- nit方法在对象被创建时,可以将“类名()”这里边括号添加的参数传递到init方法内部
- 在接收到参数时,可以动态给对象添加实例属性
- 如果init方法添加了参数,那么在创建对象时,必须给其赋值,否则报错
# 每次我们创建对象时,如果使用init方法,是不是只能添加同一个值的属性呢?
# 如果我们能够将参数传递到init方法中,是不是就可以在创建对象时,动态添加属性值了呢?
# 我们怎样给init进行传参呢?
# 面临的问题: 1.我们不需要手动调用init 在哪里给他传参呢? 2.我们传参时到底传什么参数给init方法呢?
# 在实例化对象时,类名(参数1, 参数2....)这些参数会传递给init方法,进行使用
# class Person(object):
# def __init__(self, name, age):
# print(name, age)
# TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
# p1 = Person()
# 既然我们给init方法中添加了参数,就必须传值,否则就会报错
# 在Person的括号中传参,就可以传递到init方法中,传参的数量,就是init方法中除了self之外的位置参数的数量
# p1 = Person('Jack', 18) # Jack 18
# 结论: 在Person类创建对象时,在()内添加参数,可以被init接收但是,传参数量和inti方法中的形参必须一致
# 怎样实现动态的实例属性添加呢?
class Person(object):
def __init__(self, name, age):
# self.属性名 = 参数 将函数外部传递进来的参数赋值给对象,创建实例属性
self.name = name
self.age = age
# 实例化对象时要正确传参
p1 = Person('Rose', 17)
print(p1.name, p1.age)
# 创建第二个对象,查看属性是否动态传递成功
p2 = Person('Jack', 18)
print(p2.name, p2.age)
# 通过这种方式,我们在创建对象时可以指定其不同对象属性的值不同,但是所有的对象包含的属性类别相同
# 这种形式下一定要给每一个对象单独赋值,或者给init方法中的属性一些默认值,否则会报错
7.3__str__()
方法
- 在类的内部实现
__str__()
方法,他会在我们将对象转换为str类型时自动调用,返回其return内的数据 - str方法内只能返回str类型的数据
- str方法自动调用的场景
- 强制类型转换: str(对象)
- 隐式类型转换: %s作为占位符接收对象,或者 print打印等,都会自动调用
# __str__()方法是在数据被转换为str类型时自动调用的方法
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
# 在__str__方法中只能返回字符串类型数据,否则就会报错
# TypeError: __str__ returned non-string (type int)
# return 123
# return None
return f'我的名字是{self.name}, 我的年龄是{self.age}'
p1 = Person('Rose', 18)
# 如果我们打印p1会在控制台输出什么? # <__main__.Person object at 0x7fb70db848e0>
# 默认会输出对象类型,和内存地址
# print(p1)
# 我们如果让其在打印时输出我们想要输出的内容? 重写str方法
# 重写str方法后
# 结论:打印p1时,会自动调用__str__()方法
print(p1)
# 是因为print方法我们才将p1变为我们改写的str方法中的内容么? 不是
# 其实我们再执行print时,会做一次隐式的数据类型转换 也就是使用str(对象)
str1 = str(p1)
print(str1)
# 在什么场景下会自动调用__str__呢?
# 1.强制类型转换 str(对象)
# 2.隐式类型转换 %s 作为占位符,接收p1 print打印
7.4 __del__()
方法
- 对象被释放前,自动执行
__del__()
方法 - 释放对象的几个场景
- 出了函数作用域后,局部变量被释放
- 程序执行完成后,所有变量被释放
- 执行del操作后,可以提前释放变量
# 之前我们学过del操作
# del 变量名 或者 del (变量名)
# del操作 可以切断数据和引用位置的联系
# 切断引用后,a 没有引用任何数据,1也没有任何变量引用,所以双双被释放掉
# a = 1
# del a
# print(a)
# __del__()方法,在c语言中成为析构函数
# 在对象被释放前自动执行该方法,执行后,对象立即被释放
# 定义类
# class Person(object):
# def __init__(self,name, age):
# self.name = name
# self.age = age
# p1 = Person('Rose', 18)
# del p1
# NameError: name 'p1' is not defined
# 在这种情况下,我们能否知道p1已经被释放了? 没有提示
# 如果已经被释放了还继续使用,是不是会报错? 会报错
# 我么你怎样去进行提示? 使用__del__()
# print(p1)
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __del__(self):
print('我被释放了,真爽',self.name)
p1 = Person('Rose', 18)
# del p1 # 使用del造成p1被提前释放,在程序结束前将对象释放了
# p1被释放后,我们就接收到了提示,证明p1不存在了,之后就不要使用了
# print(p1)
# 如果没有del操作,则在程序结束后,会将所有的变量进行统一释放
# print('程序结束')
# 结论:在对象被释放时,会自动调用__del__方法,并且,使用del操作可以提前释放对象,否则在程序结束后,也会将变量统一释放
# 如果一个对象,或者说同一块内存空间,被多个变量引用,使用del可以释放么?
# p2 = p1 # p1和p2指向同一内存空间,或者说两个变量引用同一个数据
# del p1
# # 如果只删除p1的引用,对象还被p2引用着,该对象不会被释放,必须切断所有引用,才能正常 释放
# del p2
# # 如果将p2的引用也切断了,则对象正常释放
# print('程序结束')
# 结论:对象被引用时无法释放,除非程序终止,如果一个对象被多个变量引用,必须将所有引用切断才能正常释放,否则无法释放对象
# 举例:多个主人牵一条狗,如果有一个主人没有撒手,狗也跑不了
p4 = None
def func():
p3 = Person('xiaoming', 15)
global p4
p4 = p3
print(p3)
func()
# 上述代码可以推断,在函数执行完成后,出了作用域,会将函数内所有的临时变量释放掉,除非其被外部变量引用
print('程序结束')
# 切断引用或释放对象的几个场景
# 1.出了函数作用域会自动释放函数内的局部变量
# 2.程序结束会自动释放所有的变量
# 3.使用del操作可以提前释放变量