15 - 类和对象

一.类

类的概念

  • 类是对一群具有相同属性或者方法的事物的一个统称,是抽象的,不能直接使用
    • 类其实就是由 属性方法 组成的一个抽象概念。

定义类

class 类名:
	pass
	
class 类名(父类):
	pass

二.对象

对象的概念

  • 对象是由类创建出来的一个具体存在,可以直接使用。由哪一个类创建出来的 对象,就拥有在哪一个类中定义的属性和方法。 在开发中,应该先有类,在类里定义好属性和行为,再根据类来创建对象。

创建类的对象

  • 对象变量名 = 类名()

三.属性

1.对象属性

  • 对象属性又称为实例属性,记录对象各自的数据,不同对象的同名实例属性,记录的数据各自独立,互不干扰。而且实例属性可以不是所属类中的属性,可以自己定义赋值

2.类属性

  • 类属性就是类对象所拥有的属性,它被该类的所有实例对象所共有,类属性可以通过类对象或者实例对象访问。

3.属性的调用和赋值修改

  • 对象.属性类.属性

  • 对象.属性 = 值类.属性 = 值 (不推荐使用这种)

在日常开发中,不推荐在类的外部直接给对象添加属性这种方式。对象应该具有哪些属性,我们应该封装在类的内部。类中有什么属性对象就使用什么属性,不要在类外面自己新定义一个对象属性

4.私有属性

  • 格式:在类属性前加__ (如__age
  • 一般在类方法中使用体现封装性,再通过定义set、get方法对私有属性进行访问和修改

5.详细说明

  1. 尽量避免类属性和实例属性同名。如果有同名实例属性,实例对象会优先访问实例属性

  2. 类属性只能通过类对象修改,不能通过实例对象修改

    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)  # 土狗
    
  3. 类属性也可以设置为私有,前边添加两个下划线

    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__的加载
    1. 创建类的对象时p = phone(),先去找Phone类的地址空间
    2. 利用Phone类,向内存中申请一块空间:0xab(假如是这个地址)
    3. 再去Phone的内存中间中找有没有__init__即魔术方法(实际上先要找__new__,后面有讲解)
      • 如果没有则继续执行p = phone()将开辟的内存地址给对象名p
      • 如果有则会进入init方法中执行里面的语句。执行完__init__相关操作,再将内存地址赋给对象名p
    4. 如果有魔术方法且当中有属性初始化等操作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__不需要返回值

涉及到继承时的使用说明详见面向对象三大特征.md中的super关键字及__init____new__的说明


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()  #学生吃

七.类类型变量

  • 变量类型分为:系统提供的类型和自定义类型。
  • 注意:如果用一个类创建出来一个对象,那么这个对象属于这个类类型变量,所以对象名就可以被当成变量使用,和其他类型变量没有区别。
posted @ 2021-10-14 19:19  深藏Bluee  阅读(98)  评论(0)    收藏  举报