您好,这里有一份 “实例”请您接收一下。

2019-11-07 15:38:35 浏览数 (1)

写在之前

昨天写了类属性,作为不分家的小伙伴,今天当然是来说说 “实例”。我在之前的文章中说过,类是对象的定义,实例才是真实的东西。比如 “人” 是一个类,但是 “人” 终究不是具体的某个会喘气的,只有 “rocky” 才是具体的东西,但他是具有 “人” 这个类所定义的属性和方法。“rocky” 就是 “人” 这个类的实例。

创建实例

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

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

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

代码语言:javascript复制
>>> man()
<__main__.man instance at 0x0000000002577D88>

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

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

代码语言:javascript复制
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__() 区别于一般方法的地方:

代码语言:javascript复制
>>> 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 语句,其他方面和普通函数就没有什么区别了。比如参数和里面的属性,你就可以像下面这样来做:

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

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

实例属性

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

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

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

代码语言:javascript复制
>>> f.x
1

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

代码语言:javascript复制
>>> 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']

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

代码语言:javascript复制
>>> f.x  = 10
>>> f.x
11

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

代码语言:javascript复制
>>> A.x
1

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

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

代码语言:javascript复制
>>> f.x
11
>>> del f.x
>>> f.x
1

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

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

代码语言:javascript复制
>>> A.x  = 10
>>> A.x
11
>>> f.x
11

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

代码语言:javascript复制
>>> 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']

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

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

代码语言:javascript复制
>>> 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'

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

写在最后

这一篇文章写得有点多,我本来想分为两次来写的,但是因为都是实例,所以放在了一起,两相印证一下,可能印象会更深。

在写这篇文章的时候,查了一些资料,看到几个之前没有注意到的点,我也在文章中给大家提醒了,知识就是这样,有很多你会忽略的东西,永远不要觉得基础不重要,也永远不要小看 “简单” 这个词。

希望你看了就要好好看,不要浪费你的时间,也请珍惜我的劳动成果。

The end。

0 人点赞