小言_互联网的博客

Python:面向对象

379人阅读  评论(0)

目录

Python中一切皆对象

类的定义

类对象及其类属性

类实例化,和slef

类的特殊属性及实例变量、   类变量

类方法和静态方法

装饰一个类

访问控制:私有(Private)属性

保护变量

私有方法

私有成员的总结

补丁

属性装饰器【@property】

对象的销毁【__del__】

封装


 

Python中一切皆对象

面向对象的三要素

  • 封装
    • 组装:将数据和操作组装到一起
    • 隐藏数据:对外暴露一些接口,通过接口访问对象。比如驾驶员使用汽车,不需要了解汽车的构造细节,只需要知道怎么使用部件,怎么驾驶就行,踩了油门就能跑,可以不了解后面的激动原理
  • 继承
    • 多服用,继承来的就不用自己写了
    • 多继承少修改,OCP(Open-closed Principle),使用多继承来改变,来体现个性
  • 多态
    • 面向对象编程最灵活的地方,动态绑定

类的定义


   
  1. class ClassName:
  2. 语句块
  • 必须使用class关键字
  • 类名必须是用大驼峰命名
  • 类定义完成后,就产生了一个类对象,绑定到了标识符ClassName上  

类对象及其类属性
 


   
  1. class Myclass:
  2. """My class """
  3. x = "abc"
  4. def foo(self):
  5. return "This My class {} ".format(id(self))
  6. print(Myclass.x) #abc
  7. print(Myclass.foo) #<function Myclass.foo at 0x017FC468>
  8. print(Myclass.__doc__) #My class

类对象:类的定义就生成了一个类对象

类的属性:类定义中的变量和类中定义的方法都是类的属性

类变量:上列中x就是类Myclass的变量

  • Myclass 中,x、foo都是类的属性,__doc__也是类的属性
  • foo方法是类的属性,类实例化之后就可以调用
  • foo是方法对象method,不是普通的函数对象function了,它一般要求至少有一个参数,第一个参数可以是self(self只是习惯用标识符,可以换名字),这个参数位置就留给了self

类实例化,和slef

类的实例化就是在类对象后面加上一个括号,就是调用类的实例化方法,完成实例化。实例化就真正创建一个该类的对象(实例)


   
  1. tom = Myclass()
  2. jerry = Myclass()

类实例化后一定会获得一个对象,就是实例对象
上面的tom,jerry都是Myclass类的实例,通过实例化生成了2个实例,每次实例化后获得的实例,是不同的实例,即使使用同样的参数实例化,也得到不一样的对杨

类实例化后,得到一个实例对象,实例对象会绑定方法,调用方法时参使用jerry.foo()的方式

但是函数签名是foo(self),少传一个参数self吗?

这个self就是jerry,Python会把方法的调用者作为第一个参数传入self的实参传入

self.name就是jerry对象name,name是保存在了jerry对象上,而不是Myclass类上,所以称为实例变量


   
  1. class Myclass:
  2. """My class """
  3. x = "abc"
  4. def foo(self):
  5. return "This My class {}".format(id(self))
  6. jerry = Myclass()
  7. print( "jerry = ",jerry.foo()) # jerry = This My class 28097616
  8. print(jerry.x) #abc
  9. tom = Myclass()
  10. print( "tom = ",tom.foo()) # tom = This My class 28972816
  11. print(tom.x) #abc
上例说明,self就是调用者,就是tom,jerry的实例对象
self这个名字只是一个惯例,它可以修改,但是请不要修改,否则影响代码的可读性

类的特殊属性及实例变量、   类变量

类的特殊属性
特殊属性 含义
__name__ 对象名
__class__ 对象类型
__dict__ 对象的属性字典
__qualname__

类的限定名














例子:

   
  1. class Myclass:
  2. """My class """
  3. age = "18"
  4. def __init__(self,name):
  5. self.name = name
  6. jerry = Myclass( "jerry")
  7. print(Myclass.__name__) # Myclass
  8. print(Myclass.__class__) # <class 'type'>
  9. 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>}
  10. print(Myclass.__qualname__) # Myclass
  11. print(jerry.__class__.__name__) # Myclass #jerry是实力化的对象,需要借助__class__找到所属的类,来调用__name__
  12. print(jerry.__class__) # <class '__main__.Myclass'>
  13. print(jerry.__dict__) # {'name': 'jerry'}
  14. print(jerry.__class__.__qualname__) # Myclass #jerry是实力化的对象,需要借助__class__找到所属的类,来调用__qualname__

上例中,可以看到类属性保存在类的__dict__中,实例属性保存在 实例的__dict__中,如果从实例访问类的属性,就需要借助__class__找到所属的类

Python类实例化后,会自动调用__init__方法,这个方法第一个参数必须留给self,其他参数随意,

Myclass()实际上调用的是__init__(self)方法,可以不定义,如果没有定义会在实例化后隐士调用。【作用:对实例化初始化

初始化函数可以多个参数,请注意第一个参数位置必须是self,例如:__init__(self,name)
__init__() 方法不能有返回值,也就是None


类的实例变量、类变量
 


   
  1. class Myclass:
  2. """My class """
  3. age = "18"
  4. def __init__(self,name):
  5. self.name = name
  6. jerry = Myclass( "jerry")
  7. tom = Myclass( "tom")
  8. print(tom.age,tom.name) # 18 tom
  9. print(jerry.age,jerry.name) # 18 jerry
  10. print(Myclass.age) # 18 My.class.name调用会报错,应为Myclass没有name这个变量
  11. Myclass.age = 50
  12. print(tom.age,jerry.age,Myclass.age) # 50 50 50

实例变量是每一个实例自己的变量,是自己独有的;

类变量是类的变量,是类的说有实例共享的属性和方法;

在看下面的代码
 


   
  1. class Myclass:
  2. """My class """
  3. heighe = 180
  4. age = 18
  5. def __init__(self,name,age=20):
  6. self.name = name
  7. self.age = age
  8. jerry = Myclass( "jerry", 20)
  9. tom = Myclass( "tom")
  10. #Myclass.age = 50
  11. print(Myclass.age,tom.age,jerry.age) # 50 20 20
  12. print(Myclass.heighe,tom.heighe,jerry.heighe) # 180 180 180
  13. #jerry.heighe = 170
  14. print(Myclass.heighe,tom.heighe,jerry.heighe) # 180 180 170
  15. #tom.heighe +=10
  16. print(Myclass.heighe,tom.heighe,jerry.heighe) # 180 190 180
  17. #Myclass.heighe += 20
  18. print(Myclass.heighe,tom.heighe,jerry.heighe) # 200 200 200
  19. Myclass.weight = 90
  20. print(Myclass.weight,tom.weight,jerry.weight) # 90 90 90
  21. print(Myclass.__dict__[ "age"]) # 18
  22. print(jerry.__dict__[ "age"]) # 20
  23. print(tom.__dict__[ "heighe"]) # KeyError: 'heighe'
  24. print(Myclass.__dict__[ "weight"]) # 90

总结:

  • 是类的,也是这个类所有实例的,其实例都可以进行访问到;是实例的,就是这个实例自己的,通过类访问不到
  • 类变量是属于类的变量,这个类的所有实例可以共享这个变量
  • 实例可以动态的给自己增加一个属性。实例.__dict__[变量名]  和 [实例.变量名]  都可以访问到
  • 实例的同命变量会隐藏这类变量,或者说是覆盖了这个变量

实例属性的查找顺序

  • 指的是实例使用.来访问属性,会先找到自己的__dict__,如果没有,然后通过属性__class__找到自己的类,再去类的__dict__中找
  • 注意,如果实例使用__dict__[变量名]访问变量,将不会按照上面的查找变量了,这是指明使用字典的key查找,不是属性查找

一般来说,类变量使用全大写来命名

  

类方法和静态方法

普通函数


   
  1. class Myclass:
  2. """My class """
  3. def my_method():
  4. print( "my class method")
  5. def foo(self):
  6. print( "my class foo")
  7. a = Myclass()
  8. #a.my_method() # TypeError: my_method() takes 0 positional arguments but 1 was given
  9. Myclass.my_method() # my class method
  10. Myclass().foo() # my class foo # 可以调用
  11. 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()  :注意,虽然语法是对的,但是没有人这么用,也就是禁止这么写

类方法
 


   
  1. class Myclass:
  2. """My class """
  3. @classmethod
  4. def my_method(cls):
  5. print( "class = {0.__name__}({0})".format(cls))
  6. def foo(self):
  7. print( "my class foo")
  8. a = Myclass()
  9. a.my_method() # 相当于 a.__class__.my_method() # class = Myclass(<class '__main__.Myclass'>)
  10. Myclass.my_method() # class = Myclass(<class '__main__.Myclass'>)
  11. 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操作类的实例

静态方法


   
  1. class Myclass:
  2. """My class """
  3. x = "abc"
  4. @classmethod
  5. def my_method(cls):
  6. print( "class = {0.__name__}({0})".format(cls))
  7. @staticmethod
  8. def foo():
  9. print( "my class foo")
  10. a = Myclass()
  11. Myclass.my_method()
  12. Myclass.foo() # my class foo
  13. a.my_method()
  14. a.foo() # my class foo
  15. 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装饰器修饰的方法
  • 调用时,不会隐士传入参数,静态方法,只是表明这个方法输入这个名词空间,函数归在一起,方便组织管理

总结

  • 类几乎可以调用所有内部定义的方法,但是调用普通方法时会报错,原因是第一个参数必须是类的实例
  • 实例也几乎可以调用所有的方法,普通的函数调用一般不可能出现,因为不允许这么定义
  • 类除了普通方法,都可以调用,普通方法需要对象的实例作为第一参数
  • 实例可以调用所有类中定义的方法(包括类方法、静态方法),普通方法传入实例自身,静态方法和类方法需要找到实例的类

装饰一个类


   
  1. #增加变量
  2. def add_name(name,cls):
  3. cls.Name = name
  4. #改成装饰器
  5. def add_name(name):
  6. def warpper(cls):
  7. cls.Name = name
  8. return cls
  9. return warpper
  10. @add_name("Jerry")
  11. class Myclass:
  12. """My class """
  13. x = "abc"
  14. @classmethod
  15. def my_method(cls):
  16. print( "class = {0.__name__}({0})".format(cls))
  17. @staticmethod
  18. def foo():
  19. print( "my class foo")
  20. print(Myclass.Name) # Jerry

之所以能够装饰,本质上是为类对象添加了一个属性,而Myclass这个标识符指向了这个类对象

访问控制:私有(Private)属性

看下这个例子 


   
  1. class Myclass:
  2. def __init__(self,name,age=18):
  3. self.name = name
  4. self.age = age
  5. def growp(self,i = 1): # 增加访问控制来控制age的值
  6. if i > 0 and i < 100:
  7. self.age +=i
  8. a = Myclass( "tom")
  9. a.growp( 20)
  10. print(a.age) # 38
  11. a.age = 160
  12. print(a.age) # 160

上面例子,本来想通过方法控制属性,但是由于属性在外面可以访问,或者说可见,就可以直接绕过方法,直接修改这个属性

Python提供了私有属性可以解决这个问题

  • 使用下滑想开头的属性,就是私有属性

   
  1. class Myclass:
  2. def __init__(self,name,age=18):
  3. self.name = name
  4. self.__age = age
  5. def growp(self,i = 1): # 增加访问控制来控制age的值
  6. if i > 0 and i < 100:
  7. self.__age +=i
  8. return self.__age
  9. a = Myclass( "tom")
  10. print(a.growp( 20)) # 38
  11. print(a.__age) # AttributeError: 'Myclass' object has no attribute '__age'

现在外部访问不到【__age】 了,【age】根本就没有定义,更是访问不到

那么这个【__age】这个私有变量,就真的无法访问吗?


   
  1. class Myclass:
  2. def __init__(self,name,age=18):
  3. self.name = name
  4. self.__age = age
  5. def growp(self,i = 1): # 增加访问控制来控制age的值
  6. if i > 0 and i < 100:
  7. self.__age +=i
  8. return self.__age
  9. a = Myclass( "tom")
  10. print(a.growp( 20)) # 38
  11. a.__age = 180
  12. print(a.__age) # 180
  13. print(a.growp( 20)) # 58
  14. print(a.__dict_ _) # {'name': 'tom', '_Myclass__age': 58, '__age': 180}
  15. a._Myclass__age = 500
  16. print(a.growp( 20)) # 520
  17. print(a.__dict_ _) # {'name': 'tom', '_Myclass__age': 520, '__age': 180}

注意看__dict__中,里面是         {'name': 'tom', '_Myclass__age': 58, '__age': 180}

私有变量的本质:

  • 类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器将其改名,转换名称为:【_Myclass__变量名】   的名称,所以用原来的名字访问不到了

知道了私有变量的新名称,就可以直接从外部访问到,并可以修改它

保护变量

在变量名前使用一个下划线,称为保护变量


   
  1. class Myclass:
  2. def __init__(self,name,age=18):
  3. self.name = name
  4. self._age = age
  5. a = Myclass( "tom")
  6. print(a._age) # 18
  7. print(a.__dict__) # {'name': 'tom', '_age': 18}

可以看出,这个【_age】属性根本就没有改变名称,和普通的属性一样,解释器不做任何特殊处理。这是开发者共同的约定,看见这种变量,就如同私有变量,不要直接使用

私有方法


   
  1. class Myclass:
  2. def __init__(self,name,age=18):
  3. self.name = name
  4. self._age = age
  5. def __getname(self):
  6. return self.name
  7. def __getage(self):
  8. return self.name
  9. a = Myclass( "tom")
  10. #print(a.__getname()) # AttributeError: 'Myclass' object has no attribute '__getname'
  11. #print(a.__getage()) # AttributeError: 'Myclass' object has no attribute '__getage'
  12. print(a.__dict__) # {'name': 'tom', '_age': 18}
  13. 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}
  14. print(a._Myclass__getname()) # tom

私有方法的本质

  • 单下划线的方法只是开发者之间的约定,解释器不做任何改变
  • 双下化下的方法,是私有方法,解释器会改名,改名策略和私有变量相同,【_类名__方法名】。方法变量都在类的【__dict__】中可以找到

私有成员的总结

在Python中使用【_】单下划线或者【__】双下划线来标识一个成员被保护或者私有化隐藏起来。但是,不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员,Python中没有绝对的安全保护成员,或者私有成员

因此,前导的下划线只是一种警告或者提醒,请遵守这个约定。除非真有必要,不要修改或者使用保护成员或者私有成员,更不要修改他们

补丁

可以通过修改或者替换类的成员。使用者调用的方式没有发生改变,但是,类提供的功能可能已经改变了。

猴子补丁【Mokey Patch】

  • 在运行时,对属性、方法、函数等进行动态替换
  • 其目的往往是为了通过替换、修改来增强、扩展原有的代码【黑魔法,慎用】

【a.py】运行文件


   
  1. from b import Person
  2. from c import getsocre
  3. def mokeypatch4Person():
  4. Person.getsorce = getsocre
  5. student1 = Person( 90, 80)
  6. print(student1.getsorce()) # (90, 80)
  7. mokeypatch4Person()
  8. student2 = Person( 60, 70)
  9. print(student2.getsorce()) # {'chi': 60, 'eng': 70}

【b.py】需要补丁的类


   
  1. class Person:
  2. def __init__(self,chinese,engling):
  3. self.chinese = chinese
  4. self.enging = engling
  5. def getsorce(self):
  6. return (self.chinese,self.enging)

 【c.py】补丁【注意补丁的函数,第一个参数是self】


   
  1. def getsocre(self):
  2. return dict(chi=self.chinese,eng=self.enging)

 假设Person类的【gesocre】方法是从数据库那数据,但是测试时候,不方便,使用猴子补丁,替换了【getsocre】方法,返回模拟的数据

属性装饰器【@property】

一般好的设计是:把实例的属性保护起来,不让外部直接访问,外部使用【getter】读取属性和【setter】方法设置属性
 


   
  1. class Myclass:
  2. def __init__(self,name,age =18):
  3. self.name = name
  4. self.__age = age
  5. @property
  6. def age(self):
  7. return self.__age
  8. @age.setter
  9. def age(self,age):
  10. self.__age =age
  11. @age.deleter
  12. def age(self):
  13. #del self.__age
  14. print( "del")
  15. a = Myclass( "tom")
  16. print(a.age) # 18
  17. a.age = 90
  18. print(a.age) # 90
  19. del a.age # del

使用【property】装饰器的时候这三个方法同命

  • property装饰器:后面跟的函数名就是以后的属性名。它就是【getter】。这个必须有,有了它至少是只读属性
  • setter装饰器:与属性名同名,且接受2个参数,第一个self,第二个是将要赋值的值,有个它,属性可写
  • deleter装饰器:可以控制是否删除属性。很少用

property装饰器必须在前,setter,deleter装饰器在后

property装饰器能通过简单的方式,把对方的操作变成对属性的访问,并起到了一定的隐藏效果

第二中写法
 


   
  1. class Myclass:
  2. def __init__(self,name,age =18):
  3. self.name = name
  4. self.__age = age
  5. def getage(self):
  6. return self.__age
  7. def set_age(self,age):
  8. self.__age =age
  9. def del_age(self):
  10. #del self.__age
  11. print( "del")
  12. age = property(getage,set_age,del_age, "age properyt")
  13. a = Myclass( "tom")
  14. print(a.age) # 18
  15. a.age = 90
  16. print(a.age) # 90
  17. del a.age # del

第三中
 


   
  1. class Myclass:
  2. def __init__(self,name,age =18):
  3. self.name = name
  4. self.__age = age
  5. def getage(self):
  6. return self.__age
  7. def set_age(self,age):
  8. self.__age =age
  9. def del_age(self):
  10. #del self.__age
  11. print( "del")
  12. age = property( lambda self :self.__age,set_age)
  13. a = Myclass( "tom")
  14. print(a.age) # 18
  15. a.age = 90
  16. print(a.age) # 90

 

对象的销毁【__del__】

类中可以定义【__del__】方法,称为析构函数

  • 作用:销毁类的实例的时候调用,以释放占用的资源,其中就放些清理资源的代码,比如释放连接
  • 注意这个方法不能引起对象的真正销毁,只是对象的销毁时候会自动调用它
  • 使用del语句删除实例,引用计数减1,当引用技术为0时,会自动调用【__del__】方法
  • 由于Python实现了垃圾回收机制,不能确定对象何时执行垃圾回收

   
  1. class Myclass:
  2. def __init__(self,name,age =18):
  3. self.name = name
  4. self.__age = age
  5. def getage(self):
  6. return self.__age
  7. def set_age(self,age):
  8. self.__age =age
  9. def del_age(self):
  10. #del self.__age
  11. print( "del")
  12. def __del__(self):
  13. print( "我走了")
  14. age = property( lambda self :self.__age,set_age)
  15. a = Myclass( "tom")
  16. print(a.age) # 18
  17. a.age = 90
  18. print(a.age) # 90

封装

面向对象的三要素之一,封装Encapsulation

  • 将数据和操作组织到类中,及属性和方法
  • 将数据隐藏起来,给使用提供操作(方法)。使用者通过操作就可以获取或者修改数据。
  • getter和setter
  • 通过访问控制,暴露适当和操作给用户,改隐藏的隐藏起来,例如保护成员和私有成员

转载:https://blog.csdn.net/Smart_look/article/details/116110162
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场