python学习交流

Pythonic OOP

2018-12-07  本文已影响16人  水之心

来源:AI 领域中的培养与开发 ——by 丁宁

1 关于双下划线

1.1 怎么读

1.2 双下划线开头和结尾的变量或方法叫什么

1.3 如何认识 Python 的 special method(摘自官方文档)

Allowing classes to define their own behavior with respect to language operators.

个人补充:尽可能的保证 Python 行为的一致性(Special Method 更像是一种协议)

2 从语言设计层面理解 Python 的数据模型

2.1 一切都是对象

2.2 Objects 的组成1:identity(标识)

a = 1
print(id(a))
a = 1.1
print(id(a))
a = 1
print(id(a))
b = 1
print(id(b))
c = 1.1
print(id(c))

下面是在 Ipython 中的结果:

1582197824
1547884955568
1582197824
1582197824
1547884955784

要点:

  1. 变量存的是创建的 object 的 identity
  2. 创建出来的不同的 object 有不同的 identity
  3. 变量的 id 变了不是因为 object 的 identity 变了,而是对应的 object 变了
  4. 对于 immutable object,计算结果如果已经存在可直接返回相同的 identity

注意:由于浮点数并不精确,所以上面的 1.1 的两个引用的 id 的不同的。

2.2 Objects 的组成2:type

2.3 Objects 的组成3:value

2.4 存放其他 Object 的 reference 的 Object :Container


3 Pythonic OOP with Special Method and Attribute

3.1 The implicit superclass – object & type object

class X:
    pass


class Y(X):
    pass


def main():
    x = X()
    y = Y()
    print(x.__class__.__name__)
    print(y.__class__.__name__)
    print(X.__class__.__name__)
    print(Y.__class__.__name__)
    print(x.__class__.__base__.__name__)
    print(y.__class__.__base__.__name__)
    print(X.__class__.__base__.__name__)
    print(Y.__class__.__base__.__name__)


if __name__ == "__main__":
    main()
X
Y
type
type
object
X
object
object

3.2 Integrating Seamlessly with Python

class X:
    pass


class Y:
    """Class Y"""

    def __str__(self):
        return "{} object".format(self.__class__.__name__)

    def __len__(self):
        return 10

    def __bool__(self):
        return False


def check_bool(x):
    if x:
        print("I'm {}. My bool value is True.".format(str(x)))
    else:
        print("I'm {}. My bool value is False.".format(str(x)))


def main():
    x = X()
    y = Y()
    print(x)
    print(y)
    # print(len(x))
    print(len(y))
    check_bool(x)
    check_bool(y)
    print(X.__doc__)
    print(Y.__doc__)


if __name__ == "__main__":
    main()
<__main__.X object at 0x0000016866469438>
Y object
10
I'm <__main__.X object at 0x0000016866469438>. My bool value is True.
I'm Y object. My bool value is False.
None
Class Y

要点:

  1. 之所以要实现 special method,是为了让自定义的 class 与 Python 的内置函数无缝衔接
  2. Python 有⼤大量的内置函数,而这些函数大部分都是调用的对象里的 special method
  3. 想查看 Python 中到底有多少 special method见 A Guide to Python's Magic Methods

4 Attribute Access and Properties

Attribute 相关的操作一般有:

数据库领域中的 CRUD 亦是如此

4.1 Level I:Basic Access(Default Access)

class X:
    pass


if __name__ == "__main__":
    # print(X.a)
    X.a = "a"
    print(X.a)
    X.a = "aa"
    print(X.a)
    del X.a
    # print(X.a)

说明:

  1. 默认情况下,CRUD 都⽀支持,而且是在 public 情况下都支持(除了双下划线开头的)
  2. 如果对象中没有这个 Attribute,访问时会报错

4.2 Level II:Property(长得像 Attribute 的 Method)(推荐使用)

Property 的设计初衷:

场景:我要减肥,需要监控 BMI 指标,但是只能测量体重,每天更新体重,隔几天看一次 BMI 指数

class X:
    def __init__(self, w, h):
        self.w = w  # 体重
        self.h = h  # 身高
        self.BMI = w / h**2


def main():
    x = X(75, 1.83)
    print(x.BMI)
    x.w = 74
    x.w = 73
    x.w = 72
    print(x.BMI)


if __name__ == "__main__":
    main()

输出:

22.395413419331717
22.395413419331717

并没有得到预期输出,原因是 __init__ 只执行一次。

改进⼀:

class X:
    def __init__(self, w, h):
        self.__w = w
        self.__h = h
        self.BMI = w / h**2

    def update_w(self, w):
        self.__w = w
        self._update_bmi()

    def _update_bmi(self):
        self.BMI = self.__w / self.__h**2


def main():
    x = X(75, 1.83)
    print(x.BMI)
    x.update_w(74)
    x.update_w(73)
    x.update_w(72)
    print(x.BMI)


if __name__ == "__main__":
    main()

便可得到预期输出:

22.395413419331717
21.49959688255845

分析:

  1. w 变为私有,更新需要通过对象方法类执行,并将 BMI 的更新放于其中,实现功能逻辑
  2. BMI 属性依旧可以被外部访问和修改
  3. w 相关的代码全部被更改
  4. 无论 BMI 属性是否被访问,每次 w 更新均会更新 BMI,造成一定的计算资源浪费

改进二:

class X:
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def get_bmi(self):
        return self.w / self.h**2


def main():
    x = X(75, 1.83)
    print(x.get_bmi())
    x.w = 74
    x.w = 73
    x.w = 72
    print(x.get_bmi())


if __name__ == "__main__":
    main()

分析:

  1. 保持 wh 属性可以随意更改,BMI 指数仅在被访问时实时计算出结果
  2. 访问 BMI 的方式由属性改为方法,造成一定程度上的代码修改
  3. w 更新频率高于 BMI 访问频率时,节省了计算资源
  4. w 未更新,却多次调用 BMI 指数时,造成了重复计算

改进三:

class X:
    def __init__(self, w, h):
        self._w = w  # 单下划线是告诉读者,最好不要改变
        self._h = h
        self._bmi = w / h**2

    def get_w(self):
        return self._w

    def set_w(self, value):
        if value <= 0:
            raise ValueError("Weight below 0 is not possible.")
         self._w = value
         self._bmi = self._w / self._h**2

    def get_bmi(self):
        return self._bmi

    w = property(get_w, set_w)
    BMI = property(get_bmi)


def main():
    x = X(75, 1.83)
    print(x.BMI)
    x.w = 74
    x.w = 73
    x.w = 72
    print(x.BMI)


if __name__ == "__main__":
    main()

分析:

  1. 通过 Property 对象显式的控制属性的访问
  2. 仅在 w 被更改的时候更新BMI,充分避免了重复计算
  3. 很容易的增加了异常处理,对更新属性进行预检验
  4. 完美复用原始调用代码,在调用方不知情的情况完成功能添加

说明:

  1. Property 对象声明必须在访问控制函数之后,否则无法创建 Property 对象
  2. 这里的写法是为了深⼊入剖析 Property 对象,其实一般不这样写,有更加优雅的写法

改进四:

class X:
    def __init__(self, w, h):
        self._w = w
        self._h = h
        self._bmi = w / h**2

    @property
    def w(self):
        return self._w

    @w.setter
    def w(self, value):
        if value <= 0:
            raise ValueError("Weight below 0 is not possible.")
        self._w = value
        self._bmi = self._w / self._h**2

    @property
    def BMI(self):
        return self._bmi


def main():
    x = X(75, 1.83)
    print(x.BMI)
    x.w = 74
    x.w = 73
    x.w = 72
    print(x.BMI)


if __name__ == "__main__":
    main()

说明:

  1. 从改进三中我们发现,传给 Property 对象的其实是函数名,那么优雅的方式当属Decorator

关于 Property 的用法

5 Cross-Cutting and Duck Typing (Python 设计理念)

5.1 单继承 vs 多态

5.2 传统 OOP vs 鸭子类型

5.3 传统 OOP 的多态 vs 鸭子类型的多态

5.4 MixIn:基于鸭子类型的视角看 Multi-Inheritance

class X:
    def f1():
        pass


class Y(X):
    def f2():
        pass


class A:
    def f3():
        pass


def do_f1(x):
    x.f1()


def do_f2(x):
    x.f2()


def do_f3(x):
    x.f3()


class Z(Y, A):
    pass

MixIn 的设计用中文讲比较贴切的应该是:赋能

5.5 Cross-Cutting:基于鸭子类型的视角看 Decorator 与 Special Method

Python 语言设计的三个关键词(其实是一件事的三个不同视角)

上一篇 下一篇

猜你喜欢

热点阅读