阅读 182

零基础学习 Python 之细说类属性 & 实例

写在之前

如果你看过昨天的文章相信你对「类」有了一些基本的认识,为了能给之后的编程打个稍微牢固的基础,我们要深入到一些细节部分中去。今天我们来看类的属性。

类属性

首先我们在交互模式下先创建一个简单的类:

>>> class A():
...    x = 1
...
复制代码

上面的 A() 类中的代码没有任何方法,只有 x = 1,当然如果你乐意的话,你可以写任何东西。先不管为什么,我们继续在交互模式下敲下面的代码:

>>> A.x
1
复制代码

A 是刚刚建立的类的名字,x 是类中的一个变量,它引用的对象是整数 1。通过 A.x 的方式就能得到整数 1,。像这样的,类中的 x 被称为类的属性,而 1 是这个属性的值,A.x 是调用类属性的方式。

我们在这里谈到了「属性」,请不要忽视这个词,在很多的领域都有它的身影。

下面我们回到之前 A 类的那个例子上。如果要调用类的某个属性,其方法是用英文的句号,就如我们例子中的 A.x。类的属性仅仅与其所定义的类绑定,并且这种属性本质上就是类里的变量。它的值不依赖任何的实例,只是由类中所写的变量赋值语句确定。所以类的属性还有另外一个名字 -- 「静态变量」。

我在前面的文章中说过很多次,在 Python 中 「万物皆对象」,类当然也不例外,它也是对象,凡是对象都具有属性和方法,而属性是可以增加删除和修改的。既然如此,那么对于之前的类 A,都可以对其目前所拥有的属性进行修改,也可以增加新的属性。

>>> A.y = 2
>>> A.y
2
复制代码

上述代码给类 A 增加了一个新的属性 y,并赋值为 2。

>>> del A.x
>>> A.x
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: class A has no attribute 'x'
复制代码

上述代码删除了一个已有的属性 x,A.x 属性被删除后,如果再调用,就会出现异常。A.y 依然存在,我们可以修改 y 这个类的属性的值:

>>> A.y = 10000
>>> A.y
10000
复制代码

y 是我们在 A 类中自己定义的属性,其实在一个类建立的同时,Python 也让这些类具有了一些默认的属性,可以用我们熟悉的 dir() 来查看类的所有属性,当然也包括方法:

>>> dir(A)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'y']
复制代码

我们仔细观察上面的结果,可以发现一个特殊的属性 dict,之所以用“特殊” 这个词来修饰,是因为它也是以双下划线开头和结尾的,类似于昨天文章中我们所见的 init()。在类里面,凡事以双下划线开头和结尾命名的属性和方法,我们都称它们为“特殊**”。

>>> A.__dict__
mappingproxy({'__module__': '__main__', 'y': 10000, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None})
复制代码

下面我再说几种类的特殊属性的含义:

  • A.name:以字符串的形式返回类的名字,需要注意的是这时候得到的仅仅是一个字符串,而不是一个类对象。

  • A.doc:显示类的文档。

  • A.base:类 A 的所有父类。如果是按照上面方式定义的类,应该显示 object,因为以上所有的类都继承了它。等到学习了“继承”,再来看这个属性,内容就丰富了。

  • A.dict:以字典形式显示类的所有属性。

  • A.module:类所在的模块。

这里稍微解释一下 A.module,我们对类 A 做如下操作:

>>> A.__module__
'__main__'
复制代码

说明这个类所描述的模块是 mian()

最后让我们来对类的属性进行一个总结:

1.类属性跟类绑定,可以自定义,删除,修改值,也可以随时增加类属性。

2.每个类都有一些特殊属性,通常情况下特殊属性是不需要修改的,虽然有的特殊属性可以修改,比如 A.doc

对于类,除了属性,还有方法。但是类中的方法,因为牵扯到实例,所以我们还是通过研究实例来理解类中的方法。

我在之前的文章中说过,类是对象的定义,实例才是真实的东西。比如「人」 是一个类,但是「人”」终究不是具体的某个会喘气的,只有「rocky」 才是具体的东西,但他是具有「人」这个类所定义的属性和方法。「rocky」 就是「人」 这个类的实例。

创建实例

创建实例并不是很难的事情,只需要调用类就可以实现:

>>> class man():
...     sex = '男'
...
>>> rocky = man()
>>> rocky
<__main__.man instance at 0x00000000004F3688>
复制代码

如果不是用很严格的说法的话,上面的这个例子就是创建了一个实例 rocky。这里有一点需要我们注意的是,调用类的方法和调用类的函数类似,如果仅仅是写 man() 的话,则是创建了一个实例:

>>> man()
<__main__.man instance at 0x0000000002577D88>
复制代码

而 rocky = man() 本质上是将变量 rocky 与实例对象 man() 建立引用关系,这种关系就如同我们在刚开始的时候学的赋值语句 x = 1 是同样的效果。

那么对于一个实例来说这个建立的过程是怎么进行的呢?我们继续来看:

class Person:
  """
  具有通常类的结构的 Person 类
  """
  def __init__(self,name):
      self.name = name

  def get_name(self):
      return self.name

  def get_sex(self,sex):
      per_sex = {}
      per_sex[self.name] = sex
      return per_sex
复制代码

实例我们用 boy = Person('rocky') ,当然了,在这里你可以创建很多个实例,还记得那句话么:类是实例的工厂。

当我们创建完实例,接下来就是调用类,当类被调用以后,先是创建一个实例对象,然后检查是否有 init(),如果有的话就调用这个方法,并且将实例对象作为第一个参数 self 传进去,如果没有的话,就只是返回实例对象。

我之前也说过,init() 作为一个方法是比较特殊的,在它里面,一般是规定一些属性或者做一些初始化,让类具有一些基本的属性,但是它没有 return 语句,这是 init() 区别于一般方法的地方:

>>> class fun:
...    def __init__(self):
...            print('this is init()')
...            return 1
...
>>> f = fun()
this is init()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: __init__() should return None
复制代码

上面的运行结果出现了异常,并且明确说明了「init() should return None」,所以不能有 return,如果非要带上的话,只能是 return None,索性就不要写了。

由此可知对于 init() ,除了第一个参数必须是 self,还要求不能有 return 语句,其他方面和普通函数就没有什么区别了。比如参数和里面的属性,你就可以像下面这样来做:

>>> class Person:
...     def __init__(self,name,sex = '男',age = 10):
...             self.name = name
...             self.sex = sex
...             self.age = age
...
复制代码

实例我们创建好了以后,我们接下来就要研究实例的内容,首先来看的是实例属性。

实例属性

和类属性相似,实例所具有的属性叫做 “实例属性”:

>>> class A:
...     x = 1
...
>>> f = A()
复制代码

类已经有了一个属性 A.x = 1,那么由类所创建的实例也应当具有这个属性:

>>> f.x
1
复制代码

除了 f.x 这个属性以外,实例也具有其它的属性和方法,我们依然用 dir 方法来看:

>>> dir(f)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x']
复制代码

实例属性和类属性最主要的不同是在于,实例属性可以随意的更改:

>>> f.x += 10
>>> f.x
11
复制代码

上面就是把实例属性修改了,但是类属性并没有因为实例属性的修改而发生变化,正如我们在前几天的文章中所说的那样,类属性是与类捆绑的,不受实例的影响。

>>> A.x
1
复制代码

上述的结果正好印证了这一点 -- 类属性不因实例属性改变而改变。既然如此,那么 f.x += 10 又改变了什么呢?

其实就是实例 f 又重新建立了一个新的属性,但是这个新的属性和原先旧的属性是一个名字,都是 f.x,所以相当于原先旧的属性被 “掩盖”了,只能访问到新的属性,所以值是 11。

>>> f.x
11
>>> del f.x
>>> f.x
1
复制代码

由上面的例子可以看出,既然新的 f.x “掩盖”了旧的 f.x,只要把新的 f.x 删除,旧的 f.x 就可以显现出来。

实例的改变不会影响到类,但是类属性可以影响到实例属性,因为实例就是通过调用类来建立的:

>>> A.x += 10
>>> A.x
11
>>> f.x
11
复制代码

如果是同一个属性 x,那么实例属性跟着类属性的改变而改变,当然,这个是针对于像字符串这种不可变对象而言的,对于类中如果引用的是可变对象的数据,则情形会有所不同,因为可变对象的数据是可以原地进行修改的:

>>> class B:
...     y = [1,2,3,4]
...
>>> B.y #类属性
[1, 2, 3, 4]
>>> f = B()
>>> f.y #实例属性
[1, 2, 3, 4]
>>> B.y.append('5')
>>> B.y
[1, 2, 3, 4, '5']
>>> f.y
[1, 2, 3, 4, '5']
>>> f.y.append('66')
>>> B.y
[1, 2, 3, 4, '5', '66']
>>> f.y
[1, 2, 3, 4, '5', '66']
复制代码

通过上面的代码我们可以看出,当类中的变量引用的是可变对象的时候,类属性和实例属性都能够直接修改这个对象,从而增加另一方的值。

还有一点我们已经知道了增加一个类属性,相应的实例属性也会增加,但是反过来就不成立了:

>>> B.x = 'aa'
>>> f.x
'aa'
>>> f.z = 'abcd'
>>> f.z
'abcd'
>>> B.z
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: class B has no attribute 'z'
复制代码

可以看出类并没有接纳实例实例增加的属性。

写在之后

更多内容,欢迎关注公众号「Python空间」,期待和你的交流。

在这里插入图片描述

关注下面的标签,发现更多相似文章
评论