欢迎光临
我们一直在努力

python中的类属性和实例属性

https://www.cnblogs.com/scolia/p/5582268.html

在上篇的时候,我们知道了:属性就是属于一个对象的数据或者函数,我们可以通过句点(.)来访问属性,同时 python 还支持在运作中添加和修改属性。

而数据变量,类似于: name = ‘scolia’ 这样的形式,会称其为字段;而类里面的函数,又称为方法。而方法又分为实例方法,类方法和静态方法,这些我们以后在讲。

我们先来看看类里面的普通字段:

复制代码
class Test(object):
    name = 'scolia'

a = Test()
print Test.name  # 通过类进行访问
print a.name    # 通过实例进行访问
复制代码

 

我们发现都是可以访问的。

但是,如果我们试图修改这个属性的话:

复制代码
class Test(object):
    name = 'scolia'

a = Test()
Test.name = 'scolia good'   # 通过类进行修改
print Test.name
print a.name
复制代码

 

我们发现两者都修改成功了。

如果通过实例来修改属性的话:

复制代码
class Test(object):
    name = 'scolia'

a = Test()
a.name = 'scolia good'  # 通过实例进行修改
print Test.name
print a.name
复制代码

 

我们发现类的属性没有修改,而实例的属性则修改成功了。这究竟是为什么?

其实这里的情况非常类似于局部作用域和全局作用域。

我在函数内访问变量时,会先在函数内部查询有没有这个变量,如果没有,就到外层中找。这里的情况是我在实例中访问一个属性,但是我实例中没有,我就试图去创建我的类中寻找有没有这个属性。找到了,就有,没找到,就抛出异常。而当我试图用实例去修改一个在类中不可变的属性的时候,我实际上并没有修改,而是在我的实例中创建了这个属性。而当我再次访问这个属性的时候,我实例中有,就不用去类中寻找了。

  如果用一张图来表示的话:

函数  dir()  就能查看对象的属性:

复制代码
class Test(object):
    name = 'scolia'

a = Test()
a.abc = 123
print dir(Test)
print dir(a)
复制代码

 

它返回一个列表,包含所有能找到的属性的名字,这里我们为实例 a 创建了 abc 属性,这里就能看到了。

有些同学会有疑问,为什么我才写了几个属性,结果却多出一堆我不认识的?

因为我们这里用的是新式类,新式类继承于父类 object ,而这些我们没有写的属性,都是在 object 中定义的。

如果我们用经典类的话:

为了方便演示,下面都使用经典类,若没有特殊说明,新旧两式基本是一样的。

其中 __doc__ 是说明文档,在类中的第一个没有赋值的字符串就是说明文档,一般在class的第二行写,没有就为 None。

__module__ 表示这个类来自哪个模块,我们在主文件中写的类,其值应该为 ‘__main__’,在其他模块中的类,其值就为模块名。

复制代码
class Test:
    """文档字符串"""
    name = 'scolia'

print Test.__doc__
print Test.__module__
复制代码

 

文档字符串不一定非要用三引号,只是一般习惯上用三引号表示注释。实例也可以访问,实际访问的是创建它的类的文档字符串,但是子类并不会继承父类的文档字符串,关于继承的问题以后再讲。

除了这两个特殊的属性之外,还有几个常用的,虽然没有显示出来,但也是可以用的。

__dict__

复制代码
class Test:
    """文档字符串"""
    name = 'scolia'

a = Test()
a.name = 'good'
print Test.__dict__
print a.__dict__
复制代码

 

这个属性就是将对象内的属性和值用字典的方式显示出来。这里可以明显的看出来,实例创建了一个同名的属性。

我们也可以使用  vars()  函数,传给函数一个对象,其结果也是一样的。

__class__

class Test:
    pass

a = Test()
print a.__class__

 

这个属性只有实例中有,它表示创建这个实例的类是哪个,这里显示是 __main__ 模块,也就是主文件中的 Test 这个类创建的。

但是其返回值并不是字符串,而是创建实例的类对象,但新旧式类返回有点不同:

经典类:

新式类:

因为返回的是创建实例的类对象,也就是说我们也要用这个返回的类对象再进行实例化。

b = a.__class__()
print b

 

这里用的是新式类为例,实际上新旧式类都能这样做。下面是旧式类的:

 

但是,这并不意味不能通过实例来修改类中的属性。我们知道对于不可修改类型的‘修改’,其实就是重新赋值。这个也和函数中的局部作用域和全局作用域类似,我在函数内部尝试‘修改’一个不可变类型其实就是创建新的同名变量。但我却可以访问全局某个可修改的对象来修改它。

看代码:

复制代码
class Test:
    list1 = []

a = Test()
a.list1.append(123)     # 同通过实例修改类中的列表
print Test.list1
print a.list1
复制代码

我通过实例访问到了一个对象,这个对象是可修改的,所以我可以修改这个对象。这相当于直接操作那个对象。

但是,等号这样的显式赋值行为还是创建新的对象:

a.list1 = [123]   # 显式的创建一个新对象

 

这可能有点绕,需要对变量的赋值、可修改对象与不可修改对象的了解,可以参考我以前的相关博文。

 

这里又有个问题了,我们通常使用 __init__ 来初始化时,会为其添加数据属性,例如 self.name = xxx ,但是却几乎不会为实例添加方法,也就是说实例中的方法,都是在类中找的。这样做其实有好处,因为方法一般是通用的,如果每一个实例都要保存一套方法的话,实在太浪费资源,而把这些方法统一保存到类中,用到的时候来类里找,就节约了许多。

当然,我们也可以任性地为某个实例添加方法,python 支持动态添加属性。

复制代码
class Test:
    pass

def fangfa():
    print '我是某个实例的方法'

a = Test()
b = Test()
a.abc = fangfa  # 特意添加一个方法
a.abc()
b.abc()     # b 没有这个方法
复制代码

 

同样的,我们也可以为类动态添加一个方法:

复制代码
class Test:
    pass

def fangfa(self):   # self 代表是实例方法,只能由实例调用
    print '我是方法'

Test.abc = fangfa
a = Test()
a.abc()
复制代码

关于方法以后再细说。

当然一般情况下我们很少这样做,因为这样会变得不可控,因为你不知道某个方法在你调用的时候有没有创建。

 


 

字段私有化:

我们可以对属性进行私有化,以限制部分访问,但关于方法私有化以后在讲,现在先说说字段私有化。

一般公有字段我们可以通过实例对象访问,类对象访问,类里面的方法也可以访问。

而私有字段一般仅类里面的方法可以访问了。

私有化的方法非常简单,只需要在变量名前面加上两个下划线即可:

复制代码
class Test:
    __name = 'scolia'   # 私有字段

    def a(self):
        print Test.__name   #内部还需要用类来方问

a = Test()
a.a()
print Test.__name   # 在外部使用类来访问
复制代码

 

我们可以看到在外部通过类来访问是不行的,但内部通过类来访问却可以。

同样的,通过实例访问也不允许。

print a.__name

 

但是,私有化并不是语法上的强制,而是 python 隐藏了私有字段的访问入口,所以我们可以在新的入口中访问到私有字段:

print Test._Test__name

 

其格式是: 对象._类__字段名;这里是类的私有字段,所以使用的对象是类对象。

上面的代码使用的是类对象,也可以使用实例对象进行访问 a._Test__name 。

私有化其实就是‘混淆’了相应的属性,这样做还有一个好处,可以保护 __xxx 变量不会与父类中的同名变量冲突。如果父类中有也有一个 __xxx 的变量的话,父类中的变量不会被子类中 __xxx 覆盖。如果是非私有的字段 xxx ,就会被子类中的覆盖掉。所以私有化也是保护关键变量的好选择。

我们上面讲的都是类中的字段的私有化,同样的,我们也可以为实例的字段进行私有化。

复制代码
class Test:
    def __init__(self):
        self.__name = 'scolia'   # 实例的私有字段

    def a(self):
        print self.__name   # 只能有内部方法能访问

a = Test()
a.a()
print a.__name  # 试图通过实例访问
复制代码

当然,因为实例属性类中并没有,所以不用考虑通过类来访问了。

同样的,这里也是隐藏了入口,访问格式依然是一样的,只不过这里的对象指的是实例了。

print a._Test__name

  总结:公用字段可以通过类对象,实例对象,类里面的方法进行访问。

     而私有字段则一般通过类里面的方法进行访问。

       一般不建议强制访问私有字段。


个人总结: 类属性类似全局变量,实例属性类似局部变量,修改全局变量,则全部值都改变,修改局部变量,则全局变量不变,如果 类属性是不可变的,比如字符串或数字 ,实例属性相当于新建了一个属性,如果类属性是可变的,比如list,则实例属性在append操作时,相当于直接操作类属性,但是赋值时,仍然创建了一个新属性

赞(0) 打赏
未经允许不得转载:乌西塔 » python中的类属性和实例属性

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

微信扫一扫打赏