Python:面向对象
发布日期:2021-05-07 14:08:03 浏览次数:7 分类:原创文章

本文共 13341 字,大约阅读时间需要 44 分钟。

目录


 

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)        #abcprint(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类上,所以称为实例变量

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 28097616print(jerry.x)                  #abctom = Myclass()print("tom = ",tom.foo())       # tom =  This My class 28972816print(tom.x)                    #abc
上例说明,self就是调用者,就是tom,jerry的实例对象
self这个名字只是一个惯例,它可以修改,但是请不要修改,否则影响代码的可读性

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

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

类的限定名














例子:
class Myclass:    """My class """    age = "18"    def __init__(self,name):        self.name = namejerry = Myclass("jerry")print(Myclass.__name__)     #   Myclassprint(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__) #   Myclassprint(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 = namejerry = Myclass("jerry")tom = Myclass("tom")print(tom.age,tom.name)     #   18 tomprint(jerry.age,jerry.name) #   18 jerryprint(Myclass.age)          #   18   My.class.name调用会报错,应为Myclass没有name这个变量Myclass.age = 50print(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 = agejerry = Myclass("jerry",20)tom = Myclass("tom")#Myclass.age = 50print(Myclass.age,tom.age,jerry.age)  # 50 20 20print(Myclass.heighe,tom.heighe,jerry.heighe)   #   180 180 180#jerry.heighe = 170print(Myclass.heighe,tom.heighe,jerry.heighe)   #   180 180 170#tom.heighe +=10print(Myclass.heighe,tom.heighe,jerry.heighe)   #   180 190 180#Myclass.heighe += 20print(Myclass.heighe,tom.heighe,jerry.heighe)   #   200 200 200Myclass.weight = 90print(Myclass.weight,tom.weight,jerry.weight)  #    90 90 90print(Myclass.__dict__["age"])  #   18print(jerry.__dict__["age"])    #   20print(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 givenMyclass.my_method()     #   my class methodMyclass().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 fooa.my_method()a.foo()        #   my class fooprint(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 +=ia = Myclass("tom")a.growp(20)print(a.age)    # 38a.age = 160print(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.__agea = Myclass("tom")print(a.growp(20))  #   38print(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.__agea = Myclass("tom")print(a.growp(20))  #   38a.__age = 180print(a.__age)      #   180print(a.growp(20))  #   58print(a.__dict__)   #   {'name': 'tom', '_Myclass__age': 58, '__age': 180}a._Myclass__age = 500print(a.growp(20))  #   520print(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 = agea = Myclass("tom")print(a._age)       #   18print(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.namea = 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 Personfrom c import getsocredef mokeypatch4Person():    Person.getsorce = getsocrestudent1 = 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)    #   18a.age = 90print(a.age)    #   90del 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)    #   18a.age = 90print(a.age)    #   90del 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)    #   18a.age = 90print(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)    #   18a.age = 90print(a.age)    #   90

封装

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

  • 将数据和操作组织到类中,及属性和方法
  • 将数据隐藏起来,给使用提供操作(方法)。使用者通过操作就可以获取或者修改数据。
  • getter和setter
  • 通过访问控制,暴露适当和操作给用户,改隐藏的隐藏起来,例如保护成员和私有成员
上一篇:Python练习题 :随机生成一批数
下一篇:Python:类型注解、inspect

发表评论

最新留言

感谢大佬
[***.8.128.20]2025年04月09日 11时56分50秒