Python中的类和对象
1.定义类python3中的根类有且只有一个object小写⚠️大写的Object不存在于python内置中是自己定义的为防止混淆不建议使用__bases__是python中一个内置的变量代表基类# 隐式继承python3自动处理 class Cat: pass # 显式继承 class Dog(object): pass if __name__ __main__: print( Cat.__bases__ ) # (class object,) print( Dog.__bases__ ) # (class object,)2.类的成员2.1 构造方法python初始化类的对象使用__init__()这个特殊方法作为类的构造方法创建对象时会被自动调用。python构造方法不支持重载不能弄多个__init__()否则最后一个覆盖前面的。构造方法就是一种实例方法class Cat: 我是说明文档 def __init__(self): print(init) print(self) print(self.__class__) if __name__ __main__: cat Cat() print(cat)init __main__.Cat object at 0x000001982BE1D400 class __main__.Cat __main__.Cat object at 0x000001982BE1D4002.2 类和实例的变量定义在类以内直接写的变量没有self.就是类属性类变量类似Java中的static通过self.定义的是实例变量实例变量通常在构造器__init__中声明实例变量和类变量不同名时实例变量通过对象.访问类变量可以通过类.访问或对象.访问实例变量和类变量同名是合法但是不推荐的这种情况下实例和类会各自有一份对象.访问到的是实例变量类变量只能通过类.访问python的实例方法第一个参数必须是当前调用对象类似Java中的this名字可以任意取但是通常都起名叫self代表当前调用对象⚠️__init__()外面的同名变量前面没有static且和self.后面的变量名重名时self.极易被Java程序员误判为是在为外面的变量初始化或赋值但是这是两个不一样的变量这种Java的思路在python中是错误的例__init__()里面的重名nameage是实例变量通过对象.访问__init__()外面的重名nameage是类变量通过类.访问country变量不重名可通过对象.访问也可以通过类.访问class Stu: #类的 name simaple_name age 0 country China def __init__(self, age, name): print(init) #实例的 self.age age self.name name def speak(self): print(self.age, self.name) if __name__ __main__: stu1 Stu(18, 元宝) stu2 Stu(20, 大黄) stu1.speak() stu2.speak() print(---------------) print(Stu.name) print(Stu.age) print(-------------) print(stu1.country) print(stu2.country) print(Stu.country)init init 18 元宝 20 大黄 --------------- simaple_name 0 ------------- China China China有时候一个类存在变量过多逐一用参数赋值会很复杂例如def __str__(self)同样是一个内置函数类似Java中的toString()打印对象时转换为字符串双下划线__开头的变量属于私有变量类外不可直接访问class Stu: def __init__(self, name, age, no, score): self.name name self.age age self.no no self.__score score def __str__(self): return f{self.name}, {self.age}, {self.no}, {self.__score} if __name__ __main__: stu1 Stu(liming, 16, 10001, 100) print(stu1) print(stu1.name) print(stu1.age) print(stu1.no) #print(stu1.__score) ❌错误 AttributeError: Stu object has no attribute __scoreliming, 16, 10001, 100 liming 16 10001解决这个问题就可以__init__()外面给某个对象设置成员变量例如为stu2动态指定一个变量noself.__dict__可以代表对象所有变量class Stu: def __init__(self, name, age, score): self.name name self.age age self.__score score def __str__(self): return f{self.__dict__} if __name__ __main__: stu1 Stu(liming, 16, 100) print(stu1) stu2 Stu(liming, 16, 100) stu2.no 10002 print(stu2){name: liming, age: 16, _Stu__score: 100} {name: liming, age: 16, _Stu__score: 100, no: 10002}上面的写法不推荐可以采用定义好再去赋值的写法class Stu: def __init__(self): self.name None self.age None self.no None def __str__(self): return f{self.__dict__} if __name__ __main__: stu1 Stu() print(stu1) stu2 Stu() stu2.age 12 stu2.no 123 stu2.name sxh print(stu2){name: None, age: None, no: None} {name: sxh, age: 12, no: 123}还可以通过可变参数class Stu: def __init__(self, **kwargs): self.name kwargs.get(name, None) self.age kwargs.get(age, None) self.no kwargs.get(no, None) def __str__(self): return f{self.__dict__} if __name__ __main__: stu1 Stu(namelzj, age19, no10002) print(stu1) stu2 Stu(namexiaohong, age19 ) print(stu2){name: lzj, age: 19, no: 10002} {name: xiaohong, age: 19, no: None}2.3 成员访问限制在python中属性可以进行访问权限控制和Java类似如果要访问受保护的属性就定义方法这是类封装性的体现受保护的属性_单个下划线开头仅仅起到提醒作用但是不阻止访问例如_age私有属性__双下划线开头类外不可访问否则程序运行出错例如__age公开访问属性没有下划线的无限制魔法属性__双下划线开头和结尾是python内置的有特殊的含义不可自行定义例如__dict__⚠️ python处理__双下划线的底层实现是将该变量替换成了_类名__私有变量名通过将某个私有变量名修改为_类名__私有变量名的形式可以突破访问控制这种访问方式极不推荐例class Stu: def __init__(self, **kwargs): self.name kwargs.get(name, None) self.__age kwargs.get(age, None) self._no kwargs.get(no, None) def __str__(self): return f{self.__dict__} def getAge(self): return self.__age if __name__ __main__: stu1 Stu(namelzj, age19, no10002) print(stu1) print(stu1.name) print(stu1.getAge()) print(stu1._no) #不建议的 print(stu1._Stu__age) #不建议的 #print(stu1.__age) #运行出错{name: lzj, _Stu__age: 19, _no: 10002} lzj 19 10002 192.4 静态方法上面例子里面出现在类中参数以self开头的方法都是实例方法在python中还存在静态方法和类方法在python中有一种方法只是出现在类中但是不依赖于实例和类的普通方法叫做静态方法采用staticmethod装饰器装饰通常都是作为一种工具方法除非传入否则不能访问类和实例的成员可以使用实例名或类名调用但是参数列表中不能出现self或cls例def sum(a, b)就是静态方法class Stu: def __init__(self): name None def call(self): self.sum(1, 2) self.__class__.sum(3, 4) Stu.sum(5, 6) staticmethod def sum(a, b): return a b if __name__ __main__: stu1 Stu() stu1.call() stu1.sum(7, 8) Stu.sum(9, 10)2.5 类方法类方法类似Java中的static方法python的类方法声明时第一个形参永远是cls代表类本身不是实例同时除__new__()以外所有类方法都要采用classmethod函数装饰器修饰。python的类方法既能通过类也能通过实例调用但是为防混淆不建议用实例调用。主要用于操作类属性或实现工厂模式。可直接访问类属性类方法和静态方法不能直接操作实例属性或实例方法。例class Stu: school bupt def __init__(self): name None def call(self): pass classmethod def classfunc1(cls): print(cls.__name__) print(cls.school) cls.sum(1, 2) cls.classfunc2() classmethod def classfunc2(cls): print(classfunc2) staticmethod def sum(a, b): return a b if __name__ __main__: Stu.classfunc1() s1 Stu() s1.classfunc2() s1.__class__.classfunc2()Stu bupt classfunc2 classfunc2 classfunc2还可以实现工厂方法例如class Person: classmethod def create(cls, name): return cls(**{name: name}) def __init__(self, name): self.name name if __name__ __main__: p Person.create(lsj) print(p.name)2.6 property装饰器property是Python的一个内置装饰器让你可以像访问属性一样调用方法实现受控的属性访问。例age作为只读class Stu: property def age(self): return self.__age def __init__(self, age): self.__age age pass if __name__ __main__: print(Stu.age) #property object at 0x000001D82AA20D10 s1 Stu(29) print(s1.age) # 29 s2 Stu(34) print(s2.age) #34例radius可读可写class Circle: def __init__(self, radius): print(init , radius) self._radius radius property def radius(self): print(获取半径) return self._radius radius.setter def radius(self, value): print(设置半径带验证) if value 0: raise ValueError(半径不能为负数) self._radius value radius.deleter def radius(self): print(删除半径) del self._radius # 使用 c Circle(5) c.radius 10 print(c.radius) # c.radius -1 # ValueError: 半径不能为负数 del c.radiusinit 5 设置半径带验证 获取半径 10 删除半径2.7 其他魔术方法__call__()让实例对象被当作函数调用class Person: def __init__(self, name): self.name name def __call__(self, age): self.age age return self if __name__ __main__: p Person(lsj) q p(22) print(q.name) #lsj print(q.age) #22__repr__()和__str__()类似同时定义时__str__()优先2.8 dataclass装饰器dataclass的用途类似于Java中的Lombok常用于数据模型类。dataclass可以帮我们省略掉__init__()__eq__()__repr__()等常见方法的手写代码专注于业务逻辑dataclass中有变量名且加了类型的变量无论是否有默认值都会自动转换为实例变量自动添加到__init__()中有变量名不加类型但是有默认值的变量默认生成为类变量有变量名不加类型又无默认值的变量写法会直接报错。对于有变量名且加了类型的变量无论是否有默认值的情况dataclass既能生成带默认值的实例变量也能生成不带默认值的实例变量无默认值的实例变量必须在前带默认值的实例变量必须在后否则语法报错。类变量放哪都行无顺序限制例from dataclasses import dataclass from datetime import date, datetime dataclass class Book: # 会生成类变量所有书籍实例对象共享 # 类变量顺序无限制 publisher 机械工业出版社 #会生成实例变量无默认值 bookName: str price: float author: str course_date: date start_time: datetime # 会生成实例变量有默认值 num: int 12 id: int None⚠️ 需要注意的是可变默认值不要直接用如[],{}而是该用None因为默认值是在类定义时创建一次然后被所有实例共享现在的高版本python解释器会直接报错禁止这种写法。⚠️dataclass装饰器会自动生成__init__()一旦手写了__init__()会导致装饰器失效所有自动功能全部关闭如果想用了装饰器又要加自定义的初始化逻辑用__post_init__(self, ...)避免冲突例from dataclasses import dataclass from datetime import date, datetime dataclass class Book: # 类变量所有书籍实例对象共享 publisher 机械工业出版社 id: int None bookName: str None price: float None author: str None course_date: date None start_time: datetime None # 初始化后校验价格合法性 def __post_init__(self): print(__post_init__) if self.price is not None and self.price 0: raise ValueError(书籍价格不能为负数) if __name__ __main__: print(Book.publisher) book Book(id 1, bookName疯狂python讲义, price2.3) print(book) book Book(id 1, bookName疯狂python讲义, price-2.3)机械工业出版社 __post_init__ Book(id1, bookName疯狂python讲义, price2.3, authorNone, course_dateNone, start_timeNone) __post_init__ Traceback (most recent call last): File D:\PycharmProjects\python-lang-test\clazz\t3.py, line 27, in module book Book(id 1, bookName疯狂python讲义, price-2.3) ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File string, line 9, in __init__ File D:\PycharmProjects\python-lang-test\clazz\t3.py, line 21, in __post_init__ raise ValueError(书籍价格不能为负数) ValueError: 书籍价格不能为负数对__eq__()的重写也会导致比较对象时比较的是成员变量的值而不是地址if __name__ __main__: book1 Book(id 1, bookName疯狂python讲义, price2.3) book2 Book(id 1, bookName疯狂python讲义, price2.3) print(book1 book2) # True3.对象初始化python对象初始化o Obj()会经历以下几个过程1.调用该Obj类的__new__(cls,...)方法在内存中开辟空间创建一个空的实例对象2.__new__(cls,...)执行完成必须返回一个当前Obj类的实例对象就是后续的self3.python自动把返回的对象传给__init__(self,...)的第一个参数self4.调用__init__(self,...)给这个空对象绑定属性初始化数据5.返回初始化完成的实例对象给变量o其中__new__(cls,...)是类方法但是无需显式添加classmethod装饰器__new__(cls,...)必须有返回值返回当前类对象时执行__init__(self,...)返回None则跳过__init__(self,...)__new__(cls,...)的时机一定早于__init__(self,...)先创建再初始化永远不要出现多个生效的__init__(self,...)例class Person: def __new__(cls, name): obj super().__new__(cls) #调用父类直到object的__new__方法开内存空间 print(new) return obj def __init__(self, name): print(init) self.name name if __name__ __main__: p Person(liming) print(p.name)