15 - 类和对象
一.类
类的概念
- 类是对一群具有相同属性或者方法的事物的一个统称,是抽象的,不能直接使用
- 类其实就是由 属性 和 方法 组成的一个抽象概念。
定义类
class 类名:
pass
class 类名(父类):
pass
二.对象
对象的概念
- 对象是由类创建出来的一个具体存在,可以直接使用。由哪一个类创建出来的 对象,就拥有在哪一个类中定义的属性和方法。 在开发中,应该先有类,在类里定义好属性和行为,再根据类来创建对象。
创建类的对象
- 对象变量名 = 类名()
三.属性
1.对象属性
- 对象属性又称为实例属性,记录对象各自的数据,不同对象的同名实例属性,记录的数据各自独立,互不干扰。而且实例属性可以不是所属类中的属性,可以自己定义赋值
2.类属性
- 类属性就是类对象所拥有的属性,它被该类的所有实例对象所共有,类属性可以通过类对象或者实例对象访问。
3.属性的调用和赋值修改
-
对象.属性或类.属性 -
对象.属性 = 值或类.属性 = 值(不推荐使用这种)
在日常开发中,不推荐在类的外部直接给对象添加属性这种方式。对象应该具有哪些属性,我们应该封装在类的内部。类中有什么属性对象就使用什么属性,不要在类外面自己新定义一个对象属性
4.私有属性
- 格式:在类属性前加
__(如__age) - 一般在类方法中使用体现封装性,再通过定义set、get方法对私有属性进行访问和修改
5.详细说明
-
尽量避免类属性和实例属性同名。如果有同名实例属性,实例对象会优先访问实例属性。
-
类属性只能通过类对象修改,不能通过实例对象修改
class Dog(object): type = "狗" # 类属性 # 创建对象 dog1 = Dog() dog1.type = "dog" # 使用 实例对象 创建了对象属性type print(dog1.type) # 结果为 “dog” 类属性和实例属性同名,访问的是实例属性 print(Dog.type) # 结果为 "狗" 访问类属性 # 只有使用类名才能修改类属性 Dog.type = "土狗" print(Dog.type) # 土狗 dog2 = Dog() print(dog2.type) # 土狗 -
类属性也可以设置为私有,前边添加两个下划线
class Dog(object): count = 0 # 公有的类属性 __type = "狗" # 私有的类属性 print(Dog.count) # 正确 print(Dog.__type) # 错误,私有属性,外部无法访问。
四.方法
- 种类:普通方法、类方法、静态方法、魔术方法
1.self和cls的使用
-
self
- 哪个对象调用了方法,方法里的
self指的就是谁,通过self.属性名可以访问到这个对象的属性;通过self.方法名()可以调用这个对象的方法。 - self是普通方法和魔术方法参数中自带的,一定有的!
- 哪个对象调用了方法,方法里的
-
cls
- 表示当前类,或者对象所属类
2.普通方法
-
格式:
def 方法名(self):或def 方法名(self,参数1,参数2...)后面跟的就是需要传入的参数,有几个创建对象时就要传入几个参数
对象名.方法名(参数1,参数2...)class Cat: def eat(self): #self是类方法中自带的 print("%s爱吃鱼" %self.name) tom = Cat() tom.name = 'Tom' # 给 tom 对象添加了一个name属性 tom.eat() # Tom爱吃鱼 -
只能通过对象调用
3.魔术方法
-
在某些触发条件下,自动执行的方法
-
__init__格式:def __init__(self):或者def __init__(self,参数1,参数2...)后面跟的就是需要传入的参数,有几个创建对象时就要传入几个参数
对象名 = 类名(参数1,参数2...) -
作用:如果在类的魔术方法中定义要创建的对象的属性并且初始化,那么创建这个类的所有的对象都有同一属性,而且都会被赋值,要修改时直接修改自己内存中加载的属性。不同于在类中定义类属性,类属性不会随着对象的创建就加载到对象内存中,而是对象需要使用类属性时会在类中找,需要修改类属性时会动态在对象空间中创建一个属性并且修改(不改变类属性)
4.类方法
-
需要用装饰器
@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数。class Dog(object): __type = "狗" # 类方法,用classmethod来进行修饰 @classmethod def get_type(cls): return cls.__type print(Dog.get_type()) -
使用场景:
- 当方法中 需要使用类对象 (如访问私有类属性等)时,定义类方法
- 类方法一般和类属性配合使用
-
注意:类方法和类属性随着类的加载就加载到内存了,所以类方法中不能操作对象属性和普通方法,只能操作类方法和类属性
5.静态方法
- 需要通过装饰器
@staticmethod来进行修饰,静态方法没有参数(形参没有self/cls)。 - 静态方法 也能够通过 实例对象 和 类对象 去访问。
class Dog(object):
type = "狗"
def __init__(self):
name = None
# 静态方法
@staticmethod
def introduce(): # 静态方法不会自动传递实例对象和类对象
print("犬科哺乳动物,属于食肉目..")
dog1 = Dog()
Dog.introduce() # 可以用 实例对象 来调用 静态方法
dog1.introduce() # 可以用 类对象 来调用 静态方法
使用场景:
- 当方法中 既不需要使用实例对象(如实例对象,实例属性),也不需要使用类对象 (如类属性、类方法、创建实例等)时,定义静态方法
- 取消不需要的参数传递,有利于 减少不必要的内存占用和性能消耗
- 静态方法随着类的加载而加载,所以只能访问类的属性和方法,对象属性和方法是无法被访问的
五.加载属性和方法的底层原理
加载属性
- 当类创建好后执行,会将类加载到内存中,然后通过
对象名 = 类()创建类的对象,在内存中新开辟一个对象的空间,对象名在栈中存储指向堆中这个开辟的地址,如果现在调用对象名.属性,会先在对象自己的内存空间中找有没有对象属性,如果没有再去找所属类中的属性,找到则会将类中的属性的值返回;没找到则会在对象空间中动态新创建一个对象属性 - 如果现在给对象属性赋值
对象名.属性 = 值,那么会动态的在对象的内存空间中创建一个对象属性并赋值,那么修改的就是对象自己空间中的对象属性,而不会修改类属性
加载方法
- 魔术方法
__init__的加载- 创建类的对象时
p = phone(),先去找Phone类的地址空间 - 利用Phone类,向内存中申请一块空间:0xab(假如是这个地址)
- 再去Phone的内存中间中找有没有
__init__即魔术方法(实际上先要找__new__,后面有讲解)- 如果没有则继续执行
p = phone()将开辟的内存地址给对象名p - 如果有则会进入init方法中执行里面的语句。执行完
__init__相关操作,再将内存地址赋给对象名p
- 如果没有则继续执行
- 如果有魔术方法且当中有属性初始化等操作
self.price = 4999,会动态的给self空间(是当前申请到的内存空间,因为执行__init__时还没有将内存空间赋值给对象p)添加price属性,并赋值
- 创建类的对象时
- 普通方法的加载
- 当使用
对象名.类方法()时,因为类方法是def 方法名(self):,所以会将对象名传给类方法参数self,然后类方法中如果使用了self.属性名,都会在对象的内存空间中去查找,如果没有找到会报错,找到的话操作的就是对象的属性。
- 当使用
class Phone:
phoneNum = 123
def __init__(self):
self.brand = '华为'
self.price = 4999
def call(self):
print('手机的的价钱为{}'.format(self.price))
p = Phone()
p.call() #手机的的价钱为4999
p1 = Phone()
p1.price = 3999
p1.call() #手机的的价钱为3999
p.phoneNum = 345
print(p.phoneNum) #345
-
类方法的加载
- 既可以用对象来调用也可以直接用类来调用类方法,因为类方法在类的加载时就已经加载到内存了。因为类方法上面加了@classmethod装饰器,所以该方法会将此对象属于的类名传给第一个参数cls,然后进行类属性或类方法进行相关操作。(普通方法和对象属性是无法在类方法中出现的,因为此时对象还没有创建,内存中没有;而类的属性和类方法随着类的加载就加载了)
-
静态方法的加载
- 也是随着类的加载就加载到内存了
六.更多的魔术方法
1.__init__
-
作用:初始化魔术方法,用来初始化对象的成员
-
触发时机:初始化对象时触发
初始化:在实例化后对对象进行初始化,即将地址赋给对象变量,对象名有了内存空间后才叫初始化
- 参数:至少有一个self
2.__new__
-
作用实例化魔术方法,用来向内存要空间 --> 获取地址
-
触发时机:实例化对象时
实例化:向内存要空间,即获取地址的过程
-
参数:至少一个cls,接收当前类
-
返回值:必须返回一个对象实例(地址)
-
注意:实例化对象时Object类底层实现,其他类继承了Object的
__new__方法才能够实现实例化对象。所以没事不要去用这个魔术方法,先触发__new__才会触发__init__
class Person:
def __init__(self, name):
self.name = name
def __new__(cls,*args,**kwargs):
position = object.__new__(cls) #为当前类即将创建的对象开辟地址,但是没有将地址赋给对象
return position
p = Person('zjj')
说明:程序先把类加载到内存中,然后
Person('zjj')先执行__new__()方法,为即将创建的对象p在空间中开辟一块地址,然后将地址放回。然后会自动将返回的地址值赋给__init__()方法中的self,将实参也赋给参数name,接着将name属性加载到这块地址空间中并且赋值,执行完__init()方法后,将地址值在赋给对象p变量
__init__和__new__总结
__new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供__new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类__new__出来的实例,或者直接是object的__new__出来的实例__init__有一个参数self,就是这个__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值
3.__call__
-
作用:想让对象当函数调用
-
触发时机:对象后面加括号,触发执行。
class Person:
def __call__(self,name):
print(name)
p = Person()
p() #此时会执行call魔术方法
4.__del__
-
作用:把对象的地址的引用
-
触发时机:当一段代码执行完成后触发,即当一块空间没有任何引用,默认执行
__del__。因为python解释器,会自动回收在本次执行过程中使用到的空间,实现内存释放 -- 垃圾回收机制
class Person:
def __init__(self, name):
self.name = name
def __del__(self):
print('***')
p = Person('zjj')
p1 = p
p2 = p
del p1 #这只是删除p1的引用
注意:不要自己用,不然会覆盖垃圾回收机制。
5.__str__
-
作用:如果想在打印对象名的时候反馈更多的文字信息,而不是地址的时候,就可以使用
-
触发时机:需要得到对象名的值时 自动触发去调用
__str__里面的内容 -
注意:一定要在
__str__方法中添加return,return后面的内容就是单独使用对象时得到的值
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return '哈哈'
p = Person('张三',18)
print(p) # 哈哈 打印对象时,会自动调用对象的 __str__ 方法。本来会打印p的地址值的
#情况2
class Student:
def eat(self):
print('{}吃!'.format(self))
def __str__(self):
return '学生'
s = Student()
s.eat() #学生吃
七.类类型变量
- 变量类型分为:系统提供的类型和自定义类型。
- 注意:如果用一个类创建出来一个对象,那么这个对象属于这个类类型变量,所以对象名就可以被当成变量使用,和其他类型变量没有区别。

浙公网安备 33010602011771号