一、面向对象
1.内存中的对象
# 1
# 注意1:一般情况下,通过同一个类创建的对象,在内存中占用不同的地址
class Person():
pass
p1 = Person()
print(id(p1))
p2 = Person()
print(id(p2))
print(p1 is p2)
# 2.
# 注意2:不同对象的重名的属性,在内存中占用不同的空间,当一个对象的属性值发生修改,对其他对象的属性值没有影响
class Person():
__slots__ = ("name",)
def __init__(self,name):
self.name = name
p1 = Person('aaa')
print(p1.name)
p2 = Person('aaa')
print(p2.name)
# 思考问题:p1.name和p2.name是否是同一份内存地址?---->不是同一份内存空间
# print(p1.name is p2.name) # True
# print(id(p1.name) == id(p2.name)) # True
p1.name = 'tom'
print(p1.name,p2.name)
"""
总结:
如果两个对象的地址相同,则这两个对象的属性值一定相同
如果两个对象的属性值相同,这两个对象不一定是同一个对象
"""
2.析构函数【了解】
构造函数:当对象创建的时候自动调用的函数
和构造函数相反,当对象被销毁的时候自动调用的而调用,称为析构函数,函数名为__del__
"""
构造函数:创建对象的过程中自动调用的函数,包括__new__和__init__
析构函数:对象被销毁的时候自动调用的函数,__del__
"""
"""
【面试题】析构函数调用的时机
"""
class Person():
def __init__(self):
print("构造函数被调用了~~~~")
def __del__(self):
print("析构函数被调用了~~~~")
# 1.当对象被定义为全局变量,整个程序执行完毕的时候,对象会被自动销毁,会自动调用析构函数
# print("start")
# p = Person()
# print("over")
"""
start
构造函数被调用了~~~~
over
析构函数被调用了~~~~
"""
print("*" * 30)
# # 2.当对象被定义为局部变量【函数中】,当函数执行完毕,对象会被自动销毁,会自动调用析构函数
# def func():
# p = Person()
# print("start")
# func()
# print("over")
"""
start
构造函数被调用了~~~~
析构函数被调用了~~~~
over
"""
print("*" * 30)
# 3.del xx,当del执行完毕,对象会被立即销毁,,会自动调用析构函数
print("start")
p = Person()
del p
print("over")
"""
start
构造函数被调用了~~~~
析构函数被调用了~~~~
over
"""
# 析构函数的使用场景:很少用,但是可以用于程序的清理行为,如:关闭文件,关闭数据库等
3.类属性和实例属性
【面试题】类属性和实例属性之间的区别
a.定义的位置不同:类属性直接定义在类中,实例属性定义在构造函数中 或者 动态绑定
b.访问方式不同:类属性可以通过类名或者对象访问,实例属性只能通过对象访问
c.访问的优先级不同:当类属性和实例属性重名时,通过对象访问,优先访问的是实例属性
d.在内存中出现的时机不同:类属性优先于实例属性出现在内存中,类属性随着类的加载而出现,实例属性随着对象的创建才出现
e.使用场景不同:如果是所有对象共享的数据,则定义为类属性,如果是每个对象特有的数据,则定义为实例属性
class Person():
# 类属性【类的字段】
num = 66
test = "abc"
def __init__(self,name,age):
# 实例属性【对象属性、对象的字段】
self.name = name
self.age = age
self.test = "hello"
p = Person('aaa',10)
# 实例属性【对象属性、对象的字段】
p.score = 100
# 访问方式
# 实例属性:对象.属性
print(p.name,p.age,p.score)
# 类属性:对象.属性 或 类名.属性
print(p.num)
print(Person.num)
# 优先级
print(Person.test) # abc
print(p.test) # hello
del p.test
print(p.test) # abc
print("*" * 40)
p1 = Person('bbb',20)
print(p.num) # 66
print(p1.num) # 66
# 修改类属性
# 注意1:对象.属性 = 值,该语法都表示动态绑定属性,此处的属性表示实例属性
# p.num = 88
# print(Person.num) # 66
# 注意2:如果要修改类属性的值,则通过 类名.属性 = 值 修改
Person.num = 88
print(Person.num)
print(p.num) #
print(p1.num) #
# 使用场景不同:如果是所有对象共享的数据,则定义为类属性,如果是每个对象特有的数据,则定义为实例属性
class Student():
school = "千锋"
def __init__(self,name,score):
self.name = name
self.score = score
s1 = Student("张三",100)
s2 = Student("李四",88)
print(s1.school,s2.school)
Student.school = "万锋"
print(s1.school,s2.school)
s1.name = 'jack'
print(s2.name)
二、封装
1.概念
广义的封装:函数的定义和类的提取,都是封装的体现
狭义的封装:在面向对象编程中,一个类的某些属性,在使用的过程中,如果不希望被外界【直接】访问,就可以将该属性封装【将不希望被外界直接访问的属性私有化private,该属性只能被当前类持有,此时可以给外界暴露一个访问的函数即可】
封装的本质:就是属性私有化的过程
封装的好处:提高了数据的安全性,提高了数据的复用性
举例:插排,不需要关心属性在类的内部被做了什么样的操作,只需要关心可以将值传进去,也可以将值获取出来
2.私有属性
2.1基本使用
公开属性【public】:可以在类以外的任何地方直接访问的属性
私有属性【private】:只能在当前类的内部被直接访问的属性
# 1.属性未被私有化【公开属性】
# 特点:可以在类中及类以外的任何地方直接访问的属性
class Animal1():
def __init__(self,name,age):
self.name = name
self.age = age
# 在类的内部访问
def show(self):
print(f"姓名:{self.name},年龄:{self.age}")
a1 = Animal1('旺财',2)
# 在类的外面直接访问
# 获取值
print(a1.name,a1.age)
a1.show()
# 修改值
a1.name = "小白"
a1.age = 3
print(a1.name,a1.age)
a1.show()
print("*" * 50)
# 2.将属性私有化【私有属性】
# 特点:只能在当前类的内部被直接访问的属性
class Animal2():
__slots__ = ("__name","__age")
def __init__(self,name,age):
# 在Python中,在属性名的前面添加两个下划线,则该属性被称为私有属性
self.__name = name
self.__age = age
def show(self):
print(f"姓名:{self.__name},年龄:{self.__age}")
a2 = Animal2('旺财',2)
# 获取值
a2.show()
# print(a2.name,a2.age)
# print(a2.__name,a2.__age)
"""
工作原理:
一个属性一旦被私有化,会在内存中以另外一种形式存在,一般格式:_类名__属性名
可能在不同的版本下,不同的操作系统下,私有属性可能以不同的形式存在,无法访问
既然属性被私有化了,就不希望被外界访问
"""
# 不建议使用
# print(a2._Animal2__name)
# print(a2._Animal2__age)
print("*" * 30)
# 3.提供给外界访问的函数【少用较少】
# a.
class Animal3():
__slots__ = ("__name","__age")
def __init__(self,name,age):
self.__name = name
self.__age = age
def show(self):
print(f"姓名:{self.__name},年龄:{self.__age}")
# 获取值
def get_name(self):
return self.__name
# 修改值
def set_name(self,name):
self.__name = name
def get_age(self):
return self.__age
def set_age(self,age):
if age < 0:
age = -age
self.__age = age
a3 = Animal3('旺财',2)
print(a3.get_name()) # a3.name
a3.set_name("小白") # a3.name = "小白"
print(a3.get_name())
print(a3.get_age())
a3.set_age(-18)
print(a3.get_age())
print("*" * 30)
# b.在Python中,通过装饰器给类的外界提供访问的函数
# @property:将被装饰的函数可以当做属性使用,作用是获取值,格式:对象.函数名 表示获取该函数的返回值
# @xxx.setter:将被装饰的函数可以当做属性使用,作用是给属性赋值,格式:对象.函数名 = 值
class Animal3():
__slots__ = ("__name","__age")
def __init__(self,name,age):
self.__name = name
self.__age = age
def show(self):
print(f"姓名:{self.__name},年龄:{self.__age}")
# 被@property装饰的函数用于获取属性值,相当于get_age()
@property
def name(self):
return self.__name
# 被@xxx.setter装饰的函数用于给属性设置值,相当于set_age(),xxx表示被@property装饰的函数名
@name.setter
def name(self,name):
self.__name = name
@property
def age(self):
return self.__age
@age.setter
def age(self,age):
if age < 0:
age = -age
self.__age = age
a3 = Animal3('旺财',2)
print(a3.name) # 调用被@property装饰的函数,获取该函数的返回
a3.name = "花花" # 调用被@name.setter装饰的函数,给属性赋值
print(a3.name)
print(a3.age)
a3.age = 4
print(a3.age)
# 注意:@property可以单独使用,可以装饰任意函数,@xxx.setter不能单独使用
2.2不同形式的属性
"""
【面试题】解释下面不同形式的变量出现在类中的意义
a:普通属性,也被称为公开属性,在类的外面可以直接访问 ****
_a:在类的外面可以直接访问,但是不建议使用,容易和私有属性混淆
__a:私有属性,只能在类的内部被直接访问。类的外面可以通过暴露给外界的函数访问 *****
__a__:在类的外面可以直接访问,但是不建议使用,因为系统属性和魔术方法都是这种形式的命名,
如:__slots__ __init__ __new__ __del__,__name__,__add__,__sub__,__mul__等
"""
class Person():
def __init__(self,name,age,weight,score):
self.name = name
self._age = age
self.__weight = weight
self.__score__ = score
p = Person("小明",10,150,80)
print(p.name)
print(p._age)
print(p.__score__)
3.私有函数
# 1.
# class Person():
# # 公开函数
# def func1(self):
# print("func~~~111")
# p = Person()
# p.func1()
# 2.
class Person():
# 公开函数
def func1(self):
print("func~~~222")
self.__show()
# 私有函数
def __show(self):
print("showing")
p = Person()
p.func1()
# p.__show()
"""
注意:
a.如果一个函数需要被私有化,则可以在类中定义一个公开函数,在公开函数中调用私有函数即可
b.类中的实例函数之间相互调用,语法:self.函数名(形参)
c.如果封装的功能只希望在类的内部使用,类的外部不希望被访问
"""
封装的作用:
a.提高了数据的安全性
b.提高了数据的复用性