在python中万物皆对象

33.面向对象

1.类
  1. 类的定义:通常在Python中的__init__方法中初始化实例对象的属性,这个方法是一个初始化方法。而使用python默认提供的构造方法__new_
    1. 定义一个类示例如下:
    #定义一个类
    class Student:
        native_place = '吉林'   #类里的变量称为类属性
        def __init__(self,name,age):
            self.name = name    #name为实例属性
            self.age = age      #age为实例属性
        #实例方法
        def instanceFunc(self):
            print('这是实例方法')
        #静态方法
        @staticmethod
        def staticFunc():
            print('这是静态方法')
        #类方法
        @classmethod
        def classFunc(cls):
            print('这是类方法')
    
    
    1. 注意:实例方法的第一个参数默认是self,即实例对象的引用;类方法的第一个参数默认是cls,即类对象的引用。类对象和实例对象之间的关系?参见3
    class Student(object):
        def instanceMethod(self):
            # 实例方法: <__main__.Student object at 0x00000123CCD1F6A0>
            print('实例方法:',self)
        # 类方法
        @classmethod
        def classMethod(cls):
            # 类方法: <class '__main__.Student'> 1253265361152
            print('类方法:',cls,id(cls))
        # 静态方法
        @staticmethod
        def staticMethod():
            pass
    
    s = Student()
    # 实例对象的引用: <__main__.Student object at 0x00000123CCD1F6A0>
    print('实例对象的引用:',s)
    s.instanceMethod()
    
    # 类对象的引用: <class '__main__.Student'> 1253265361152
    print('类对象的引用:',Student,id(Student))
    s.classMethod()
    
    
    # 三种方法存储在类对象的__dict__属性中,没有在实例当中
    print(s.__dict__)   #{}
    print(Student.__dict__)
    
    
  2. 类也是对象,属于type类型的
    1. 可以将类赋值给一个变量
    2. 可以为它增加属性
    3. 可以作为实参进行参数的传递
    # Student对象由type类创建
    class Student(object):
        pass
    
    var = Student
    # <class '__main__.Student'> <class 'type'>
    print(var, type(var))
    
    instanceObj = var()
    # <__main__.Student object at 0x000001D756C5EA70> <class '__main__.Student'>
    print(instanceObj, type(instanceObj))
    
  3. 元类的概念:创建类对象的类(即type类) ,它继承自object类。
    1. type是一个用来创建类对象的类,使用type类创建类对象示例如下:
    # 创建一个类对象
    # 第一个参数指定对象名,第二个参数指定基类,第三个参数指定由属性构成的字典
    Student = type('Student', (), {})
    print(Student) # <class '__main__.Student'>
    # 创建该类的实例
    print(Student())# <__main__.Student object at 0x00000242A2B6EA30>
    
    # 使用class关键字定义类,python内部就实现了创建Student这个类对象
    class Student:
        pass
    print(Student) # <class '__main__.Student'>
    
    1. __class__属性:这个属性用于获取对象所属的类型,有了这个属性,可以实现在实例方法中访问类属性
    class Student:
    native_school_name = 'ZTU'
    def getName(self):
        return __class__.native_school_name
    
    stu = Student()
    print(stu.getName()) # ZTU
    
    1. __metaclass__属性:指定用来创建当前类对象(类也是一个对象)的元类,一般在自定义类中作为属性。
    class MMetaClass(type):
        def __new__(cls, name, bases, attr):
            print("这是一个构造方法")
            return super().__new__(cls, name, bases, attr)
    class Student(metaclass=MMetaClass):
        pass
    
    1. 总结,如下图所示
    a = 1024
    print(a.__class__)              # <class 'int'>
    print(a.__class__.__class__)    # <class 'type'>
    
    
    str = 'test'
    print(str.__class__)            # <class 'str'>
    print(str.__class__.__class__)  # <class 'type'>
    
    class Student:
        pass
    
    obj = Student()
    print(obj.__class__)            # <class '__main__.Student'>
    print(obj.__class__.__class__)  # <class 'type'>
    
  4. 类的创建流程:
    1. 检测类中是否明确__metaclass__属性.
    2. 检测父类中是否存在__mataclass__属性
    3. 检测模块中是否存在__metaclass__属性
    4. 通过内置的type这个元类来创建类对象
  5. 实例对象的创建
#实例对象可以调用实例方法,类方法,静态方法
stu = Student('Jack',20)
stu.staticFunc()
stu.classFunc()
#下面两种调用方式结果一致
stu.instanceFunc()      #这是实例方法
# 类对象理解为创建实例对象的模板
Student.instanceFunc(stu)#这是实例方法
print(stu.name)
print(stu.age)

#Student为类对象,和stu实例对象不一样。访问类属性
print(Student.native_place) #吉林
#调用类方法
Student.classFunc()         #这是类方法
#调用静态方法
Student.staticFunc()        #这是静态方法

# 类的创建方式
# 1.通过元类来创建类对象
def eat(self):
    print(self)

dog = type('Dog',(),{'weig':23,'eat':eat})
# 变量dog引用着类对象
print(dog)  #<class '__main__.Dog'>
#创建实例对象
d = dog()
print(d)    #<__main__.Dog object at 0x0000017CFE1F5BE0>
d.eat()     #<__main__.Dog object at 0x0000017CFE1F5BE0>
  1. 动态绑定实例对象的属性和方法
class Student:
    #实例方法
    def instanceFunc(self):
        print("这是实例方法")
    def __init__(self,name,age):
        self.name = name
        self.age = age
#定义一个函数
def func():
    print('这是类外定义的函数')
stu1 = Student('Jack',20)
#给实例对象动态绑定属性
stu1.gender = '男'
print(stu1.name,stu1.age,stu1.gender)   #Jack 20 男
# 删除实例属性
del stu1.gender
#给实例对象动态绑定方法
stu1.func = func
stu1.func()         #这是类外定义的函数
  1. 增删改查类对象的属性和方法:
class Student(object):
    # 类属性
    id = '学生'
    def __init__(self):
        pass
class Teacher(object):
    id = '老师'

if __name__ == '__main__':
    # 变量s引用着一个实例对象
    s = Student()

    # Student这个变量引用着类对象
    # 增加类属性
    Student.addr = 'xxx'
    s.__class__.inter = 'study'

    # 访问类属性
    print(Student.addr) #xxx
    print(Student.inter)#study

    #删除类属性
    # del Student.id
    #del s.__class__.id
    print(Student.id)   #AttributeError

    # 还可以通过变量s访问类属性,实例对象中的__class__对象指向类对象
    print(s.addr)       #xxx

    print(type(s.__class__),id(s.__class__))
    print(type(s.__class__),id(Student))
    # <class 'type'> 1672459155488
    # <class 'type'> 1672459155488

    # 更改变量s引用的实例对象的__class__引用的值
    s.__class__ = Teacher
    print(s.id) #老师

    # 类属性被各个实例对象共享
    one = Student()
    two = Student()
    print(one.id)   #学生
    print(two.id)   #学生

    Student.id = '老师'
    print(one.id)   #老师
    print(two.id)   #老师

    # 限制添加类属性
    class Student:
        # 限制只能再添加两个实例对象属性name,age
        __slots__ = ['name','age']

    s = Student()
    s.name = 'Jack'
    s.age = 23
    s.id = 2021 #AttributeError

  1. 设置只读的实例属性
class Student(object):
    def __init__(self):
        self.__age = 18

    # @property的作用是可以以使用属性的方式,来使用这个方法
    # 方法名设置为age的目的是防止在类的外部新创建age这个实例属性
    @property
    def age(self):
        return self.__age

s = Student()
print(s.age)    # 18
s.age = 23 # AttributeError: can't set attribute


class Student:
    # 私有的实例属性带有两个下划线
    __addr = "xxx"
    # 公有的类属性则不带有两个下划线
    name = "zs"
    def info(self):
        return self.__addr
stu = Student()
# AttributeError: 'Student' object has no attribute '__addr'
print(stu.__addr)
print(stu.info())
print(stu.name)
2.面向对象的三大特征

面向对象的三大特征:封装,继承,多态

  1. 封装:在python中没有专门的修饰符用于属性私有,如果该属性不希望在类外部被访问,前边使用两个下划线;也没有专门的修饰符用于属性保护,希望受到保护的属性使用一个下划线;不加一个下划线表示公有属性
class Student:
    def __init__(self,name,age):
        self.name = name
        self.set_age(age)
    def set_age(self,age):
        self.__age = age
    def get_age(self):
        return self.__age
stu = Student('Jack',23)
# name属性不带下划线,表示共有属性
print(stu.name,stu.get_age())   #Jack 23
print(dir(stu))
print(stu._Student__age)        #23,可以通过这种方式获取不希望再类外直接访问的属性__age
#伪私有,将私有属性重命名为‘__类名__属性名’(名字重整)
  1. 继承:
    1. 语法格式如下
    class 子类名(父类1,父类2,...):
        pass
    
    1. 注:Python中支持多继承
    #如果一个类没有继承任何类,则默认继承object类
    class Person(object):
        def __init__(self,name,age):
            self.name = name
            self.age = age
        def info(self):
            print(self.name,self.age)
    class Student(Person):
        def __init__(self,name,age,stu_num):
            #定义子类时,必须在其初始化函数中调用父类的初始化函数
            super().__init__(name,age)
            self.stu_num = stu_num
            
    stu = Student('Jack',23,20170201818)
    stu.info()  #Jack 23
    
  2. 多态
class Animal(object):
    def eat(self):
        print('Animal base calss')
class Dog(Animal):
    #重写父类中的方法
    def eat(self):
        print('Dog吃骨头')
class Cat(Animal):
    def eat(self):
        print('Cat吃鱼')
class Person(object):
    def eat(self):
        print('人吃饭')

def func(obj):
    obj.eat()

func(Animal())  #Animal base calss
func(Dog())     #Dog吃骨头
func(Cat())     #Cat吃鱼
func(Person())  #人吃饭
  1. 方法重写
class Person(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def info(self):
        print('姓名为:{0},年龄为:{1}'.format(self.name,self.age))

class Student(Person):
    def __init__(self,name,age,stu_num):
        super().__init__(name,age)
        self.stu_num = stu_num
    #对继承自父类中的方法进行重写
    def info(self):
        super().info()
        print('学号为:{0}'.format(self.stu_num))

stu = Student('Jack',23,20170201818)
#姓名为:Jack,年龄为:23
#学号为:20170201818
stu.info()
  1. 内置函数dir()可以查看指定对象的所有属性。object类是所有类的父类,它的_str_()方法用于返回对一个对象的描述,相当于Java中的toString方法,可以重写该方法。
print(stu.__str__())    #<__main__.Student object at 0x0000027FE5CCFFA0>
print(Student.__str__(Student)) #<class '__main__.Student'>
print(dir(stu))
  1. 类的特殊属性和方法
    1. 特殊属性:
      1. __dict__:获取类对象或者实例对象的所有属性和方法的字典
      class A:
          pass
      class B:
          pass
      class C(B,A):
          def __init__(self,name):
              self.name = name
      c = C('张三')
      print(c.__dict__)   #实例对象的属性字典,{'name': '张三'}
      print(A.__dict__)
      print(C.__mro__)    #类的层次结构
      
    2. 特殊方法:
      1. __new__:用于创建对象
      2. __init__:对创建的对象进行初始化
      3. __del__()方法:当对象被删除时会自动被调用,相当于C++中的析构函数
  2. 深拷贝和浅拷贝
    1. 对于深copy来说,列表是在内存中重新创建的,列表中可变的数据类型是重新创建的,列表中的不可变的数据类型是公用的。
    2. 对于浅copy来说,只是在内存中重新创建了一个空间存放一个新列表,但是新列表中的元素与原列表中的元素是公用的。
    #浅拷贝示例(只拷贝第一层)
    lst1 = [[1,3],'hello','world']
    lst2 = lst1.copy()
    #3013312822528 3013310103344 3013311459952
    print(id(lst1[0]),id(lst1[1]),id(lst1[2]))
    #3013312822528 3013310103344 3013311459952
    print(id(lst2[0]),id(lst2[1]),id(lst2[2]))
    
    #对lst2中可变序列的修改影响到了lst1
    lst2[0][1] = 100
    print(lst1) #[[1, 100], 'hello', 'world']
    print(lst2) #[[1, 100], 'hello', 'world']
    
    #深拷贝示例(克隆)
    import copy
    lst1 = [[1,3],'hello','world']
    lst2 = copy.deepcopy(lst1)
    #2743982477760 2743981360112 2743981109936
    print(id(lst1[0]),id(lst1[1]),id(lst1[2]))
    #2743982477376 2743981360112 2743981109936
    print(id(lst2[0]),id(lst2[1]),id(lst2[2]))
    
    #对lst2中可变序列(如列表)的修改不会影响lst1
    lst2[0][1] = 100
    print(lst1) #[[1, 3], 'hello', 'world']
    print(lst2) #[[1, 100], 'hello', 'world']
    
    lst2[2] = "hi"
    print(lst1) #[[1, 3], 'hello', 'world']
    print(lst2) #[[1, 100], 'hello', 'hi']