面向对象的编程语言?

Python到底是一种什么类型的编程语言:面向对象、函数式还是过程式语言?
Python借用了所有这3种流行方法的编程范式,还鼓励程序员根据需要混合使用

在其他编程语言中(如Java)你写的所有代码都必须放在一个类里,要从这个类实例化对象
在Python中,"一切都是对象“不代表”一切都必须是“。可以用你熟悉的任何一种范式创建代码,可以同样把所有代码放在一个类中,也可以不这么做

Python支持面向对象编程(OOP):如封装一个对象、继承和多态

Python的类

  • 类将行为(本质即函数)和状态(本质即变量)打包在一个对象中
  • Python中的类相当于JS中的原型,类可以完成对象实例化(从类可以创建新的对象实例)
  • 对象共享行为,但不共享状态
    从一个类创建对象时,每个对象会共享这个类的行为(类中定义的方法),但是会维护其自己的状态副本(属性)

为类增加方法:直接在类中定义函数即可
为类增加属性:在__init__中完成

定义一个类,并创建对象

定义一个CountFromBy类,从这个类能创建如下的对象:

  • 属性:具有初始值val和步长incr(若没有指定,默认val为0,incr为1)
  • 方法:具有increase()方法,每次调用时对象的属性val增加incr

1. 为类增加方法

  • 要为类增加方法,直接在类中使用def定义函数即可
  • 注意,Python类的任何方法必须传入第一个参数,用来指示当前对象,即当前正在使用此方法的对象
    Python编程社区约定的做法是:总是将方法的第一个参数命名为self(类似于JS中的this
  • 在方法代码组中,引用属性时要加self前缀,确保操作应用于对象的属性。
    如下,应该写self.val而不是val 前面说过对象共享行为,但不共享状态,只有这样才知道在方法中要修改哪一个对象的属性(即当前正在使用方法的那个对象)
    另外,这样做能保证调用结束后,变量仍存在:如果只写val,则调用结束后变量被销毁,无法再访问val(超出作用域),而对象中的val属性没有发生改变
class CountFromBy(object):#约定:对于类,采用CamelCase命名
    def increase(self) -> None:
        self.val += self.incr
    ...
    ...

注意,也可写成class CountFromBy:class CountFromBy():(二者等价),但是建议写成class CountFromBy(object):,作用是继承object,从而继承其一系列方法


如何调用方法

根据上文,已经知道Python对象调用方法时,第一个参数一定传入这个对象的引用
那么,对于CountFromBy类的a实例,我们调用increase方法时,应该这样写:

>>>a = CountFromBy(0,1)
>>>CountFromBy.increase(a)#调用类的一个方法,传入的第一个参数是某个对象实例

然而实际的写法为

>>>a = CountFromBy(0,1)
>>>a.increase()

实际上,解释器会自动将a.increase()语句转化为CountFromBy.increase(a)语句

因此我们只要使用a.increase()的简单版本就好

  • 这时要明白,我们调用increase方法时,需要提供的参数就少一个
    因为在调用方法时,解释器自动将当前对象a赋给第一个参数self

2. 从类创建对象实例时,初始化对象的属性

创建一个新对象时,如何初始化呢?
如果你了解面向对象编程,肯定会想到在其他语言中的“构造函数”。
构造函数是一个特殊方法,它定义了第一次创建一个对象时会发生什么(包括对象实例化和属性初始化)

在JS中使用构造函数:

//Dog称为构造函数

function Dog(name, breed, weight) {
	//“对象实例化”部分(将传入的参数赋给新对象的属性)
	this.name = name;
	this.breed = breed;
	this.weight = weight;
}
Dog.prototype.run = function() {
	console.log("Run!"); 
};
//“属性初始化”部分
Dog.prototype.name = "dog";
Dog.prototype.breed = "unknown";
Dog.prototype.weight= 0;

//传入参数,获得一个具有某些属性的对象
var fido = new Dog("Fido", "Mixed", 38);

在 Python中有所不同:

  • 对象实例化由解释器自动处理,所以不需要定义构造函数来完成这个工作
  • 属性初始化的工作需要自己完成:有一个名为__init__(称为dunder init)的魔法方法允许你根据需要初始化属性。

绕路介绍一下前置知识:魔法方法__init__(dunder init)

  • object类是Python解释器内置的类
  • Python中所有类都自动继承这个类(包括你自定义的类)
    这意味着,所有类都继承了object类的标准行为(即一些方法),当然也可以根据需要覆盖它们(提供你自己的方法实现)
  • object中的标准方法中,若其名称包含双下划线dunder__,称为魔法方法
    在自己的类中,覆盖这些魔法方法(编写自己的代码)通常很有用
    覆盖__eq__方法,可指定从类创建的对象使用相等操作符==时会发生什么
    覆盖__ge__方法,可指定对对象使用大于操作符>时要发生什么
    覆盖__init__方法,可指定要如何初始化对象属性
    覆盖__repr__方法,可指定解释器要如何表示对象

使用__init__初始化对象的属性

从 Python类创建的新对象,如何初始化呢?

在你的类中覆盖(重新定义)__init__方法即可

  • 每次从你的类创建一个新对象时,解释器都会调用你的__init__方法
  • 传入类的参数值,会被发送到__init__
    例如,对于mycount=CountFromBy(100,10),100和10会被传入__init__

ps.__init__同样是一个方法,也要遵循前述规则:必须有第一个参数self

class CountFromBy:
    def __init__(self,v: int=0, i: int=1) -> None:
    #v默认值为0,i默认值为1
        self.val = v
        self.incr = i
    def increase(self) -> None:
        self.val += self.incr

3. 创建并使用对象

已经为类增加了方法、初始化了属性
下面就可以从类创建对象了

>>> a=CountFromBy()#使用默认值val=0,incr=1
>>> a.increase()
>>> a.val
1

>>> b=CountFromBy(100,10)#指定属性val=100,incr=10
>>> b.increase()
>>> b.val
110

额外介绍一个魔法方法:__repr__

直接打印“对象”,会显示什么呢?

>>> c=CountFromBy()
>>> c
<__main__.CountFromBy object at 0x000002BE66000A90>

>>> type(c)
<class '__main__.CountFromBy'>
>>> hex(id(c))
'0x2be66000a90'

解释器默认表示对象的方式是:对象类型+内存地址

编写自己的魔法方法__repr__,可以覆盖这种默认行为,并指定解释器对于对象的字符串表示

class CountFromBy:
    def __init__(self,v: int=0, i: int=1) -> None:
        self.val = v
        self.incr = i
    def increase(self) -> None:
        self.val += self.incr
    def __repr__(self) -> str:
        return str(self.val)
        
================= RESTART: C:\Users\13272\Desktop\CountFb.py =================
>>> c=CountFromBy(100,10)
>>> c.val
100

>>> c
100#自定义的对象表示方法

关于类的更多技巧

  • @staticmethod:这个修饰符允许你在一个类中创建一个静态函数(不接收self作为第一个参数)
  • @classmethold:这个修饰符允许你创建一个类方法,它接收一个类作为它的第一个对象(通常称为cls),而不是self
  • @property:这个修饰符允许你重新指定和使用一个方法,就好像它是一个属性。
  • __slots__:这是一个类指令,(使用时)可以大大提高从类创建对象的内存效率(要以牺牲一定的灵活性为代价)。