目录
Python中一切皆对象
面向对象的三要素
- 封装
- 组装:将数据和操作组装到一起
- 隐藏数据:对外暴露一些接口,通过接口访问对象。比如驾驶员使用汽车,不需要了解汽车的构造细节,只需要知道怎么使用部件,怎么驾驶就行,踩了油门就能跑,可以不了解后面的激动原理
- 继承
- 多服用,继承来的就不用自己写了
- 多继承少修改,OCP(Open-closed Principle),使用多继承来改变,来体现个性
- 多态
- 面向对象编程最灵活的地方,动态绑定
类的定义
class ClassName: 语句块
- 必须使用class关键字
- 类名必须是用大驼峰命名
- 类定义完成后,就产生了一个类对象,绑定到了标识符ClassName上
类对象及其类属性
class Myclass: """My class """ x = "abc" def foo(self): return "This My class {} ".format(id(self)) print(Myclass.x) #abc print(Myclass.foo) #<function Myclass.foo at 0x017FC468> print(Myclass.__doc__) #My class类对象:类的定义就生成了一个类对象
类的属性:类定义中的变量和类中定义的方法都是类的属性
类变量:上列中x就是类Myclass的变量
- Myclass 中,x、foo都是类的属性,__doc__也是类的属性
- foo方法是类的属性,类实例化之后就可以调用
- foo是方法对象method,不是普通的函数对象function了,它一般要求至少有一个参数,第一个参数可以是self(self只是习惯用标识符,可以换名字),这个参数位置就留给了self
类实例化,和slef
类的实例化就是在类对象后面加上一个括号,就是调用类的实例化方法,完成实例化。实例化就真正创建一个该类的对象(实例)
tom = Myclass() jerry = Myclass()类实例化后一定会获得一个对象,就是实例对象
上面的tom,jerry都是Myclass类的实例,通过实例化生成了2个实例,每次实例化后获得的实例,是不同的实例,即使使用同样的参数实例化,也得到不一样的对杨类实例化后,得到一个实例对象,实例对象会绑定方法,调用方法时参使用jerry.foo()的方式
但是函数签名是foo(self),少传一个参数self吗?
这个self就是jerry,Python会把方法的调用者作为第一个参数传入self的实参传入
self.name就是jerry对象name,name是保存在了jerry对象上,而不是Myclass类上,所以称为实例变量
上例说明,self就是调用者,就是tom,jerry的实例对象
class Myclass: """My class """ x = "abc" def foo(self): return "This My class {}".format(id(self)) jerry = Myclass() print( "jerry = ",jerry.foo()) # jerry = This My class 28097616 print(jerry.x) #abc tom = Myclass() print( "tom = ",tom.foo()) # tom = This My class 28972816 print(tom.x) #abc
self这个名字只是一个惯例,它可以修改,但是请不要修改,否则影响代码的可读性
类的特殊属性及实例变量、 类变量
类的特殊属性 特殊属性 含义 __name__ 对象名 __class__ 对象类型 __dict__ 对象的属性字典 __qualname__ 类的限定名
例子:
class Myclass: """My class """ age = "18" def __init__(self,name): self.name = name jerry = Myclass( "jerry") print(Myclass.__name__) # Myclass print(Myclass.__class__) # <class 'type'> print(Myclass.__dict__) # {'__module__': '__main__', '__doc__': 'My class ', 'age': '18', '__init__': <function Myclass.__init__ at 0x019AC228>, '__dict__': <attribute '__dict__' of 'Myclass' objects>, '__weakref__': <attribute '__weakref__' of 'Myclass' objects>} print(Myclass.__qualname__) # Myclass print(jerry.__class__.__name__) # Myclass #jerry是实力化的对象,需要借助__class__找到所属的类,来调用__name__ print(jerry.__class__) # <class '__main__.Myclass'> print(jerry.__dict__) # {'name': 'jerry'} print(jerry.__class__.__qualname__) # Myclass #jerry是实力化的对象,需要借助__class__找到所属的类,来调用__qualname__上例中,可以看到类属性保存在类的__dict__中,实例属性保存在 实例的__dict__中,如果从实例访问类的属性,就需要借助__class__找到所属的类
Python类实例化后,会自动调用__init__方法,这个方法第一个参数必须留给self,其他参数随意,Myclass()实际上调用的是__init__(self)方法,可以不定义,如果没有定义会在实例化后隐士调用。【作用:对实例化初始化】
初始化函数可以多个参数,请注意第一个参数位置必须是self,例如:__init__(self,name)
__init__() 方法不能有返回值,也就是None
类的实例变量、类变量
class Myclass: """My class """ age = "18" def __init__(self,name): self.name = name jerry = Myclass( "jerry") tom = Myclass( "tom") print(tom.age,tom.name) # 18 tom print(jerry.age,jerry.name) # 18 jerry print(Myclass.age) # 18 My.class.name调用会报错,应为Myclass没有name这个变量 Myclass.age = 50 print(tom.age,jerry.age,Myclass.age) # 50 50 50实例变量是每一个实例自己的变量,是自己独有的;
类变量是类的变量,是类的说有实例共享的属性和方法;
在看下面的代码
class Myclass: """My class """ heighe = 180 age = 18 def __init__(self,name,age=20): self.name = name self.age = age jerry = Myclass( "jerry", 20) tom = Myclass( "tom") #Myclass.age = 50 print(Myclass.age,tom.age,jerry.age) # 50 20 20 print(Myclass.heighe,tom.heighe,jerry.heighe) # 180 180 180 #jerry.heighe = 170 print(Myclass.heighe,tom.heighe,jerry.heighe) # 180 180 170 #tom.heighe +=10 print(Myclass.heighe,tom.heighe,jerry.heighe) # 180 190 180 #Myclass.heighe += 20 print(Myclass.heighe,tom.heighe,jerry.heighe) # 200 200 200 Myclass.weight = 90 print(Myclass.weight,tom.weight,jerry.weight) # 90 90 90 print(Myclass.__dict__[ "age"]) # 18 print(jerry.__dict__[ "age"]) # 20 print(tom.__dict__[ "heighe"]) # KeyError: 'heighe' print(Myclass.__dict__[ "weight"]) # 90总结:
- 是类的,也是这个类所有实例的,其实例都可以进行访问到;是实例的,就是这个实例自己的,通过类访问不到
- 类变量是属于类的变量,这个类的所有实例可以共享这个变量
- 实例可以动态的给自己增加一个属性。实例.__dict__[变量名] 和 [实例.变量名] 都可以访问到
- 实例的同命变量会隐藏这类变量,或者说是覆盖了这个变量
实例属性的查找顺序
- 指的是实例使用.来访问属性,会先找到自己的__dict__,如果没有,然后通过属性__class__找到自己的类,再去类的__dict__中找
- 注意,如果实例使用__dict__[变量名]访问变量,将不会按照上面的查找变量了,这是指明使用字典的key查找,不是属性查找
一般来说,类变量使用全大写来命名
类方法和静态方法
普通函数
class Myclass: """My class """ def my_method(): print( "my class method") def foo(self): print( "my class foo") a = Myclass() #a.my_method() # TypeError: my_method() takes 0 positional arguments but 1 was given Myclass.my_method() # my class method Myclass().foo() # my class foo # 可以调用 print(Myclass.__dict__) # {'__module__': '__main__', '__doc__': 'My class ', 'my_method': <function Myclass.my_method at 0x014DC468>, 'foo': <function Myclass.foo at 0x01526150>, '__dict__': <attribute '__dict__' of 'Myclass' objects>, '__weakref__': <attribute '__weakref__' of 'Myclass' objects>}Myclass.my_method()
- 是可以调用的,因为这个方法只是被Myclass这个名词空间管理的一个普通方法,my_method这是Myclass的一个属性而已
- 实例化之后,a.my_method() 在定义没有指定self,所以不能完成实例对象的绑定,不能用
Myclass().foo() :注意,虽然语法是对的,但是没有人这么用,也就是禁止这么写
类方法
class Myclass: """My class """ @classmethod def my_method(cls): print( "class = {0.__name__}({0})".format(cls)) def foo(self): print( "my class foo") a = Myclass() a.my_method() # 相当于 a.__class__.my_method() # class = Myclass(<class '__main__.Myclass'>) Myclass.my_method() # class = Myclass(<class '__main__.Myclass'>) print(Myclass.__dict__) # {'__module__': '__main__', '__doc__': 'My class ', 'my_method': <classmethod object at 0x01D09070>, 'foo': <function Myclass.foo at 0x02246150>, '__dict__': <attribute '__dict__' of 'Myclass' objects>, '__weakref__': <attribute '__weakref__' of 'Myclass' objects>}
- 在类定义中,使用@classmethodz装饰器修饰的方法
- 必须至少一个参数,且第一个参数留给cls,cls指代调用者即类对象自身
- cls这个标识符可以是任意合法名称,但是为了易读,请不要修改
- 通过cls可以直接操作类的属性
- 无法通过cls操作类的实例
静态方法
class Myclass: """My class """ x = "abc" @classmethod def my_method(cls): print( "class = {0.__name__}({0})".format(cls)) @staticmethod def foo(): print( "my class foo") a = Myclass() Myclass.my_method() Myclass.foo() # my class foo a.my_method() a.foo() # my class foo print(Myclass.__dict__) # {'__module__': '__main__', '__doc__': 'My class ', 'x': 'abc', 'my_method': <classmethod object at 0x01359070>, 'foo': <staticmethod object at 0x0135FB90>, '__dict__': <attribute '__dict__' of 'Myclass' objects>, '__weakref__': <attribute '__weakref__' of 'Myclass' objects>}
- 在类定义中,使用@staticmethod装饰器修饰的方法
- 调用时,不会隐士传入参数,静态方法,只是表明这个方法输入这个名词空间,函数归在一起,方便组织管理
总结
- 类几乎可以调用所有内部定义的方法,但是调用普通方法时会报错,原因是第一个参数必须是类的实例
- 实例也几乎可以调用所有的方法,普通的函数调用一般不可能出现,因为不允许这么定义
- 类除了普通方法,都可以调用,普通方法需要对象的实例作为第一参数
- 实例可以调用所有类中定义的方法(包括类方法、静态方法),普通方法传入实例自身,静态方法和类方法需要找到实例的类
装饰一个类
#增加变量 def add_name(name,cls): cls.Name = name #改成装饰器 def add_name(name): def warpper(cls): cls.Name = name return cls return warpper @add_name("Jerry") class Myclass: """My class """ x = "abc" @classmethod def my_method(cls): print( "class = {0.__name__}({0})".format(cls)) @staticmethod def foo(): print( "my class foo") print(Myclass.Name) # Jerry之所以能够装饰,本质上是为类对象添加了一个属性,而Myclass这个标识符指向了这个类对象
访问控制:私有(Private)属性
看下这个例子
class Myclass: def __init__(self,name,age=18): self.name = name self.age = age def growp(self,i = 1): # 增加访问控制来控制age的值 if i > 0 and i < 100: self.age +=i a = Myclass( "tom") a.growp( 20) print(a.age) # 38 a.age = 160 print(a.age) # 160上面例子,本来想通过方法控制属性,但是由于属性在外面可以访问,或者说可见,就可以直接绕过方法,直接修改这个属性
Python提供了私有属性可以解决这个问题
- 使用下滑想开头的属性,就是私有属性
class Myclass: def __init__(self,name,age=18): self.name = name self.__age = age def growp(self,i = 1): # 增加访问控制来控制age的值 if i > 0 and i < 100: self.__age +=i return self.__age a = Myclass( "tom") print(a.growp( 20)) # 38 print(a.__age) # AttributeError: 'Myclass' object has no attribute '__age'现在外部访问不到【__age】 了,【age】根本就没有定义,更是访问不到
那么这个【__age】这个私有变量,就真的无法访问吗?
class Myclass: def __init__(self,name,age=18): self.name = name self.__age = age def growp(self,i = 1): # 增加访问控制来控制age的值 if i > 0 and i < 100: self.__age +=i return self.__age a = Myclass( "tom") print(a.growp( 20)) # 38 a.__age = 180 print(a.__age) # 180 print(a.growp( 20)) # 58 print(a.__dict_ _) # {'name': 'tom', '_Myclass__age': 58, '__age': 180} a._Myclass__age = 500 print(a.growp( 20)) # 520 print(a.__dict_ _) # {'name': 'tom', '_Myclass__age': 520, '__age': 180}注意看__dict__中,里面是 {'name': 'tom', '_Myclass__age': 58, '__age': 180}
私有变量的本质:
- 类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器将其改名,转换名称为:【_Myclass__变量名】 的名称,所以用原来的名字访问不到了
知道了私有变量的新名称,就可以直接从外部访问到,并可以修改它
保护变量
在变量名前使用一个下划线,称为保护变量
class Myclass: def __init__(self,name,age=18): self.name = name self._age = age a = Myclass( "tom") print(a._age) # 18 print(a.__dict__) # {'name': 'tom', '_age': 18}可以看出,这个【_age】属性根本就没有改变名称,和普通的属性一样,解释器不做任何特殊处理。这是开发者共同的约定,看见这种变量,就如同私有变量,不要直接使用
私有方法
class Myclass: def __init__(self,name,age=18): self.name = name self._age = age def __getname(self): return self.name def __getage(self): return self.name a = Myclass( "tom") #print(a.__getname()) # AttributeError: 'Myclass' object has no attribute '__getname' #print(a.__getage()) # AttributeError: 'Myclass' object has no attribute '__getage' print(a.__dict__) # {'name': 'tom', '_age': 18} print(a.__class__.__dict__) # {'__module__': '__main__', '__init__': <function Myclass.__init__ at 0x01ABC468>, '_Myclass__getname': <function Myclass.__getname at 0x01B06150>, '_Myclass__getage': <function Myclass.__getage at 0x01B064B0>, '__dict__': <attribute '__dict__' of 'Myclass' objects>, '__weakref__': <attribute '__weakref__' of 'Myclass' objects>, '__doc__': None} print(a._Myclass__getname()) # tom私有方法的本质
- 单下划线的方法只是开发者之间的约定,解释器不做任何改变
- 双下化下的方法,是私有方法,解释器会改名,改名策略和私有变量相同,【_类名__方法名】。方法变量都在类的【__dict__】中可以找到
私有成员的总结
在Python中使用【_】单下划线或者【__】双下划线来标识一个成员被保护或者私有化隐藏起来。但是,不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员,Python中没有绝对的安全保护成员,或者私有成员
因此,前导的下划线只是一种警告或者提醒,请遵守这个约定。除非真有必要,不要修改或者使用保护成员或者私有成员,更不要修改他们
补丁
可以通过修改或者替换类的成员。使用者调用的方式没有发生改变,但是,类提供的功能可能已经改变了。
猴子补丁【Mokey Patch】
- 在运行时,对属性、方法、函数等进行动态替换
- 其目的往往是为了通过替换、修改来增强、扩展原有的代码【黑魔法,慎用】
【a.py】运行文件
from b import Person from c import getsocre def mokeypatch4Person(): Person.getsorce = getsocre student1 = Person( 90, 80) print(student1.getsorce()) # (90, 80) mokeypatch4Person() student2 = Person( 60, 70) print(student2.getsorce()) # {'chi': 60, 'eng': 70}【b.py】需要补丁的类
class Person: def __init__(self,chinese,engling): self.chinese = chinese self.enging = engling def getsorce(self): return (self.chinese,self.enging)【c.py】补丁【注意补丁的函数,第一个参数是self】
def getsocre(self): return dict(chi=self.chinese,eng=self.enging)假设Person类的【gesocre】方法是从数据库那数据,但是测试时候,不方便,使用猴子补丁,替换了【getsocre】方法,返回模拟的数据
属性装饰器【@property】
一般好的设计是:把实例的属性保护起来,不让外部直接访问,外部使用【getter】读取属性和【setter】方法设置属性
class Myclass: def __init__(self,name,age =18): self.name = name self.__age = age @property def age(self): return self.__age @age.setter def age(self,age): self.__age =age @age.deleter def age(self): #del self.__age print( "del") a = Myclass( "tom") print(a.age) # 18 a.age = 90 print(a.age) # 90 del a.age # del使用【property】装饰器的时候这三个方法同命
- property装饰器:后面跟的函数名就是以后的属性名。它就是【getter】。这个必须有,有了它至少是只读属性
- setter装饰器:与属性名同名,且接受2个参数,第一个self,第二个是将要赋值的值,有个它,属性可写
- deleter装饰器:可以控制是否删除属性。很少用
property装饰器必须在前,setter,deleter装饰器在后
property装饰器能通过简单的方式,把对方的操作变成对属性的访问,并起到了一定的隐藏效果
第二中写法
class Myclass: def __init__(self,name,age =18): self.name = name self.__age = age def getage(self): return self.__age def set_age(self,age): self.__age =age def del_age(self): #del self.__age print( "del") age = property(getage,set_age,del_age, "age properyt") a = Myclass( "tom") print(a.age) # 18 a.age = 90 print(a.age) # 90 del a.age # del第三中
class Myclass: def __init__(self,name,age =18): self.name = name self.__age = age def getage(self): return self.__age def set_age(self,age): self.__age =age def del_age(self): #del self.__age print( "del") age = property( lambda self :self.__age,set_age) a = Myclass( "tom") print(a.age) # 18 a.age = 90 print(a.age) # 90
对象的销毁【__del__】
类中可以定义【__del__】方法,称为析构函数
- 作用:销毁类的实例的时候调用,以释放占用的资源,其中就放些清理资源的代码,比如释放连接
- 注意这个方法不能引起对象的真正销毁,只是对象的销毁时候会自动调用它
- 使用del语句删除实例,引用计数减1,当引用技术为0时,会自动调用【__del__】方法
- 由于Python实现了垃圾回收机制,不能确定对象何时执行垃圾回收
class Myclass: def __init__(self,name,age =18): self.name = name self.__age = age def getage(self): return self.__age def set_age(self,age): self.__age =age def del_age(self): #del self.__age print( "del") def __del__(self): print( "我走了") age = property( lambda self :self.__age,set_age) a = Myclass( "tom") print(a.age) # 18 a.age = 90 print(a.age) # 90
封装
面向对象的三要素之一,封装Encapsulation
- 将数据和操作组织到类中,及属性和方法
- 将数据隐藏起来,给使用提供操作(方法)。使用者通过操作就可以获取或者修改数据。
- getter和setter
- 通过访问控制,暴露适当和操作给用户,改隐藏的隐藏起来,例如保护成员和私有成员
转载:https://blog.csdn.net/Smart_look/article/details/116110162