Python 面向对象基础

1. 类

和 Java 一样,所有类都默认继承 object,object 是最顶层的父类。

1.1 实例属性和类属性

实例属性通过 self 关键字定义,类属性没有 self 修饰,直接写在类中。**类属性一定要通过类名调用,不要使用对象名调用。**例子如下:

class A:
    aa = 1
    def __init__(self, x, y):
        self.x = x
        self.y = y

a = A(2,3)

A.aa = 11
a.aa = 100
print(a.x, a.y, a.aa)
print(A.aa)

b = A(3,5)
print(b.aa)

1.2 私有属性和私有方法

Python 中没有 private 关键字,属性或方法的私有都是通过双下划线完成的。定义为私有的属性和方法不能被子类继承。
题外话,Python 中会将双下划线修饰的属性变形为 _类名__属性名 这种命名方式,如果主动使用这种方式也可以调用到想要的属性,但是开发中不要这样做,调用私有属性需要通过提供的 getter 和 setter 方法。例子如下:

from class_method import Date
class User:
    def __init__(self, birthday):
        self.__birthday = birthday

    def get_age(self):
        #返回年龄
        return 2018 - self.__birthday.year


if __name__ == "__main__":
    user = User(Date(1990,2,1))
    print(user._User__birthday)
    print(user.get_age())

1.3 构造函数

构造函数通过 init 方法定义。例子如下:

class Date:
    # 构造函数
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

1.4 获取类或者对象的内部结构

  1. 方法一:dict 能够获取类或者对象的内部结构。
  2. 方法二:使用 dir 内置函数,这种方式更加强大。

例子如下:

#自省是通过一定的机制查询到对象的内部结构
from chapter04.class_method import Date
class Person:
    """
    人
    """
    name = "user"

class Student(Person):
    def __init__(self, scool_name):
        self.scool_name = scool_name

if __name__ == "__main__":
    user = Student("慕课网")

    #通过__dict__查询属性
    # print(user.__dict__)
    # user.__dict__["school_addr"] = "北京市"
    # print(user.school_addr)
    # print(Person.__dict__)
    # print(user.name)
    a = [1,2]
    print(dir(a))

1.5 判断一个对象是否具有某个属性

hasattr 方法

#我们去检查某个类是否有某种方法
class Company(object):
    def __init__(self, employee_list):
        self.employee = employee_list

    def __len__(self):
        return len(self.employee)


com = Company(["bobby1","bobby2"])
print(hasattr(com, "__len__"))

1.6 一切皆对象

一切皆对象其实是在探讨 type、object、class 之间的关系。

在这里插入图片描述

理解上图,首先需要弄明白两个点:第一个是类的创建者,第二个是类的父类。这里,创建者是 type。父类是 object。
上图中描述了三点:
第一:object 和 type 之间的关系:object 是 type 的是一个实例,对象。
第二:object 与 其他所有类的关系(包括 type):object 是最顶层的父类,object 的父类是空;
第三:type 与自身的关系:type 是一个类,但同时 type 也是自己的一个对象;
延伸:type 是一个类,同时又是自己的一个对象,这里可以通过 C 语言的指针来理解,具体的实现是用的指针。
上述三点构成了一切皆对象。
为什么说 list 除了是一个类之外,还说 list 是一个对象?这里的list 也可以换成是其他类,如 str、dict。原因如下:

  1. 所有的内部类都是由 type 创建出来的;
  2. type 创建自定义类,自定义类再创建一个对象;

2. 继承 inheritance

2.1 继承的实现

Python 的继承不需要关键字,只需要在类名后面直接添加父类名称即可。例子如下:

class A:
    def __init__(self):
        print ("A")

class B(A):
    def __init__(self):
        print ("B")
        super().__init__()

2.2 判断对象是否是一个类的实例

Python 中提供了 isinstance 关键字完成这一需求,类似于 Java 中 instanceof 关键字。不要使用 type 配合 is 关键字的方式。 isintance 会查找继承链,而 is 不会,is 只判断对象是否指向同一个内存地址。

class A:
    pass

class B(A):
    pass

b = B()

print(isinstance(b, B))
print(isinstance(b, A))

print(type(b) is A)

2.3 super 真的是调用父类吗?(重点)

答案:不是,super 调用的是 mro 函数的下一个类的构造函数。MRO 全称 Method Resolution Order。

class B(A):
    def __init__(self):
        print("B")
        super().__init__()


class C(A):
    def __init__(self):
        print("C")
        super().__init__()


class D(B, C):
    def __init__(self):
        print("D")
        super(D, self).__init__()


if __name__ == "__main__":
    print(D.__mro__)
    d = D()

由于 Python 允许多继承,多继承会存在菱形继承问题,会出现如下两种类型:
类型一:
在这里插入图片描述
上图中,A 同时继承 B 和 C,B 和 C 又都继承 D,假设 C 覆盖了 D 中的某个方法,记该方法为 M。在 Python 2 的非常早期版本中使用的是 DFS 方法搜索类的方法继承关系,使用 DFS 方法将导致 A 类的对象始终无法调用 C 类中重写的方法 M,调用的始终是 D 类中原始的方法 M。为了解决这个问题,Python 后来将 DFS 算法更新为 BFS 算法,广度优先搜索算法虽然能解决类型一的继承问题,但是却解决不了类型二这种情形。
类型二:
在这里插入图片描述
上图中,A 同时继承类 B 和 C,B 和 C 又分别继承 D 和 E 类,假设 D 类和 C 类中有同名方法 M。如果使用 BFS 方法会导致 A 类的对象依然会调用 C 类中的 M 方法,而这就导致了错误调用。正确的调用应该是 D 类中的 M 方法,因为 A 首先继承 B 类,B 类中存在它从 D 类中继承过来的方法 M,应该返回的调用方法是 B 类从 D 类继承而来的方法,而不是 C 类中的方法 M。
Python 现在的算法采用的是 C3 算法,解决了上述的两种菱形继承问题。

2.4 mixin 模式

尽管 Python 支持多继承,但是通常都不建议使用多继承方式。如果有多继承需求,通常采用 mixin 模式解决。
mixin模式特点:

  1. Mixin类功能单一
  2. 不和基类关联,可以和任意基类组合, 基类可以不和mixin关联就能初始化成功
  3. 在mixin中不要使用super这种用法

3. 多态 polymorphism

3.1 多态

在 Java 语言中,多态的实现是类之间必须继承关系,将子类实例化的对象赋值给父类,从而实现多态。而在 Python 语言中,不需要继承关系,类之间只需要有共同的方法即可,也就是所谓的鸭子类型。注意仔细体会下面的例子:

class Cat(object):
    def say(self):
        print("i am a cat")

class Dog(object):
    def say(self):
        print("i am a fish")

class Duck(object):
    def say(self):
        print("i am a duck")

animal_list = [Cat, Dog, Duck]
for animal in animal_list:
    animal().say()

3.2 鸭子类型

理解了上面的例子,我们来进一步理解鸭子类型。鸭子类型贯穿了 Python 语言的始终,关系到整个语言面向对象的设计理念。当我们定义了一个类之后,只要实现了魔法函数(以双下划线开始和结束的方法), Python 就可以识别出来,进而产生多态。下面的 Company 类实现了 getitemlen,故而可以被当做是一个 iteratable 类型。


class Dog(object):
    def say(self):
        print("i am a fish")

class Company(object):
    def __init__(self, employee_list):
        self.employee = employee_list

    def __getitem__(self, item):
        return self.employee[item]

    def __len__(self):
        return len(self.employee)

company = Company(["tom", "bob", "jane"])

dog = Dog()
a = ["bobby1", "bobby2"]

b = ["bobby2", "bobby"]
name_tuple = ["bobby3", "bobby4"]
name_set = set()
name_set.add("bobby5")
name_set.add("bobby6")
a.extend()
print(a)

4. 抽象基类

通过前面的学习,我们知道 Python 语言的多态都是围绕鸭子类型设计的,并不需要继承某一父类,只需要实现特定的方法即可,那么为什么还会有抽象基类这一概念呢?这一问题的答案,我们通过两个需求来说明。

  1. 判断一个对象是否是一个类的实例
  2. 强制某个子类必须实现某些方法
#我们去检查某个类是否有某种方法
class Company(object):
    def __init__(self, employee_list):
        self.employee = employee_list

    def __len__(self):
        return len(self.employee)


com = Company(["bobby1","bobby2"])
print(hasattr(com, "__len__"))

#我们在某些情况之下希望判定某个对象的类型
from collections.abc import Sized
isinstance(com, Sized)

第一点,开发过程中经常有需求。通过定义抽象基类,我们可以判断对象是否是某一对象的实例。上面的例子中,com 是 Sized 的实例。如果没有抽象基类这一概念,我们必须通过 hasattr(com, “len”) 方式判断这个实例是否包含某个属性,将 Sized 定义成 len 的抽象基类,我们就可以以 isinstance 这种面向对象、处处操作的粒度都是对象的方式进行。
第二点,我们在开发一个框架时经常会有这种需求,此时就需要设计一个抽象基类,指定子类都必须实现这些方法。下面,首先我们来看一下如何实现一个抽象基类。

#如何去模拟一个抽象基类
class CacheBase():
    def get(self, key):
        raise NotImplementedError
    def set(self, key, value):
        raise NotImplementedError
class RedisCache(CacheBase):
    def set(self, key, value):
        pass

redis_cache = RedisCache()
redis_cache.set("key", "value")

当子类调用 set 方法时,就会报错。**但是,这种方式的缺点是必须调用时才会报错。**如果想实例化时,没有实现特定的方法就报错呢?此时就需要使用 Python 的 abc 模块。

import abc
from collections.abc import *

class CacheBase(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def get(self, key):
        pass

    @abc.abstractmethod
    def set(self, key, value):
        pass

class RedisCache(CacheBase):
    def set(self, key, value):
        pass

redis_cache = RedisCache()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值