Python基础 - 面向对象

katyusha 2020-02-18

引入

Python 既是面向过程, 也能面向对象.

初学来理解为啥要面向对象, 不太可能, 用处呢, 是在工程项目中, 代码优化, 封装等.. 才会真正理解哦.

面对对象3大特点:封装 继承 多态
面对对象的一个关键性观念在于,将数据及对数据的操作封装在一起,组合成一个相互依存,不可分割的整体.
不同对象间,通过消息机制来通信或者同步;对于同类型对象(instance)进行分类, 抽象后,得出共同特征而形成了类
面对对象程序设计关键在于如何合理地定义这些类并且组织多个类之间的关系

创建类时用变量形式表示对象特征的成员成为数据成员(attribute); 用函数形式表示对象行为的成员称为**成员方法(method)

类的定义与使用

class Car:
    def info(self):
        print('This is a car.')
car = Car()
car.info()
This is a car.
isinstance(car, Car)  # 是否是某类的实例
True
isinstance(car, str)
False
type(car)  # 是一个对象类型
__main__.Car
33

数据成员与成员方法

Python 中并没有对私有成员提供严格的访问保护机制,通过一种特殊的方式"**对象名.__类名__xxx"也可以在外部访问**

私有成员与方法

class A:
    def __init__(self, value1=0, value2=0):  # 构造方法
        self._value1 = value1  
        self.__value2 = value2  # 私有属性
        
    def set_value(self, value1, value2):  # 公有方法 
        """给私有属性传值"""
        self._value1 = value1  # 在类的内部,可以直接访问私有成员
        self._value2 = value2
        
    def show(self):  
        """间接给外部访问私有成员"""
        print(self._value1)
        print(self.__value2)      
    
a1 = A()
a1._value1  # 在类外部可以直接访问非私有成员
0
a1.__value2  # 在类外部不可以直接访问私有成员
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-11-286155ff3344> in <module>
----> 1 a.__value2  # 在类外部不可以直接访问私有成员


AttributeError: 'A' object has no attribute '__value2'
a1.show()  # 但可以通过间接方法去访问,不过这有些不可控
0
0
a1._A__value2  # Python 的大BUG, 根本不存在私有的,不存在的
0
a1._A__Value2 = 'cj'  # 于是,什么私有啥的,就轻松被改掉了
a1._A__Value2
'cj'
a1._A__value2  # 但是为什么第二次查看就没了???
0
a2 = A()
a2._A__value2
0

哦,我好像知道单例模式能干嘛了,就是改掉一个东西,所以东西都要跟着变呢 重写new方法

# 单例模式测试
class A:
    
    __instance = None  # 定义一个类属性
    
    def __new__(cls):  # 类方法是cls,实例方法是self
        if cls.__instance is None:
            cls.__instance = object.__new__(cls)
        return cls.__instance
        
    def __init__(self, value1=0, value2=0):  # 构造方法
        self._value1 = value1  
        self.__value2 = value2  # 私有属性
        
    def set_value(self, value1, value2):  # 公有方法 
        """给私有属性传值"""
        self._value1 = value1  # 在类的内部,可以直接访问私有成员
        self._value2 = value2
        
    def show(self):  
        """间接给外部访问私有成员"""
        print(self._value1)
        print(self.__value2)      
    
a1 = A()
a2 = A()
a3 = A()
print(a1._value1, a2._value1)  # 正常访问实例属性值
0 0
a1._value1 = 'CJ'  # 改变a1的共有实例属性
print(a1._value1, a2._value1, a3._value1)  # 厉害了,a2 a3也跟着变了, 有点像飞机大战,给100架飞机(对象)改颜色,厉害了
CJ CJ CJ
print(dir(A), end=' ')  #  dir(obj) 查看对象成员
['_A__instance', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'set_value', 'show']
print(dir(a1), end = ' ')
['_A__instance', '_A__value2', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_value1', 'set_value', 'show']
  • _ xxx,保护成员,只有类, 子类可以访问这些成员数据,当然外部也可以,但不建议
  • 一或多个下划线的成员不能 from module import* 除非指定 __ all __ = [data1, data2 ]说明可以被导入
  • __ xxx__ 系统定义的特殊成员
  • __ xxx 为私有成员,只有在类内部能访问也不能继承,但可以内部定义方法,间接访问
  • Python其实并不存在私有成员, 在外部可通过对象名._ 类名__ xxx 这样的特殊形式访问

数据成员

  • 类数据成员(类属性 类方法) 对象数据成员(实例对象属性 实例对象方法)
  • 同一个类的不同对象(实例)的数据成员(属性,方法)互不影响(除非单例模式)
  • 类的数据成员(类属性, 类方法)是该类所有对象共享的,一般不在任何一个成员方法中定义
  • 主程序中(类外部),对象数据成员属于实例,只能通过对象名访问; 类数据属于类,可以通过类名或对象名访问

利用类数据成员的共享性, 可是实时获得该类的对象数量, 并且可以控制类创建的对象最大数量

class Demo(object):
    total = 0  # class attribute
    def __new__(cls, *args, **kwargs):
        if cls.total >= 3:
            raise Exception("You can only creat up to 3 objects.")  # 自定义异常
        else:
            return object.__new__(cls)  # 在范围内则不重写object的__new__方法
        
    def __init__(self):
        Demo.total += 1  # 类去访问类属性,作用于所有的实例对象
        
t1 = Demo()
t1  # 实例化对象,返回的是一个地址
<__main__.Demo at 0xb1b789c978>
t2 = Demo()
t3 = Demo()
t4 = Demo()  # 超出了3个(condtion),会被__new__方法限制
---------------------------------------------------------------------------

Exception                                 Traceback (most recent call last)

<ipython-input-4-44c1d26c9790> in <module>
----> 1 t4 = Demo()  # 超出了3个(condtion),会被__new__方法限制


<ipython-input-1-c52d2c3e245b> in __new__(cls, *args, **kwargs)
      3     def __new__(cls, *args, **kwargs):
      4         if cls.total >= 3:
----> 5             raise Exception("You can only creat up to 3 objects.")  # 自定义异常
      6         else:
      7             return object.__new__(cls)  # 在范围内则不重写object的__new__方法


Exception: You can only creat up to 3 objects.
x = 10
if x > 20:
    raise Exception("this is a wrong message created by myself")   # raise 好像我还不会用呢

成员方法 类方法 静态方法

在面对对象程序设计中,函数和方法是有本质区别的.方法一般指与特定实例绑定的函数, 通过对象调用方法时, 对象本身将会被作为第一个参数自动传递过去,普通函数则不具备这个特点.例如,内置函数sorted()必须要指明要排序的对象, 而对象的sort()方法则不需要, 默认是对当前序列进行排序.

class Demo:
    pass

t = Demo()
t
<__main__.Demo at 0xb1b20c6358>
def test(self, v):
    """动态增加类属性"""
    self.value = v
t.test = test  # 动态增加普通函数 看不懂这段代码???
t.test
<function __main__.test(self, v)>
t.test(t, 3) # 需要我self传递参数 ???
print(t.value)
3

看不懂下面这段代码

import types

t.test = types.MethodType(test, t)  # 动态增加绑定的方法
t.test
<bound method test of <__main__.Demo object at 0x000000B1B20C6358>>
t.test(5)  # 不需要为self 传递参数
print(t.value)
5
  • Python类成员方法大致可分为:**公有方法, 私有方法, 静态方法, 类方法
  • 公有, 私有方法指属于对象的实例方法,私有方法一般以两个或多个下划线开头,它们都可以访问属于类和对象的成员
  • 公有方法通过对象名直接调用,而私有方法只能在其他实例方法中通过前缀self进行调用,或在外部通过特殊形式调用(对象名._ 类__xxx)
  • Python还支持大量特殊方法__ xxx___,往往与某个运算符或内置函数相对应

  • 所有实例方法(私有, 公有, 抽象, 特殊, 类方法等),都必须至少有一个名为self的参数,必须是第一个形参代表当前对象
  • 内部访问类成员需要self,外部则不需要, 在外部通过对象名调用公有方法时,需要显式为该方法的self,传递一个对象名, 用来指定访问哪个对象的成员(反恐精英,人在人里不是人,是self, 人在枪里是才是人,是形参)
  • 静态方法和类方法都可以通过类名和对象名调用,但不能直接访问属于哪个对象的成员,只能访问属于类的成员
  • 它们不属于任何实例不会绑定到任何实例,与实例方法相比,能够减少很多开销
  • 类方法以cls作为第一个参数表示该类自身,在调用时不需要要为其传值,静态方法可以不接受任何参数

class Root:
    __total = 0
    def __init__(self, v):  # 构造方法
        self.__value = v
        Root.__total += 1  # 给类属性传值
        self.x = 0
        
    def show(self):  # 普通实例方法,以self作为第一个参数
        print('self.__value:', self.__value)
        print('Root.__total:', Root.__total)
        
    @classmethod  # 修饰器,声明类方法
    def class_show_total(cls):  # 类方法以cls作为第一个参数
        print(cls.__total)
        
    @staticmethod    # 修饰器,声明静态方法
    def static_show_total():  # 静态方法,可以没有参数
        print(Root.__total)
a1 = Root(666)
a1.show()
self.__value: 666
Root.__total: 3
a1.class_show_total()  # 通过实例对象来调用类方法
3
r.static_show_total()  # 通过实例对象调用静态方法
3
r2 = Root(999)
Root.class_show_total()  # 通过类名调用类方法
1
Root.static_show_total()  # 通过类名调用静态方法
1
Root.show()   # 通过类名不能调用实例方法
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-52-c0fdb99b2d01> in <module>
----> 1 Root.show()   # 通过类名钓友实例方法,失败


TypeError: show() missing 1 required positional argument: 'self'
Root.show(r2)   # 通过 类名.实例方法名(对象) 来进行调用
self.__value: 999
Root.__total: 1

实例对象可以访问实例属性 实例方法 类属性 类方法 静态方法; 类名可以访问类方法 类属性 静态方法, 不能直接访问实例方法 实例属性

Root.x  # 通过类名不能直接访问实例属性
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-55-5e11f75167b2> in <module>
----> 1 Root.x  # 通过类名不能直接访问实例属性


AttributeError: type object 'Root' has no attribute 'x'

属性 property

属性(property) 是一种特殊形式的成员方法,结合了公开数据成员和成员方法的优点, 既可以像成员方法那样对值进行必要的检查,又可以像数据成员一样灵活访问
Python3x中, 如果设置属性为只读则无法修改其值,也无法为对象增加与属性同名的新成员,也无法删除对象属性(不能增, 改, 删)

class Test:
    def __init__(self, value):
        self.__value = value  # 私有数据成员
            
    @property  # 修饰器, 定义属性,提供对私有数据成员的访问
    def value(self):  
        return self.__value  # 只读属性,无法增, 改, 删
t = Test(666)
t.value  # 只读属性
666
t.value = 999  # 不允许修改值
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-62-cb004e267824> in <module>
----> 1 t.value = 999  # 允许修改值


AttributeError: can't set attribute
del t.value  # 不允许删除值
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-63-ec615fe9a488> in <module>
----> 1 del t.value  # 不允许删除值


AttributeError: can't delete attribute
t.value
666

将其改成,可读,可修改,不可以删除

class Test:
    def __init__(self, value):
        self.__value = value
        
    def __get(self):
        """读取私有数据成员的值"""
        return self.__value  
    
    def __set(self, v):
        """修改私有数据成员的值"""
        self.__value = v
        
    value = property(__get, __set) # 可读可改, 指定相应的读写方法
    
    def show(self):
        print(self.__value)
        
t = Test(666)
t.__value  # 不能直接访问私有属性
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-66-e41fedfc61d4> in <module>
----> 1 t.__value


AttributeError: 'Test' object has no attribute '__value'
t.show()  # 可通过间接方法的方式来访问
666
t.__get()  # 不可以访问私有方法
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-69-d63121715559> in <module>
----> 1 t.__get()


AttributeError: 'Test' object has no attribute '__get'
t.value = 'cj'  # property,这里允许修改属性值
t.show()  # 对应的私有变量也得到了修改
cj
del t.value  # 不允许删除属性
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-72-cd1108188511> in <module>
----> 1 del t.value


AttributeError: can't delete attribute

将私有属性设置为可读, 可修改, 可删除

class Test:
    def __init__(self, value):
        self.value = value
        
    def __get(self):
        """访问私有数据成员"""
        return self.__value
    
    def __set(self, v):
        """修改私有数据成员"""
        self.__value = v
        
    def __del(self):
        """删除私有数据成员"""
        del self.__value
        
    value = property(__get, __set, __del)  # 可读, 可写, 可删除的属性
    
    def show(self):
        print(self.__value)
        
t = Test(999)
t.show()  # 间接访问私有成员的方法
999
t.value  # 可读
999
t.value = 'cj'  # 可改
t.value
'cj'
del t.value  # 可删除
t.value
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-78-02db3359037c> in <module>
      1 del t.value  # 可删除
----> 2 t.value


<ipython-input-73-674abf9bc7e5> in __get(self)
      5     def __get(self):
      6         """访问私有数据成员"""
----> 7         return self.__value
      8 
      9     def __set(self, v):


AttributeError: 'Test' object has no attribute '_Test__value'
t.show()  # 对象值都删除了,还访问个毛毛呀
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-79-845e971b547d> in <module>
----> 1 t.show()  # 对象值都删除了,还访问个毛毛呀


<ipython-input-73-674abf9bc7e5> in show(self)
     18 
     19     def show(self):
---> 20         print(self.__value)
     21 
     22 t = Test(999)


AttributeError: 'Test' object has no attribute '_Test__value'

继承 多态

子类/派生类可以继承父类的所有公有成员,不能继承其私有成员
可用super().方法(), 或者基类名.方法名() 实现子类的追加,而不覆盖基类的数据成员

要求:设计Person类, 并根据Person派生Teacher类,并分别创建Person类与Teacher类的对象

# 基类:
class Person:
    def __init__(self, name=' ', age=20, sex='man'):
        """通过调用方法进行初始化,可实现对参数更好地控制"""
        self.set_name(name)
        self.set_age(age)
        self.set_sex(sex)
        
    def set_name(self, name):
        """校验name必须是string"""
        if not isinstance(name, str):  # 如果,name 不是 str的实例(非字符串,报错)
            raise Exception("name must be a string.")
        self.__name = name
    
    def set_age(self, age):
        """校验age必须是integer"""
        if type(age) != int:
            raise Exception("age must be an integer.")
        self.__age = age
    
    def set_sex(self, sex):
        """校验sex必须是man or woman"""
        if sex not in ('man', 'woman'):
            raise Exception("sex must be 'man' or 'woman'")
        self.__sex = sex
        
    def show(self):
        print(self.__name, self.__age, self.__sex, sep='\n')  # 换行输出
        

# 派生类
class Teacher(Person):  # 继承 Person类
    def __init__(self, name='', age=22, sex='man', department='Computer'):
        
        super().__init__(name, age, sex)  # 给子类用父类的方法,同时实现子类的追加 department属性
#         Person.__init__(self, name, sex) 这是第二种写法
        self.set_department(department)
    
    def set_department(self, department):
        """校验department必须为string"""
        if type(department) != str:  # 等同于 mot isinstance(department, str)
            raise Exception("department must be a string.")
            
        self.__department = department
        
    def show(self):
        super().show()  # 不覆盖父类,并在其之上append
        print(self.__department)
        
        
if __name__ == '__main__':
    # 创建父类对象
    cj = Person("cj", 22, 'man')
    cj.show()
    print('==' * 10)
    
    # 创建子类对象
    cj1 = Teacher('cj1', 23, 'man', 'Math')
    cj1.show()
    # 调用继承的方法修改年龄
    print("==" * 10)
    cj1.set_age(24)
    cj1.show()
cj
22
man
====================
cj1
23
man
Math
====================
cj1
24
man
Math
type(22)
int

多继承时,D(A,B,C),重名时会从左到右搜索,使用第一个匹配成功的成员

多态

多态(polymorphism)指基类的同一个方法,在不同派生类对象中具有不同的表现和行为 龙生九子,子子皆不同

# 在子类中重写父类方法,实现多态
class Animal:
    "基类"
    def show(self):
        print('I am an animal.')
        

class Cat(Animal):
    def show(self):
        """重写(覆盖)了父类的show方法"""
        print('I am a cat.')
        
        
class Tiger(Animal):
     def show(self):
        """重写(覆盖)了父类的show方法"""
        print('I am a tiger.')
        
        
class Test(Animal):
    pass

x = [i() for i in (Animal, Cat, Tiger, Test)]  # 列表推导式,对象,有点意思

for i in x:
    i.show()
I am an animal.
I am a cat.
I am a tiger.
I am an animal.

特殊方法与运算符重载

目前我的水平,除了用__init(), new(), del(), mro__()其余的好像也暂时搁着吧先

小结

  • 面对对象程序设计的关键是如何合理定义类并组织多个类之间的关系

  • Python 是面向对象的解释性高级动态编程语言,完全支持面向对象的基本功能和全部特性,如封装, 继承, 多态

  • __ xxx表示私有成员, __ xxx __ 表示特殊方法

  • 类外部不能直接访问私有成员,可通过类内部定义访问方法或者通过对象名._ 类名__ 私有成员名 来访问

  • 函数和方法这两个概念有本质区别(传参self)

  • 所有实例方法必须以self作为第一个参数

  • 属性property是一种特殊形式的成员方法,结合了公开数据成员和成员方法两者的优点

  • 如要做派生类/子类中调用基类方法,可使用内置函数super().方法名() 或者通过基类名.方法名()的形式

  • 多态是指基类的同一个方法在不同的派生类中,具有不同的表现和行为

  • Python 类的特殊方法与特定的内置函数或运算符相对应, **在自定义类中实现了某个特殊方法,就支持了某个运算符和内置函数

相关推荐