• 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

Python面向对象:类和实例

Python编程 彭东稳 7年前 (2017-09-22) 20052次浏览 已收录 0个评论

类和实例

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

以 Student 类为例,在 Python 中,定义类是通过 class 关键字:

第一行:class 后面紧接着是类名,即 Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用 object 类,这是所有类最终都会继承的类。

第二行:用来定义注释信息的。

第三行:用来定义类的属性(类中的变量叫属性)。

第四行:用来定义方法(类中的函数叫方法),method 方法的第一个参数永远是 self,表示创建的实例本身。因此,在 method 方法内部,就可以把各种属性绑定到 self,因为self就指向创建的实例本身。

第五行:定义实例属性(只有将类实例化为实例时此定义才有效)。

定义好了 Student 类,就可以根据 Student 类创建出 Student 的实例,创建实例是通过 类名+() 实现的:

可以看到,变量 instance 指向的就是一个 Student 的实例,后面的 0x10fde39e8 是内存地址,每个 object 的地址都不一样,而 Student 本身则是一个类。

类实例化之后,就可以通过点操作符访问实例的属性或者调用实例的方法。调用类属性,也就是类中定义的变量(公共的):

然后可以调用类中的方法,也就是函数:

可以发现,当我调用实例方法的时候,对于 self 参数是忽略的,上面也说了方法的第一个参数永远是 self,表示创建的实例本身,会由解释器自动传入。

调用实例属性,在 instance.method 调用之前,Student() 类不会把 name 属性附加到实例 name 上的。

在方法部分,通过定义一个特殊的__init__方法,在创建实例的时候,就把 name,score 等属性绑上去:

__init__方法是用来初始化属性的函数,其第一个参数永远是 self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为 self 就指向创建的实例本身。其实在类内部默认有一个__init__函数,才是类调用的第一个方法,是真正用来创建实例的,其没有属性,会变成 self 传递给__init__函数,__init__函数会对创建出来的实例做初始化工作。但是通常__new__函数不需要写,除非要改变默认创建实例的行为才需要修改,这也就是元编程了。

另外当没有显示地定义__init__方法时,会使用默认的__init__方法,默认__init__方法如下:

有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但 self 不需要传,Python 解释器自己会把实例变量传进去:

由以上部分可以发现,类体可以包含:声明语句、类成员定义、数据属性、方法等。类中的函数和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量 self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数和关键字参数等。

实例属性和类属性

由于 Python 是动态语言,根据类创建的实例可以动态增减属性。给实例绑定属性的方法是通过实例变量,或者通过 self 变量:

对 Student 类可以做如下操作:

但是,如果我们想要限制实例的属性怎么办?比如,只允许对 Student 实例添加 name 和 age 属性。为了达到限制的目的,Python 允许在定义类的时候,定义一个特殊的__slots__变量,来限制该类实例能添加的属性:

然后,我们试试:

由于’score’没有被放到__slots__中,所以不能绑定 score 属性,试图绑定 score 将得到 AttributeError 的错误。

使用__slots__要注意, __slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。除非在子类中也定义__slots__ ,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

再来说一下作用域,定义一个类:

实例化一下:

我们可以看出两个实例是不同的命名空间,也就是说不同实例之间的操作是不会影响到另外一个实例的变量。

但是,如果类本身需要绑定一个属性呢?可以直接在 class 中定义属性,这种属性是类属性,归类所有:

当我们定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。来测试一下:

从上面的例子可以看出,在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

对于类属性,所有实例共享。

可以看出两个实例初始化时引用同一个对象,所以当其中一个实例属性发生新的赋值时,Python 会创建一个新的对象引用,此时会看见两个实例属性就不同了。

当类属性发生改变时,所有实例属性都会改变(已经重新被赋值过的实例属性不会改变):

为了更好地验证,我们把类属性变成一个列表试试看:

可以发现 d1 实例追加一个元素后,d2 实例也有这个元素。由此看出两个实例都是引用同一个对象,由于 d1 属性没有发生新的赋值,所以引用对象没有发生变化。

实例与方法

通过上面的演示,基本明白了类与实例,以及类属性和实例属性了。对于类中定义的方法来说,其作用域是类的,如下演示:

实例化一下:

修改实例方法:

方法的作用域都属于类级,可以看出当把类的方法改变之后,所有实例都改变了。另外这种改变方法有一个术语叫 “monkey patch”,也是有安全风险的,比如一些黑客可以通过植入代码来改变你的内部函数;好处就是可以在不动第三方库源码的情况下,通过这种方法,可以改变一些内部实现了。

另外,对于类中定义的方法来说,通过类来调用与实例调用是不一样的:

一个返回的是 function 类型,一个返回的是 method 类型。他们的主要区别在于,函数的传参都是显式传递的,而方法传参往往都会有隐式传递的,具体根据于调用方。例如示例中的 C().f 通过实例调用的方式会隐式传递 self 数据。

类方法及静态方法

除了实例方法,还有类方法和静态方法。

定义类方法,需要一个内置的 @classmethod 装饰器,classmethod 则是要让 C.f 和 c.f 都返回方法,并且传递隐式参数 cls,传递的是类本身。运行代码如下:

测试类方法和实例方法:

实例方法和类方法的区别在于传入的第一个参数,实例方法会自动传入实例本身作为第一个参数,而类方法会自动传入当前类。

定义静态方法,需要一个内置的 @staticmethod 装饰器,staticmethod 的效果是让 C.f 与 c.f 都返回函数,等价于 object.__getattribute__(c, "f") 或 object.__getattribute__(C, "f") ,运行代码如下:

静态方法的作用不大,一般就是用来做组织代码用的,可以把一批方法归纳到一个命名空间。

可以看出,实例无法调用静态方法,而类可以。当我们用实例调用方法的时候,总是会传入一个参数,要么是实例本身,要么是它的类。

虽然通常静态方法都只是类会调用,但是如果想让实例可以调用静态方法,就需要使用 @staticmethod 装饰器装饰一下(一般都会加上这个装饰器,标明一下这是一个静态变量),当我们实例调用静态方法时,@staticmethod 会把第一个参数去掉。

实例调用静态方法:

总结:方法的作用域都属于类级,可以看出当把类的方法改变之后,所有实例都改变了。另外具体这个方法是实例方法、类方法、或者静态方法,都由第一个参数决定。当第一个参数是实例的时候,就属于实例方法;当第一个参数是类的时候就是类方法;当不要求第一个参数的时候是静态方法。静态方法用的一般不多,可以被普通函数或模块替代,而类方法和实例方法一般用的挺多。


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (1)
[资助本站您就扫码 谢谢]
分享 (0)

您必须 登录 才能发表评论!