Python面向对象基础


引言: 面向对象编程—Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。面向对象的设计思想来源于自然界,自然界中的生物有其对应的种属,例如豹子属于豹属生物(类),而正在撒哈拉沙漠觅食的一头猎豹则是豹属生物类的一个实例。而在东非大草原上追逐斑马的一头猎豹同样属于豹属生物类的一个实例。下面我们通过代码来实现!

class Panthera(object):
    """
        豹属动物类
    """
    def __init__(self,addr):
        self.addr = addr
        print(f"这是正在{addr}的一头猎豹")


if __name__ == '__main__':
    # 正在撒哈拉沙漠觅食的一头猎豹
    Panthera1 = Panthera("撒哈拉沙漠觅食")
    # 在东非大草原上追逐斑马的一头猎豹
    Panthera2 = Panthera("东非大草原上追逐斑马")

这是正在撒哈拉沙漠觅食的一头猎豹
这是正在东非大草原上追逐斑马的一头猎豹

类的实例化

从上述代码可以看出,类的定义是通过关键字“class 类名A(A继承的类):”

类名首字母大写是约定俗成的。A的父类默认是object,即所有类公共的父类,在Python3中不强制要求写出object,默认为object。 详情参考Python2与Python3关与object的区别

通过“类名(参数列表)”完成的,在这个过程又分为两个阶段。

  1. 调用____new____(cls):在创建实例前调用,至少有一个参数为要实例化的类,此参数在实例化时由Python解释器自动提供。作用是返回一个实例
  2. 调用____init____():在实例创建后调用,作用是初始化实例,self即为____new____的返回值,并在此基础上为一些参数赋值

下面主要介绍一下我们应该怎样依靠自己的能力去查询未知的方法呢?

首先前后两个下划线的方法是内建方法,所以我们在IDLE交互式界面中输入

>>> help(__builtins__.__new__)
Help on built-in function __new__:

__new__(*args, **kwargs) method of builtins.type instance
    Create and return a new object.  See help(type) for accurate signature

根据提示,我们再去type中更详细的查询,道理也很显然,Python3的每个都属于<class ‘type’>,我们可以在上述代码中通过下面两行代码得出

print(type(Panthera))
print(type(Panthera1))

终于在type模块中找到了init和new的详细解释

help(type)

init(self, /, *args, **kwargs)
| Initialize self. See help(type(self)) for accurate signature.

Static methods defined here:
|
| new(*args, **kwargs)
| Create and return a new object. See help(type) for accurate signature.

上面的案例中每个豹子实例有两个参数,第一个是self,第二个是addr(豹子所在地域)。self不需要人为传递Python解释器自己会把实例变量传进去(通过new),其余参数在实例化时必须一一对应传入。

类和实例属性

在实属性上python默认没有严格限制,除了在____init____里初始化参数,还可以动态创建属性

Panthera1.name = 'A'
print(Panthera1.name)

动态修改属性

del Panthera1.name
print(Panthera1.name)

类的属性又是什么呢?每头猎豹所在的地域不同,但它们都有一些相同的属性,不论在哪里,所有的猎豹的此属性都相同,这就是类的属性。在类中直接定义

class Panthera(Cat):
    """
        豹属动物类
    """
    father = 'cat'

    def __init__(self, addr):
        self.addr = addr
        print(f"这是正在{addr}的一头猎豹")
        
    def __new__(cls) -> Any:
        return super().__new__(cls)

与实例属性相同,类属性也可以做类似的增删改查,方法与实例属性类似。唯一要注意的是,实例属性最好不要与类属性同名,否则会覆盖掉类属性。

  • 要查找的实例属性不存在时,会寻找名称相同的类属性
  • 当实例属性与类属性同名时,使用实例.属性名返回的是实例属性,类.属性名返回的是类属性
  • 类属性只能通过类对象修改

我们可以看到,属性的使用随意性极大,这不利于程序的安全性和可靠性。对于某些属性应该有严格的访问和修改限制。

私有属性:属性前加两个下划线

私有属性不能直接通过实例对象调用,为了引用私有属性,可以在类中定义访问和修改其的函数,还可以在函数中对参数做限制

class Panthera(object):
    """
        豹属动物类
    """
    father = 'cat'

    def __init__(self, addr):
        self.__addr = addr
        print(f"这是正在{addr}的一头猎豹")

    def get_Addr(self):
        return self.__addr

    def set_Addr(self, addr):
        if isinstance(addr, int):
            raise Exception("地域必须为str")
        self.__addr = addr


if __name__ == '__main__':
    # 正在撒哈拉沙漠觅食的一头猎豹
    Panthera1 = Panthera("撒哈拉沙漠觅食")
    # 在东非大草原上追逐斑马的一头猎豹
    Panthera2 = Panthera("东非大草原上追逐斑马")
    print(Panthera1.get_Addr())

小结:

  1. 前后双下划线为特殊变量,可以直接访问
  2. 前双下划线为私有变量,不能直接访问(在某些编译器中对象._类名__属性名可以访问)
  3. 前单下划线为私有变量,可以直接访问。但约定俗称不要直接访问此类变量
  4. Python没有真正意义上的私有变量,与Java不同

封装

对于一种抽象事物,除了其本身的特有属性,还有一些特有行为,这些行为就是方法,方法分为类方法和实例方法。这些方法均在类中定义,引用这些方法的人并不知道方法的内在实现细节,这种思想就是封装。

上述代码中的get_addr()和set_addr()就属于封装的一种。顺便引出方法

实例方法:在类中定义的方法都属于实例方法。实例方法的第一个形参必须是self,其余形参根据情况定义。

类方法:通过注解@clasmethod表明一个形参为cls的方法为类方法。cls由python解释器自动传入类自身,但类和实例对象都可调用

@classmethod
def clear(cls):
    print("这是豹属动物类")

继承

继续前面的背景;进一步,属于豹属生物(类)的生物必然也属于猫科(类)。所以我们定义猫科类,豹属类的父类就是猫科类,从生物学上看,猫科动物大多喜独居。肉食,常以伏击方式捕杀其他温血动物。豹子和其他猫科动物同样具有这些特点,那是否要在猫科动物的子类都写一遍同样的方法呢?

class Cat(object):
    """
    猫科动物类
    """

    def __init__(self, family):
        self.family = family

    # pycharm一般会将没有引用属性的类看做静态方法
    def habit1(self):
        print(f"我是{self.family}动物,我喜欢独居,是肉食性动物")

    def clear(self):
        print("这是猫科动物")


class Panthera(Cat):
    """
        豹属动物类
    """
    father = 'cat'

    @classmethod
    def clear(cls):
        print("这是豹属动物类")

    def __init__(self, addr, family):
        super().__init__(family)
        self.__addr = addr
        print(f"这是正在{addr}的一头猎豹")

    def get_Addr(self):
        return self.__addr

    def set_Addr(self, addr):
        if isinstance(addr, int):
            raise Exception("地域必须为str")
        self.__addr = addr


if __name__ == '__main__':
    # 正在撒哈拉沙漠觅食的一头猎豹
    Panthera1 = Panthera("撒哈拉沙漠觅食", "猫科")
    # 在东非大草原上追逐斑马的一头猎豹
    Panthera2 = Panthera("东非大草原上追逐斑马", "猫科")
    # print(Panthera1.get_Addr())
    Panthera1.clear()
    print(Panthera2.family)

子类后的括号内要写出父类的名称,在子类的____init____方法中要通过super().____init____引用父类方法,从而使子类实例拥有父类的属性。父类也称为超类(superclass),名称super因此而得名。子类(Subclass)

子类重写父类的方法,当父类的方法不适合子类时,即可在子类中定义与父类方法名相同的方法,修改方法内部实现。因此调用子类方法时,将会调用子类自身的方法。

多态

1.顾名思义,一个对象有多种状态,拿子类来说。子类既属于本身又属于父类,但父类不属于子类

print(isinstance(Panthera1, Cat))
print(isinstance(cat,Panthera))

2.传参时,参数是父类的实例,但子类对象仍可以做实参

def fight(cat):
    cat.habit1()
    
if __name__ == '__main__':
    # 正在撒哈拉沙漠觅食的一头猎豹
    Panthera1 = Panthera("撒哈拉沙漠觅食", "猫科豹属")
    cat = Cat("猫科")
    fight(cat)
    fight(Panthera1)    

再创建一个老虎类,重写habit1()方法

class Tiger(Cat):
    """
    虎属
    """
    def __init__(self, family):
        super(Tiger, self).__init__(family)

    def habit1(self):
        print(f"我是{self.family}动物,我是万兽之王,是肉食性动物")
        
if __name__ == '__main__':
    tiger = Tiger("虎属")
    fight(tiger)

发现,fight函数依然可以使用,这样无论创建多少个子类,都可以作为fight()的参数。这就是著名的“开闭”原则:

对扩展开放:允许新增Cat子类;

对修改封闭:不需要修改依赖Cat类型的fight()等函数。

3.动态语言的“鸭子类型”

对于fight()函数来说,传入参数只有有habit1()函数即可;对于Java等静态语言,传入参数有严格的限制,必须是其本身或子类。

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值