返回顶部
首页 > 资讯 > 后端开发 > Python >Python面向对象编程之我见
  • 649
分享到

Python面向对象编程之我见

我见面向对象Python 2023-01-31 01:01:47 649人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

面向对象基本概念 面向对象是一种编程范式。范式是指一组方法论。编程范式是一组如何组织代码的方法论。编程范式指的是软件工程中的一种方法学。 一些主流的编程范式: OOP - 面向对象编程 世界观:一切皆对象。 FP - 函数式编程 世

面向对象基本概念

面向对象是一种编程范式。范式是指一组方法论。编程范式是一组如何组织代码的方法论。编程范式指的是软件工程中的一种方法学。

一些主流的编程范式:

  1. OOP - 面向对象编程
    世界观:一切皆对象。
  2. FP - 函数式编程
    世界观:一切皆函数。一般指无副作用的函数。
  3. PP - 过程化编程
  4. IP - 指令式编程
  5. LP - 逻辑化编程
  6. aop - 面向方面编程 装饰器

设计方法:

  1. 自顶向下
  2. 自底向上

面向对象更进一步的抽象了世界。OOP的世界观:

  1. 世界是由对象组成的
  2. 对象具有运动规律和内部状态
  3. 对象之间可以相互作用

就是一个模板或蓝图,用来生成对象的。我们可以把类看做是一套模具,而模具加工出来的产品就是对象。当我们从一套模具中塑造出一个产品的时候,我们就可以说创建了一个实例。

面向对象的特性:

  1. 唯一性:对象都是唯一的,不存在两个相同的对象,除非他们是同一个对象。
  2. 分类性:对象是可分类的,世界是由不同的类型组成的。

面向对象的三大特征:

  1. 封装
  2. 继承
  3. 多态

面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,而实例则是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据有可能不同。

在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

面向对象的本质:对行为和数据的封装;有时候数据就是数据;而有的时候行为就是行为。我们先使用python标准库中的namedtuple来实现一个入门的类吧。目的是为了组织数据。命名元组的优势:组织的更好且字段有名称。

  from collections import namedtuple

  Door = namedtuple('Door', ['number', 'status'])

  # 实例化
  door = Door(10010, 'closed')
  print(door.status)
  print(door.number)

: closed
: 10010

以面向对象的方式实现Door,

  class Door:
      def __init__(self, number, status):
          # . 用于访问对象的属性与方法
          self.number = number
          self.status = status

  door = Door(10010, 'closed')  # 调用初始化方法(其他语言中的构造方法)
  print(door.number)  # 获取属性,输出:10010
  print(door.status)  # 获取属性,输出closed

类就是数据与逻辑(或动作)的集合。上述的Door类中只有数据没有逻辑,那么我们在该类中加入开门与关门的动作,用来操纵类中的数据。上述的例子改写如下:

class Door:
    def __init__(self, number, status):
        self.number = number
        self.status = status

    def open_door(self):
        self.status = 'opened'

    def close_door(self):
        self.status = 'closed'

door = Door(10010, 'opened')

print("door's number is: {}".fORMat(door.number))
print("door's status is: {}".format(door.status))

print("现在关门做点坏事")
door.close_door()
print("door's status is: {}".format(door.status))

print("坏事做完,开启门窗透透气吧")
door.open_door()
print("door's status is: {}".format(door.status))

执行上述代码:

$ python3 door.py
door's number is: 10010
door's status is: opened
现在关门做点坏事
door's status is: closed
坏事做完,开启门窗透透气吧
door's status is: opened

上述代码中,我们通过open_door()close_door()函数来操作了Door类的status数据。

如果大家写过c++Java代码,可以很轻松地用C++Java进行实现。我们看看C++是如何实现上述代码的(只是作为了解,不想了解可以跳过):

// filename: door.cpp
#include <iOStream>

using namespace std;

class Door
{
public:
    int number;
    string status;

    Door(int number, string status)
    {
        this->number = number;
        this->status = status;
    }

    void open_door(void);
    void close_door(void);
};

void Door::open_door(void)
{
    this->status = "opened";
}

void Door::close_door(void)
{
    this->status = "closed";
}

int main(int arGC, char *argv[])
{
    Door door(10010, "opened");

    cout << "door's number is: " << door.number << endl;
    cout << "door's status is: " << door.status << endl;

    cout << "现在关闭门窗做点坏事" << endl;
    door.close_door();
    cout << "door's status is: " << door.status << endl;

    cout << "坏事做完,开启门窗透透气吧" << endl;
    door.open_door();
    cout << "door's status is: " << door.status << endl;

    return 0;
}

编译并运行。结果如下:

$ g++ door.cpp -o door
$ ./door
door's number is: 10010
door's status is: opened
现在关闭门窗做点坏事
door's status is: closed
坏事做完,开启门窗透透气吧
door's status is: opened

我们知道,Java是源自于C++的。那么我们看看如何用Java代码该怎么写呢(只是作为了解,不想了解可以跳过)?

// filename: Door.java
class DoorConstructor {
    int number;
    String status;

    DoorConstructor(int number, String status) {
        this.number = number;
        this.status = status;
    }

    public void close_door() {
        this.status = "closed";
    }

    public void open_door() {
        this.status = "opened";
    }
}

public class Door {
    public static void main(String args[]) {
        DoorConstructor door = new DoorConstructor(10010, "opened");
        System.out.println("door's number is: " + door.number);
        System.out.println("door's status is: " + door.status);

        System.out.println("现在关门做点坏事");
        door.close_door();
        System.out.println("door's status is: " + door.status);

        System.out.println("坏事做完,开启门窗透透气吧");
        door.open_door();
        System.out.println("door's status is: " + door.status);
    }
}

编译并运行:

$ javac Door.java
$ java Door
door's number is: 10010
door's status is: opened
现在关门做点坏事
door's status is: closed
坏事做完,开启门窗透透气吧
door's status is: opened

我们看看Go语言是如何使用面向对象的。先看代码(只是作为了解,不想了解可以跳过):

package main

import "fmt"

type Door struct {
    number int
    status string
}

func (d *Door) close_door() {
    d.status = "closed"
}

func (d *Door) open_door() {
    d.status = "opened"
}

func main() {
    door := Door{10010, "opened"}

    fmt.Println("door's number is:", door.number)
    fmt.Println("door's status is:", door.status)

    fmt.Println("现在关门做点坏事")
    door.close_door()
    fmt.Println("door's status is:", door.status)

    fmt.Println("坏事做完,开启门窗透透气吧")
    door.open_door()
    fmt.Println("door's status is:", door.status)
}

编译并运行:

$ go build door.go 
$ ./door 
door's number is: 10010
door's status is: opened
现在关门做点坏事
door's status is: closed
坏事做完,开启门窗透透气吧
door's status is: opened

上面我们通过四种支持面向对象的编程语言(当然还有很多编程语),简单地演示了这些语言的基本套路。这里所举的例子都是入门级的,限于小白的水平也做不到深入。举这些例子的目的是想告诉大家:面向对象编程只是一种思想,掌握了编程思想,那么使用什么样的语言来完成你的当前的任务就看这门语言提供了哪些特性、自己对这门语言的理解及熟练程度。

实例化的过程

接下来会通过一些具体的实例说明实例化的过程。

In [14]: class Heap:
    ...:     def __init__(self):  # 此函数通常叫做构造函数,在Python中更多叫做初始化函数,在对象创建完成后会立刻执行
    ...:         self.data = []

    ...:     def add(self, x):  # 第一个参数是self,其他参数与一般函数定义一样
    ...:         self.data.append(x)

    ...:     def pop(self):
    ...:         if self.data:
    ...:             self.data.pop()
    ...:         else:
    ...:             print('heap is empty')
    ...:             

In [15]: heap = Heap()  # 实例化Heap类,实例为heap

In [16]: heap.data
Out[16]: []

In [17]: heap.add(3)

In [18]: heap.add(4)

In [19]: heap.add(5)

In [20]: heap.data
Out[20]: [3, 4, 5]

In [21]: heap.pop()

In [22]: heap.pop()

In [23]: heap.data
Out[23]: [3]

In [24]: heap.pop()

In [25]: heap.data
Out[25]: []

In [26]: heap.pop()
heap is empty

上面代码中的self代表heap这个实例。当然,代码中的self并不一定要写为self,还可以是其他Python非关键字。

再来一个例子:

$ cat person.py
class Person:  # 创建一个名为Person的类
    def __init__(self, name, job=None, pay=0):  # 初始化函数接收三个参数,与一般的函数参数具有相同意义
    self.name = name  # 创建对象时填充这些字段
    self.job = job  # self就是将要创建的对象(或实例)
    self.pay = pay

bob = Person('Bob Smith') # test the class
sue = Person('Sue Jones', job='dev', pay=10000) # 自动执行__init__方法
print(bob.name, bob.pay) # 获取对象的属性
print(sue.name, sue.pay) # 不同的对象其自身的数据不一定相同

尽管上面的Person类非常简单,不过它依然演示了一些重要的内容。我们注意到bob的name并不是sue的name,并且sue的pay不是bob的pay。bob和sue它们都是两个独立的信息记录。从技术的角度来看,bob与sue都是namespace objects,就像其他所有的类实例一样,它们创建时都有各自独立的状态信息的拷贝。因为每个类的实例都有自己self属性的集合,可以把类可以理解为一个蓝图、工厂或模具。

一个示例,

class Door:
    def __init__(self, number, status):
        self.number = number
        self.status = status

    def open(self):
        self.status = 'opened'

    def close(self):
        self.status = 'closed'

door = Door(1, 'closed') # 看起来非常像一个函数调用。事实上,
                         # 确实发生了一些函数调用,它调用了__init__函数,
                         # 第一个参数由解释器自动传入,表示实例本身,
                         # 通常命名为self,也可以为其他非关键字
print(door.__class__)
print(Door.__class__)
print(type.__class__)
# 所有类,都是type或者type的子类的实例

: <class '__main__.Door'>
: <class 'type'>
: <class 'type'>

__init__函数并不会创建对象,__init__函数初始化对象。对象(或实例)创建过程为:

  1. 首先创建对象
  2. 对象作为self参数传递给__init__函数
  3. 返回self

实例怎么来的?由类的__new__方法实现。如果要改变默认创建默认的创建实例的行为,可以写__new__方法,不过通常是不写的。

class Door:
 #    def __new__(cls): # 创建实例的,可以改变实例创建的行为,这是元编程的体现
 #        pass

    def __init__(self, number, status):
        self.number = number
        self.status = status

    def open(self):
        self.status = 'opened'

    def close(self):
        self.status = 'closed'

door = Door(1, 'closed') # 看起来非常像一个函数调用。事实上,
                         # 确实发生了一些函数调用,它调用了__init__函数,
                         # 第一个参数由解释器自动传入,表示实例本身,
                         # 通常命名为self
print(door.__class__)
print(Door.__class__)
print(type.__class__)
# 所有类,都是type或者type的子类的实例

: <class '__main__.Door'>
: <class 'type'>
: <class 'type'>

实例化的时候,传递的参数列表是__init__方法除了第一个参数之外的所有参数,支持函数的所有参数变化。

当没有显式的定义__init__方法的时候,会使用默认的__init__方法,

def __init__(self):
    pass

通过.操作符访问实例的属性或者调用实例的方法。当我们调用实例方法的时候,第一个参数即实例本身,由解释器自动传入。

类的作用域

先给出一些规则:

  • 实例变量的作用域是在实例内部。
  • 所有实例共享类变量。赋值会产生新的变量。
  • 实例可以动态增减属性。
  • 类变量可以通过类直接访问,而且通过类修改变量,会影响所有实例。
  • 方法的作用域是类级别的。

结合一个简单的例子说明,

In [1]: class Door:
   ...:     type = 'A'  # 类的直接下级作用域的变量,叫做类变量,所有的实例共享该变量。
   ...:     def __init__(self, number, status):
   ...:         self.number = number  # 关联到实例的变量,叫做实例变量
   ...:         self.status = status
   ...:     def open(self):
   ...:         self.status = 'opened'
   ...:     def close(self):
   ...:         self.status = 'closed'
   ...:         

In [2]: d1 = Door(10010, 'closed')

In [3]: d2 = Door(10011, 'opened')

In [4]: d1.type
Out[4]: 'A'

In [5]: d2.type
Out[5]: 'A'

In [6]: d2.open = lambda self: print("haha, it's cool!")

In [8]: d2.open
Out[8]: <function __main__.<lambda>(self)>

In [9]: d2.open(d2)
haha, it's cool!

In [10]: d1.open()

In [11]: d1.status
Out[11]: 'opened'

抛出一个问题:如果执行d1.type = 'B'语句后,那么执行d2.type语句会有什么输出呢?

类变量对类和实例都可见。再看一个例子:

In [14]: class HaHa:
    ...:     NAME = 'HaHa'
    ...:     
    ...:     def __init__(self, name):
    ...:         self.name = name
    ...:         

In [15]: haha = HaHa('haha')

In [16]: haha.NAME  # 等价于haha.__class__.NAME
Out[16]: 'HaHa'

In [17]: haha.__class__.NAME
Out[17]: 'HaHa'

In [19]: haha.NAME = 'hehe'  # 等价于haha.__dict__['NAME'] = 'hehe'

In [20]: haha.NAME
Out[20]: 'hehe'

In [21]: haha.__class__.NAME
Out[21]: 'HaHa'

由此可以获得属性的查找顺序:

  1. __dict__
  2. __class__

我们也从中体会到:在Python中,赋值意味着创建。

类方法/静态方法

方法都是类级的。方法的定义都是类级的,但是有的方法使用实例调用,有的方法却是使用类来调用。

In [9]: class Haha:
   ...:     def instance_print(self):
   ...:         print("instance method")
   ...:         
   ...:     @claSSMethod
   ...:     def class_print(cls):
   ...:         print(id(cls))
   ...:         print("class method")
   ...:         
   ...:     @staticmethod
   ...:     def static_print():
   ...:         print("static method")
   ...:         
   ...:     def xxx_print():
   ...:         print("this is a function")
   ...:         

In [10]: haha = Haha()

In [11]: haha.instance_print()
instance method

In [12]: haha.class_print()
37234952
class method

In [13]: haha.static_print()
static method

In [15]: Haha.xxx_print()
this is a function

In [16]: id(Haha)
Out[16]: 37234952

实例方法与类方法,实例方法和类方法的区别在于传入的第一个参数,实例方法会自动传入当前实例,类方法会自动传入当前类。类方法可以被实例使用,并且被实例使用时,传入的第一个参数还是类。

In [1]: class A:
   ...:     def method_of_instance(self):
   ...:         print('method of instance')
   ...:         
   ...:     @classmethod
   ...:     def method_of_class(cls):
   ...:         print('method of class')
   ...:         

In [2]: a = A()

In [3]: a.method_of_instance()
method of instance

In [4]: a.method_of_class()
method of class

In [5]: A.method_of_instance()  # 并不会传入self参数
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-ba3f94db75c3> in <module>()
----> 1 A.method_of_instance()

TypeError: method_of_instance() missing 1 required positional argument: 'self'

In [6]: A.method_of_class()
method of class

In [7]: A.method_of_instance(a)
method of instance

In [8]: A.method_of_instance(A)
method of instance

再看一个例子,当我们用实例调用方法的时候,总是会传入一个参数,要么是实例本身,要么是它的类。

In [1]: class A:
   ...:     def method_of_instance(self):
   ...:         print('method of instance')
   ...:         
   ...:     @classmethod
   ...:     def method_of_class(cls):
   ...:         print('method of class')
   ...:         
   ...:     @staticmethod
   ...:     def static_method():
   ...:         print('static method')
   ...:         

In [2]: a = A()

In [3]: a.method_of_instance()
method of instance

In [4]: a.method_of_class()
method of class

In [5]: a.static_method()
static method

In [6]: A.method_of_instance()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-ba3f94db75c3> in <module>()
----> 1 A.method_of_instance()

TypeError: method_of_instance() missing 1 required positional argument: 'self'

In [7]: A.method_of_class()
method of class

In [8]: A.static_method()
static method

In [9]: A.method_of_instance(a)
method of instance

# 实例调用方法的时候,会传入实例本身作为第一个参数;
# 类调用方法的时候,不会传递本身作为第一个参数;
# @classmethod 装饰器会向方法传递一个参数,传递的是类本身;

方法的作用域都属于类级别,具体是实例方法,还是类方法,或者是静态方法,由第一个参数决定。可以简单地理解为:当第一个参数是实例的时候,是实例方法;当第一个参数是类的时候,是类方法,当不要求第一个参数时,是静态方法。

In [1]: class A:
   ...:     var = 'A'
   ...:     
   ...:     @classmethod
   ...:     def change_var(cls, val):
   ...:         cls.var = val
   ...:         

In [2]: a1 = A()

In [3]: a2 = A()

In [4]: a1.var
Out[4]: 'A'

In [5]: a2.var
Out[5]: 'A'

In [6]: A.change_var('B')

In [7]: a1.var
Out[7]: 'B'

In [8]: a2.var
Out[8]: 'B'

In [9]: a1.change_var('C')

In [10]: a1.var
Out[10]: 'C'

In [11]: a2.var
Out[11]: 'C'

再来看一个例子:

In [1]: class Car:
   ...:     country = 'China'
   ...:     
   ...:     def __init__(self, length, width, height, owner=None):
   ...:         self.owner = owner
   ...:         self.length = length
   ...:         self.width = width
   ...:         self.height = height
   ...:         self.country = "中国"
   ...:         

In [2]: a1 = Car(1.2, 1.4, 1.5, "James")

In [3]: a2 = Car(2.2, 2.4, 2.5, "Wade")

In [4]: a1.owner, a2.owner
Out[4]: ('James', 'Wade')

In [5]: a1.country, a2.country
Out[5]: ('中国', '中国')

In [6]: a2.country = "美国"

In [7]: a1.country, a2.country
Out[7]: ('中国', '美国')

In [8]: Car.country
Out[8]: 'China'

In [9]: del a2.country

In [10]: a2.country
Out[10]: 'China'

所有实例需要共享一些状态、数据的时候,就可以使用类变量。当在实例中需要修改类变量的时候,我们就可以把修改的内容放到类方法中。

类变量被赋值的话(赋值会产生新的引用),就会变成了实例变量。

访问控制

这里主要涉及公有变量、私有变量及公有方法、私有方法。Python中没有像C++Java中的关键字,诸如:publicprivateprotected等关键字。我们看看Python中是怎么做的。

In [2]: class Door:
   ...:     def __init__(self, number, status):
   ...:         self.number = number
   ...:         self.__status = status
   ...:         
   ...:     def open_door(self):
   ...:         self.__status = 'opened'
   ...:         
   ...:     def close_door(self):
   ...:         self.__status = 'closed'
   ...:         
   ...:     def door_status(self):
   ...:        return self.__status
   ...:     
   ...:     def __set_number(self, number):
   ...:         self.number = number
   ...:         

In [3]: door = Door(10010, 'opened')

In [4]: door.__status
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-cfaa823e7519> in <module>()
----> 1 door.__status

AttributeError: 'Door' object has no attribute '__status'

In [5]: door.__status = 'haha'   # 赋值意味着创建

In [6]: door.__status
Out[6]: 'haha'

In [7]: door.__dict__
Out[7]: {'_Door__status': 'opened', '__status': 'haha', 'number': 10010}

In [8]: door.door_status()
Out[8]: 'opened'

In [9]: door.open_door()

In [10]: door.door_status()
Out[10]: 'opened'

In [11]: door.close_door()

In [12]: door.door_status()
Out[12]: 'closed'

In [13]: door.__set_number(10011)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-13-e7eb0a552659> in <module>()
----> 1 door.__set_number(10011)

AttributeError: 'Door' object has no attribute '__set_number'

In [14]: door.__dict__
Out[14]: {'_Door__status': 'closed', '__status': 'haha', 'number': 10010}

In [15]: dir(door)
Out[15]: 
['_Door__set_number',   # 变成了这个样子
 '_Door__status',            # 变成了这个样子
 '__class__',
 '__delattr__',
...
 '__sizeof__',
 '__status',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'close_door',
 'door_status',
 'number',
 'open_door']

所有双下划线,非双下划线结尾的成员,都是私有成员。对于上述的__status私有变量,如何进行访问呢?在Python中,可以通过

_类名+带双下划线的属性
针对上面的例子就是:_Door__status

Python的私有成员是通过改名实现的。严格地说,Python里没有真正的私有成员。除非真的有必要,并且清楚知道会有什么后果,否则不要用这个黑魔法。

接下来再看看以单下划线开始的变量,

In [1]: class A:
   ...:     def __init__(self):
   ...:         self._a = 3
   ...:         

In [2]: a = A()

In [3]: a._a
Out[3]: 3

In [4]: a._a = 4

In [5]: a._a
Out[5]: 4

In [6]: a.__dict__
Out[6]: {'_a': 4}

单下划线开始的变量是一种惯用法,标记此成员为私有,但是解释器不做任何处理。

本来还想介绍property装饰器呢,留给大家自己摸索一下吧。

封装

先看一个例子,

Heap = namedtuple('Heap', ['add', 'pop'])

def heap_factory():
    data = []

    def add(x):
        pass

    def pop():
        pass

    return Heap(add, pop)

heap = heap_factory()
# 对外界来说,data是不可见的,外界无法访问data

在Python中如何进行封装的?来看一个小例子,

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

a = A(1, 2, 3)
print(a.x)
print(a.y)
print(a.z)
a.x = 2
print(a.x)

: 1
: 2
: 3
: 2

下面是封装的例子,

class B:
    def __init__(self, x, y, z):
        self.x = x
        self.__y = y
        self._z = z

b = B(1, 2, 3)
b.x
b.__y
b._z

在Python中,以双下划线开始,并且不以双下划线结尾的变量,是私有变量,外界无法直接访问。通常,我们不定义以双下线开始,双下划线结尾的变量和方法,因为这在Python中有特殊含义。

接下来看看私有方法,方法也是一样的规则,以双下划线开头,非双下划线结尾的方法是私有方法。

class D:
    def __private_method(self):
        print('private method')

d = D()
d.__private_method()

# 通过dir(d)时,也看不到__private_method()方法。

 Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
AttributeError: 'D' object has no attribute '__private_method'

一个稍微综合的例子,

class F:
    __private_class_var = u'私有类变量'

    def __init__(self):
        self.__private_instance_var = u'私有实例变量'

    @classmethod
    def public_class_method(cls):
        print(cls.__private_class_var)

    def public_instance_method(self):
        print(self.__private_class_var)
        print(self.__private_instance_var)

f = F()
f.public_class_method()
f.public_instance_method()

: 私有类变量
: 私有类变量
: 私有实例变量

私有属性在类的内部均可访问,无论是类方法还是实例方法。接下来再看一个稍微变态的例子,

class G:
    __private_class_var = 'private class var'

    def public_instance_method(self):
        print(G.__private_class_var)

g = G()
g.public_instance_method()
G.__private_class_var

再来一个例子,

class H:
    __private_class_var = 'private class var'

    @staticmethod
    def public_static_method():
        print(H.__private_class_var)

h = H()
h.public_static_method()
H.public_static_method()

前面说过,类的私有属性是不能直接被访问的,这是真的吗?接着看F这个例子,

class F:
    __private_class_var = 'private class var'

    def __init__(self):
        self.__private_instance_var = 'private instance var'

    @classmethod
    def public_class_method(cls):
        print(cls.__private_class_var)

    def public_instance_method(self):
        print(self.__private_class_var)
        print(self.__private_instance_var)

f = F()
f.public_class_method()
f.public_instance_method()

# 使用__dict__查看实例f的属性
f.__dict__
f._F__private_instance_var

事实上,Python的私有属性并不是真正私有,而是一个变量重命名而已。看一个例子说明此问题:

class J:
    def __init__(self):
        self.__a = 1
        self.__b = 2

    def __private_method(self):
        print('private method')

j = J()
j._J__a
j._J__private_method()

一个综合点的例子,

  class Door:
      def __init__(self, number, status):
          self.number = number
          self.__status = status

      def open(self):
          self.__status = 'opened'

      def close(self):
          self.__status = 'closed'

      def get_status(self):
          return self.__status

      @property
      def status(self):
          """
使用`property`装饰器描述符对status方法进行装饰,可以让我们访问status方法像访问类的属性一样。
          """
          return self.__status

  door = Door(1, 'number')
  door.open()
  door.status = 'opened'
  door.get_status()
  door.status # 属性

还想对status进行赋值,但赋值只能是opened或closed,该怎么破?

class Door:
    def __init__(self, number, status):
        self.number = number
        self.__status = status

    def open(self):
        self.__status = 'opened'

    def close(self):
        self.__status = 'closed'

    @property # @proverty装饰器,可以把方法装饰成了一个同名属性
    def status(self):
        return self.__status

    @status.setter # @xxxx.setter xxxx代表被@property装饰的属性吗,当对此属性赋值时,会调用此方法
    def status(self, value):
        if value in ('closed', 'opened'):
            self.__status = value
        else:
            raise ValueError(value)

    @status.deleter # 当删除此属性时,会调用此方法
    def status(self):
        raise NotImplementedError('You can not delete status of door')

door = Door(1, 'number')
door.open()
door.status # 属性
door.status = 'xxxx'
door.get_status()
door.status
door.status = 'closed'

del door.status

继承

啥也不说,先来一个例子,

In [1]: class Base:
   ...:     def __init__(self):
   ...:         self.x = 0
   ...:         

In [2]: class A(Base):
   ...:     pass
   ...: 

In [3]: a = A()

In [4]: a.x  # 访问父类中的x
Out[4]: 0

在Python3中,如果没有显式的指定继承哪个类,默认是继承自object类,也就是新式类。

子类获得父类一些(非全部)方法和属性。看一个例子,

In [1]: class Base:
   ...:     def __init__(self):
   ...:         self.x = 1
   ...:         self._y = 2
   ...:         self.__z = 3
   ...:         

In [2]: class A(Base):
   ...:     def get_x(self):
   ...:         print(self.x)
   ...:         
   ...:     def get_y(self):
   ...:         print(self._y)
   ...:         
   ...:     def get_z(self):
   ...:         print(self.__z)
   ...:         

In [3]: a = A()

In [4]: a.get_x()
1

In [5]: a.get_y()
2

In [6]: z.get_z()  # 私有属性,无法继承
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-6-b29b1f799fa1> in <module>()
----> 1 z.get_z()

NameError: name 'z' is not defined

In [7]: a.__dict__   # 看一下实例a的属性信息
Out[7]: {'_Base__z': 3, '_y': 2, 'x': 1}

In [9]: b = B()

In [10]: b.get_z()
3

In [11]: b.__dict__
Out[11]: {'_Base__z': 3, '_y': 2, 'x': 1}

In [12]: b.z = 3  # 赋值意味着创建

In [13]: b.z   # 再次访问z
Out[13]: 3

In [14]: b.__dict__  # 再次查看__dict__
Out[14]: {'_Base__z': 3, '_y': 2, 'x': 1, 'z': 3}

无论是类变量还是实例变量都可以继承;类方法、实例方法和静态方法都可以继承,但私有的除外。

方法重写: 子类覆盖父类的方法。有的子类就是需要有点儿个性,那么可以覆盖或重写父类的方法即可。

In [1]: class Base:
   ...:     def my_print(self):
   ...:         print('I am base class')
   ...:         

In [2]: class A(Base):
   ...:     def my_print(self):
   ...:         print('I am a class')
   ...:         

In [3]: a = A()

In [4]: a.my_print()
I am a class

如果还要父类的方法呢?可以使用super()方法。super()方法返回super对象,可以使用super对象调用父类的方法。

In [1]: class Base:
   ...:     def my_print(self):
   ...:         print('I am base class')
   ...:         

In [2]: class A(Base):
   ...:     def my_print(self):
   ...:         print('I am a class')
   ...:         

In [3]: a = A()

In [4]: a.my_print()
I am a class

In [5]: class B(Base):
   ...:     def my_print(self):
   ...:         print('I am b class')
   ...:         super().my_print()  # super()等价于super(__class__, self) -> Base
   ...:         

In [6]: b = B()

In [7]: b.my_print()
I am b class
I am base class

子类能否继承祖先类的属性呢?看一个例子:

In [5]: class TopBase:
   ...:     def my_print(self):
   ...:         print('Top class')
   ...:         

In [6]: class Base(TopBase):
   ...:     def my_print(self):
   ...:         print('Base class')
   ...:         

In [7]: class A(Base):
   ...:     def my_print(self):
   ...:         super(Base, self).my_print()  # super(Base, self) -> TopBase, 返回当前类的父类
   ...:         

In [8]: a = A()

In [9]: a.my_print()
Top class

通过上面的例子的演示,super对象不但可以使用父类的属性,还能使用祖先的属性。super(type, obj)返回super对象,指代type的父类。

super对象持有类级别的成员。举个例子看看,

In [1]: class Base:
   ...:     @classmethod
   ...:     def my_print(cls):
   ...:         print('Base class')
   ...:         

In [2]: class A(Base):
   ...:     @classmethod
   ...:     def my_print(cls):
   ...:         print('A class')
   ...:         super().my_print()  # 这里的super(),相当于super(D, cls)
   ...:         

In [3]: a = A()

In [4]: a.my_print()
A class
Base class

当父类定义了带参数的初始化方法时,子类要显式的定义初始化方法,并且在初始化方法里初始化父类。

多继承与MRO(Method Resolution Order)

本节内容小白理解的也不是很深刻,从网上找了很多资料,在这里罗列一下,仅供参考。

MRO:方法查找顺序。MRO的两个原则:

  1. 本地优先:自己定义或重写的方法优先;否则按照继承列表,从左向右查找。
  2. 单调性:所有子类,也要满足查找顺序。

Python通过C3算法来确定是否满足MRO的两个原则。

下面的两种写法在Python3中的写法是等价的,

class A:
    pass

class A(object):
    pass

在Python2.3之前,没有一个最上层的基类;从2.4版本开始,Python引入了object这个最上层的基类,即所有类都继承自object,但是为了兼容,必须要显式指定。在Python2中,如果是第一种写法,无法使用super方法。

针对Python3,因为不用兼容旧风格,所以两种写法是等效的,通常使用第一种写法。

Python支持多继承,接下来看一个例子:

In [1]: class A:
   ...:     def my_print(self):
   ...:         print('A')
   ...:         

In [2]: class B:
   ...:     def my_print(self):
   ...:         print('B')
   ...:         

In [3]: class C(A, B):
   ...:     pass
   ...: 

In [4]: c = C()

In [5]: c.my_print()
A

In [6]: class D(B, A):
   ...:     pass
   ...: 

In [7]: d = D()

In [8]: d.my_print()
B

In [9]: class E(A):
   ...:     def my_print(self):
   ...:         print('E')
   ...:         

In [10]: class F(E, B):
    ...:     pass
    ...: 

In [11]: f = F()

In [12]: f.my_print()
E

In [13]: class G(E, A):
    ...:     pass
    ...: 

In [14]: g = G()

In [15]: g.my_print()
E

In [16]: class H(A, E):
    ...:     pass
    ...: 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-16-7127b631affd> in <module>()
----> 1 class H(A, E):
      2     pass

TypeError: Cannot create a consistent method resolution
order (MRO) for bases E, A

In [17]: A.__mro__
Out[17]: (__main__.A, object)

In [18]: E.__mro__
Out[18]: (__main__.E, __main__.A, object)

In [19]: G.__mro__
Out[19]: (__main__.G, __main__.E, __main__.A, object)

关于C3算法是如何工作的,这里给出小白学习时参考的博文,地址为:https://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path

以上面的类C为例进行一个推演,

class C(A, B) ==>
mro(C) => [C] + merge(mro(A), mro(B), [A, B])
       => [C] + merge([A, O], [B, O], [A, B])
       => [C, A] + merge([O], [B, O], [B])
       => [C, A, B] + merge([O], [O])
       => [C, A, B, O]
C.__mro__
(__main__.C, __main__.A, __main__.B, object)

另外一个推演,

class E(A):
class H(A, E):
mro(H) => [H] + merge(mro(A), mro(E), [A, E])
       => [H] + merge([A, O], [E, A, O], [A, E])
       => [H] + # A在列表中,但[E, A, O]中的A不是首元素,因此抛出异常
       raise TypeError

总结

写了这么多,是该总结一下了。本文开始介绍了一些主流的编程范式及面向对象编程的特点。

Python在众多编程语言中还算是比较容易入门的,就连我这个机械系的小白也能玩得自嗨,更不用说计算机专业出身的大神了。

使用什么语言来完成实际的工作都无所谓,关键是所使用的语言能提供哪些语言特性,我们要有能力组合这些语言的特性以及标准库或第三方库来设计出良好程序。

扫码关注“devops技术网”获取更多的Python教程
Python面向对象编程之我见

--结束END--

本文标题: Python面向对象编程之我见

本文链接: https://lsjlt.com/news/183063.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Python面向对象编程之我见
    面向对象基本概念 面向对象是一种编程范式。范式是指一组方法论。编程范式是一组如何组织代码的方法论。编程范式指的是软件工程中的一种方法学。 一些主流的编程范式: OOP - 面向对象编程 世界观:一切皆对象。 FP - 函数式编程 世...
    99+
    2023-01-31
    我见 面向对象 Python
  • 【python】面向对象编程之@prop
      @property装饰器作用:把一个方法变成属性调用 使用@property可以实现将类方法转换为只读属性,同时可以自定义setter、getter、deleter方法   @property&@.setter class ...
    99+
    2023-01-31
    面向对象 python prop
  • python 面向对象编程
    文章目录 前言如何理解面向对象编程在 python 中如何使用面向对象编程定义类创建对象self添加和获取对象属性添加属性类外添加属性类中添加属性 访问属性类外访问属性类中访问属性 ...
    99+
    2023-08-31
    python 开发语言
  • Python面向对象编程
      面向对象最重要的概念就是类(Class)和实例(Instance),Java比较熟了,下面贴代码注释   class Student(object): def __init__(self, name, score): ...
    99+
    2023-01-30
    面向对象 Python
  • Python-面向对象编程
    面向对象最重要的概念就是类(Class)和实例(Instance),类是抽象的模板,比如人类、动物类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。 以人类为例,创建一个实例为 xiaomi...
    99+
    2023-01-31
    面向对象 Python
  • Python面向对象编程(三)
    目录一、isinstance和issubclass二、反射(hasattr和getattr和setattr和delattr)1、反射在类中的使用2、反射在模块中的使用3、实例:基于反...
    99+
    2024-04-02
  • Python面向对象编程(二)
    目录一、对象的继承1、类的构造函数继承__init__():2、继承关系中,对象查找属性的顺序二、类的派生1、派生方法一(类调用)2、派生方法二(super)三、类的组合四、多父类继...
    99+
    2024-04-02
  • Python面向对象编程(一)
    目录一、程序中定义类和对象1、 定义类2、 定义对象二、定制对象独有特征1、引入2、定制对象独有特征3、对象属性查找顺序4、类定义阶段定制属性三、对象的绑定方法1、类使用对象的绑定对...
    99+
    2024-04-02
  • python 面向对象编程(2)
    文章目录 前言封装多态类属性和实例属性定义以及访问类属性修改类属性实例属性 类方法静态方法 前言 前面我们介绍了 python 类和对象以及继承、私有权限,那么今天我们将来介绍 py...
    99+
    2023-08-31
    python 开发语言
  • Python面向对象编程 一
    一、类    面向对象是对函数进行分类和封装,其主要目的是提高程序的重复实用性,让开发更方便快捷。    在我们的认知中,我们会根据属性相近的东西归为一类。例如:鱼类,鱼类的共同属性是呼吸,卵生。任何一个鱼都在此鱼类基础上创建的。    定...
    99+
    2023-01-31
    面向对象 Python
  • Python OOP 面向对象编程
    参考:黑马程序员教程 - Python基础 面向对象 OOP三大特性,且三个特性是有顺序的: 封装 继承 多态 封装 指的就是把现实世界的事务,封装、抽象成编程里的对象,包括各种属性和方法。这个一般都很简单,不需要多讲。 唯一要注意的...
    99+
    2023-01-31
    面向对象 Python OOP
  • Python学习之面向对象编程详解
    目录什么是面向对象编程(类)类的关键字 - class类的定义与使用类的参数 - selfself 的解析与总结类的构造函数构造函数的创建方法关于对象的生命周期什么是面向对象编程(类...
    99+
    2024-04-02
  • Python面向对象编程之类的概念
    目录1、面向对象基本概念1.1 万物皆对象1.2 面向对象编程1.3 面向对象的特征2、Python面向对象的术语3、Python类的构建3.1 类的基本构建 3.2 类的构造函数3...
    99+
    2024-04-02
  • Python面向对象编程之类的封装
    目录1、封装的理解2、私有类属性、公开类属性、私有实例属性和公开实例属性2.1 公开类属性2.2 私有类属性2.3 公开实例属性2.4 私有实例属性2.5 私有属性不一定真的私有3、...
    99+
    2024-04-02
  • Python面向对象编程之类的继承
    目录1、对继承的理解2、类继承的构建3、Python中最基础的类4、ython类的重载4.1 属性重载4.2 方法重载5、类的多继承1、对继承的理解 继承(Inheritance) ...
    99+
    2024-04-02
  • Python面向对象编程之类的运算
    目录1、运算概念的理解2、运算符的重载2.1 算术运算符2.2 比较运算符2.3 成员运算2.4 其他运算3、Python类的多态1、运算概念的理解 运算(Operation)是操作...
    99+
    2024-04-02
  • Python面向对象编程之类的进阶
    目录1、引用的概念2、对象的拷贝2.1 实例方法的引用2.2 类的特性装饰器3、类的名称修饰3.1 _单下划线开头的名称修饰3.2 _单下划线结尾的名称修饰3.3 __双下划线开头的...
    99+
    2024-04-02
  • 5 Python的面向对象编程
    概述         在上一节,我们介绍了Python的函数,包括:函数的定义、函数的调用、参数的传递、lambda函数等内容。在本节中,我们将介绍Python的面向对象编程。面向对象编程(Object-Oriented Programmi...
    99+
    2023-08-31
    python 面向对象 继承 运算符重载
  • Python面向对象高级编程
      1、__slots__ Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性 class Student(object): __slots__ = ('name',...
    99+
    2023-01-30
    面向对象 高级编程 Python
  • python面向对象编程小结
    这个是跟着教程一步一步走过来的,所以记下自己学习的过程。 一、类基础 1、类的定义 class <类名>:     <其他语句> class <类名>(父类名):     <其他语句> >...
    99+
    2023-01-31
    小结 面向对象 python
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作