Python的面向对象

一、面向对象概述

1、语言的分类

1)面向机器
抽象成机器指令,机器容易理解
代表:汇编语言

2)面向过程
做一件事情,排出个步骤,第一步干什么,第二步干什么,如果出现情况A,做什么处理,如果出现了情况B,做什么处理
适用于问题规模小,可以步骤化,按部就班处理,(有if、for循环)
代表:C语言

3)面向对象OOP
随着计算机需要解决的问题的规模扩大,情况越来越复杂;需要很多人、很多部门协作,面向过程编程不太适合了
代表:C++、Java、Python等

2、面向对象
面向对象:一种认识世界、分析世界的方法论;将万事万物抽象为类;
一切对象皆有类型,类型本身也是类对象,一切皆对象;

1)类和实例
类class:类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合;
用计算机语言来描述类,就是属性和方法的集合;
对象instance、object:对象是类的具象,是一个实体;
属性:它是对象状态的抽象,用数据结构来描述
操作:它是对象行为的抽象,用操作名和实现该操作的方法来描述

哲学
一切皆对象,对象是数据和操作的封装
对象是独立的,但是对象之间可以相互作用
目前OOP是最接近人类认知的编程范式

2)面向对象三要素

封装
组装:将数据和操作组装到一起
隐藏数据:对外只暴露一些接口,通过接口访问对象
比如驾驶员使用汽车,不需要了解汽车的构造细节,只需要知道使用什么部件怎么驾驶就行

继承
多复用,继承来的就不用自己写了
多继承少修改,OCP(Open-closed Principle),使用继承来改变,来体现个性

多态
面向对象编程最灵活的地方,动态绑定

二、Python的类

类是属性和方法的集合,方法也是类的属性,方法不是普通的函数对象,它要求至少一个self参数,实例调用;

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

class MyClass:
    """A example class""" 
    x = 'abc' # 类属性
​
    def foo(self): # 类属性foo,也是方法 
        return 'My Class'
​
print(MyClass.x)  # abc
print(MyClass.foo)  # <function MyClass.foo at 0x10eb4a730>
print(MyClass.__doc__)  # A example class

2、类对象和类属性
类对象,类的定义就会生成一个类对象
类的属性,类定义中的变量和类中定义的方法都是类的属性
类变量,类中定义的普通变量,上例中x是类MyClass的变量

MyClass中,x、foo都是类的属性, doc 也是类的属性
foo是方法对象method,不是普通的函数对象function了,它一般要求至少有一个参数,第一个参数可以是 self(self只是个惯用标识符,可以换名字),这个参数位置就留给了self;self 指代当前实例本身;

3、实例化
a = MyClass() # 实例化
使用上面的语法,在类对象名称后面加上一个括号,就调用类的实例化方法,完成实例化;Python类实例化后,会自动调用 init 方法;这个方法第一个参数必须留给self,其它参数随意;

1)__init__方法
MyClass()实际上调用的是 init(self) 方法,可以不定义,如果没有定义会在实例化后隐式调用;
作用:也叫构造函数、构造器,对实例进行初始化
注意: init() 方法不能有返回值,也就是只能是return None

2)实例变量和类变量
实例变量是每一个实例自己的变量,是自己独有的;类变量是类的变量,是类的所有实例共享的属性和方法;一般来说,类变量可使用全大写来命名;

  • Python中每一种对象都拥有不同的属性;函数、类都是对象,类的实例也是对象;
    __class__拿到的只是类型,类是一种特殊的类型<class ‘type’>,实例调用__class__等同于type方法;
  • 类属性保存在类的 dict 中,实例属性保存在实例的 dict 中,如果从实例访问类的属性,就需要借助 class 找到所属的类;
  • 对象(实例或类)可以动态的给自己增加一个属性;实例.dict[变量名] 和 实例.变量名 都可以访问到;实例的同名变量会隐藏掉类变量,或者说是覆盖了这个类变量;
  • 实例属性的查找顺序:指的是实例使用 .点号来访问属性,会先找自己的 dict ,如果没有,然后通过属性 class 找到自己的类,再去类的 dict 中找;注意如果实例使用 dict[变量名] 访问变量,将不会按照上面的查找顺序找变量了,这是指明使用字典的key 查找,不是属性查找;

装饰器装饰一个类
为一个类通过装饰,增加一些类属性,例如能否给一个类增加一个NAME属性并提供属性值

def add_name(name):
    def wrapper(cls):
        cls.NAME = name
        return cls
    return wrapper
​
@add_name('Tom')
class Person:
    AGE = 3
​
print(Person.NAME)  # Tom

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

4、类方法和静态方法
不是类中所有的方法第一参数都是self,即使是实例调用;

1)普通函数
Person.normal_method() 可以放在类中定义,因为这个方法只是被Person这个名词空间管理的一个普通的方法,normal_method是Person 的一个属性而已;
由于normal_method在定义的时候没有指定self,所以不能完成实例对象的绑定,不能用Person().normal_method()调用;
注意:虽然语法是对的,但是禁止这么写;

2)类方法@classmethod
指在类定义中,使用@classmethod装饰器修饰的方法,通过cls参数可直接操作类的属性;
类方法,类似于C++、Java中的静态方法;类和实例均可调用该方法,但传入的第一参数是cls;

在类定义中,使用@classmethod装饰器修饰的方法

  • 必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象自身 cls这个标识符
  • 可以是任意合法名称,但是为了易读,请不要修改
  • 通过cls可以直接操作类的属性,注意无法通过cls操作类的实例
class Person:
    @classmethod
    def class_method(cls): # cls是类
        print('class = {0.__name__} ({0})'.format(cls)) 
        cls.HEIGHT = 170
    ​
Person.class_method()  # class = Person (<class '__main__.Person'>)
print(Person.__dict__)
# {'__module__': '__main__', 'class_method': <classmethod object at 0x10afdd080>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HEIGHT': 170}

3)静态方法@staticmethod
在类定义中,使用@staticmethod装饰器修饰的方法,类和实例均可调用此方法;
调用时,不会隐式的传入参数
静态方法,只是表明这个方法属于这个名词空间;函数归在一起,方便组织管理

class Person:
    @classmethod
    def class_method(cls): # cls是什么
        print('class = {0.__name__} ({0})'.format(cls)) 
        cls.HEIGHT = 170
    
    @staticmethod
    def static_methd():
        print(Person.HEIGHT)
​
Person.class_method()  # class = Person (<class '__main__.Person'>)
Person.static_methd()  # 170
Person().static_methd()  # 170
print(Person.__dict__)
# {'__module__': '__main__', 'class_method': <classmethod object at 0x10e29b080>, 'static_methd': <staticmethod object at 0x10e76b9e8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HEIGHT': 170}

5、方法的调用
类几乎可以调用所有内部定义的方法,但是调用 普通的方法 时会报错,原因是第一参数必须是类的实例;
实例也几乎可以调用所有的方法, 普通的函数 的调用一般不可能出现,因为不允许这么定义;

类除了普通方法都可以调用,普通方法需要对象的实例作为第一参数
实例可以调用所有类中定义的方法(包括类方法、静态方法),普通方法传入实例自身,静态方法和类方法需要找到实例的类

class Person:
    def normal_method():
        print('normal')
​
    def method(self):
        print('{} method'.format(self))
​
    @classmethod
    def class_method(cls):
        print('class={0.__name__}({0})'.format(cls))
        cls.HEIGHT=170
​
    @staticmethod
    def static_method():
        print(Person.HEIGHT)
​
tom=Person()
tom.method()  # <__main__.Person object at 0x109afab00> method
​
# 无论是类还是实例调用类方法,都会传入类
Person.class_method()  # class=Person(<class '__main__.Person'>)
tom.class_method()     # class=Person(<class '__main__.Person'>)
​
Person.static_method()  # 170
tom.static_method()  # 170
​
print(Person.__dict__)
# {'__module__': '__main__', 'normal_method': <function Person.normal_method at 0x107c6b730>, 'method': <function Person.method at 0x107c6b7b8>, 'class_method': <classmethod object at 0x107c72978>, 'static_method': <staticmethod object at 0x107c729e8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HEIGHT': 170}

三、访问控制

1、私有属性 Private
使用双下划线开头的属性名,就是私有属性;以防外部访问这个属性,破坏本身对属性的控制;私有变量离开当前的类就不能访问;

私有变量的本质:类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器会将其改名,转换名称为 _类名__变量名 的名称,所以用原来的名字访问不到了,这个类指私有变量自己所在的类;知道了私有变量的新名称,就可以直接从外部访问到,并可以修改它;

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
​
    def growup(self, i=1):
        if i>0 and i<150:# 控制逻辑
            self.__age += i
​
    def getage(self):
        return self.__age
​
p1 = Person('tom') 
p1.growup(20) 
print(p1.__dict__)  # {'name': 'tom', '_Person__age': 38}
# print(p1.__age) # 报错 'Person' object has no attribute '__age'
print(p1.getage())  # 38
 
p1.__age = 28
print(p1.__age)  # 28
print(p1.getage())  # 38
print(p1.__dict__)  # {'name': 'tom', '_Person__age': 38, '__age': 28}
​
# 直接修改私有变量 
p1._Person__age = 15 
print(p1.getage())  # 15
print(p1.__dict__)  # {'name': 'tom', '_Person__age': 15, '__age': 28}

2、保护变量
在变量名前使用一个下划线,称为保护变量;__dict__可查看一切;改名仅限在类定义范围中实现;
保护变量不会被改变名称,它和普通的属性一样,解释器不做任何特殊处理;这只是开发者共同的约定,看见这种变量,就如同私有变量,不要直接使用;

3、私有方法
使用单下划线、双下划线命名方法;
单下划线的方法只是开发者之间的约定,解释器不做任何改变; 双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同, _类名__方法名 ; 方法变量都在类的 dict 中可以找到;

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

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

四、补丁

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

猴子补丁
Monkey Patch:在运行时,对属性、方法、函数等进行动态替换
其目的往往是为了通过替换、修改来增强、扩展原有代码的能力;黑魔法,慎用。

五、属性装饰器

一般好的设计是:把实例的属性保护起来,不让外部直接访问,外部使用getter读取属性和setter方法设置属性;比如下例;还有Python提供的属性装饰器;注意使用property装饰器的时候这三个方法同名;

1、属性property装饰器
后面跟的函数名就是以后的属性名;它就是getter;
这个必须有,有了它至少是只读属性;
property装饰器本质上是个类,它能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果;

2、属性setter装饰器
与属性名同名,且接收2个参数,第一个是self,第二个是将要赋值的值;
有了它,属性可写

3、属性deleter装饰器
可以控制是否删除属性;很少用
property装饰器必须在前,setter、deleter装饰器在后

# 通过age和set_age方法操作属性,这是原始的方法调用
class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
    
    def age(self):
        return self.__age
​
    def set_age(self, age):
        self.__age = age
​
tom = Person('Tom')
print(tom.age())  # 18,注意是方法调用
tom.set_age(20)  
print(tom.age())  # 20


# 属性装饰器
class Person:
    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')
​
tom = Person('Tom')
print(tom.age)  # 18
tom.age = 20
print(tom.age)  # 20
del tom.age  #  del


# 属性装饰器的另一种写法
class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
​
    def getage(self):
        return self.__age
​
    def setage(self, age):
        self.__age = age
​
    def delage(self):
        # del self.__age
        print('del')
​
    age = property(getage, setage, delage, 'age property')
​
​
tom = Person('Tom')
print(tom.age)  # 18
tom.age = 20
print(tom.age)  # 20
del tom.age  # del
​

# 另一种写法
class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
​
    age = property(lambda self:self.__age)    # 相当于getter,依然只读,不能set
    
    def setage(self, age):
        self.__age = age
    
    age = property(age, setage)
​
tom = Person('Tom')
print(tom.age)  # 18

六、对象的销毁

del 方法
类中可以定义 del 方法,称为析构函数(方法)
作用:销毁类的实例的时候调用,以释放占用的资源;其中就放些清理资源的代码,比如释放连接、文件描述符等并不做对象的消亡,和__init__构造函数的准备工作相对;

注意这个方法不能引起对象的真正销毁,只是对象销毁的时候会自动调用它;
使用del语句删除实例,引用计数减1;当引用计数为0时,会自动调用 del 方法;由于Python实现了垃圾回收机制,不能确定对象何时执行垃圾回收;

由于垃圾回收对象销毁时,才会真正清理对象,还会在回收对象之前自动调用 del 方法,除非你明确知道自己的目的,建议不要手动调用这个方法;

七、方法重载overload

其他面向对象的高级语言中,会有重载的概念;
所谓重载,就是同一个方法名,但是参数数量、类型不一样,就是同一个方法的重载;
Python没有重载!Python不需要重载!

Python中,方法(函数)定义中,形参非常灵活,不需要指定类型(就算指定了也只是一个说明而非约束),参数个数也不固定(可变参数);一个函数的定义本身就可以实现很多种不同形式实参的调用;所以Python不需要方法的重载,或者说Python本身就实现了其它语言的重载;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值