类属性和实例属性
python中类和实例都维护一个__dict__字典,用来存放方法和属性,可以通过dir()函数查看,下面直接通过代码去说明类属性和实例属性的区别。
class Student():
# 类属性
num = '类属性'
def __init__(self,name):
# 实例属性
self.name = name
def method1(self):
print(self.name)
@classmethod
def method(cls):
cls.age = '类属性'
上面定义了一个类Student
类属性
>>> # 1.查看类属性
>>> dir(Student)
[..., 'method', 'method1', 'num']
为了直观,默认的方法和属性就省略了。可以看到Student类维护的__dict__字典中,有类中定义的method和method1方法,还有类属性num。实例属性name不在字典中,实例属性由实例自身维护,也意味着无法通过类名去调用实例属性。此外,虽然实例方法在字典中,但是用类名访问会报错,如下:
>>> Student.method1()
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
TypeError: method1() missing 1 required positional argument: 'self'
该报错说明,该方法需要一个self参数,即对象的实例,创建两个实例,如下:
>>> s1,s2=Student('u1'),Student('u2')
然后通过实例调用实例方法method1(),如下:
>>> # 解决上面类型调用出错的问题,不建议这样用
>>> Student.method1(s1)
u1
>>> # 用实例调用,不需要传递参数,解释器会自动将实例进行传递
>>> s1.method1()
u1
实例属性
>>> # 2.查看实例属性
>>> dir(s1)
[..., 'method', 'method1', 'name', 'num']
>>> dir(s2)
[..., 'method', 'method1', 'name', 'num']
为了直观,默认的方法和属性也省略了。可以看到实例s1和实例s2中维护的__dict__字典中,类方法和类属性都存在,即可以通过实例访问类方法和类属性
>>> # 证明两个实例指向的内存不一致
>>> s1 is s2
False
>>> # 证明两个实例指向的类属性的内存一致
>>> s1.num is s2.num
True
>>> # 证明两个实例指向的实例属性的内存不一致
>>> s1.name is s2.name
False
如上,实例s1和s2各自维护了自己的__dict__字典。
自定义类属性和实例属性
前面已经看到,类和实例都维护着一个__dict__列表,那么像操作列表一样,在类外能不能自定义类属性和方法属性?
答案是可以的,如下:
>>> Student.num2='类属性'
>>> dir(Student)
[..., 'method', 'method1', 'num', 'num2']
>>> dir(s1)
[..., 'method', 'method1', 'name', 'num', 'num2']
>>> dir(s2)
[..., 'method', 'method1', 'name', 'num', 'num2']
>>> # 证明实例间的属性互不影响
>>> s1.name2='实例属性'
>>> dir(s1)
[..., 'method', 'method1', 'name', 'name2', 'num', 'num2']
>>> dir(s2)
[..., 'method', 'method1', 'name', 'num', 'num2']
当我们定义了一个class,创建了一个class的实例后,可以给该类和实例绑定任何属性和方法,这就是动态语言的灵活性,但是这样使得代码的维护性降低了,不建议这样写,为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性,如下:
>>> class Student():
... __slots__=('name',)
... num = '类属性'
... def __init__(self,name):
... self.name = name
... @classmethod
... def method(cls):
... cls.age = '类属性'
... def method1(self):
... print(self.name)
...
>>> s1,s2=Student('u1'),Student('u2')
>>> # 对属性进行了限制,无法添加
>>> s1.name2='实例属性'
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'name2'
通过类方法创建类属性
>>> Student.method()
>>> dir(Student)
[..., 'age', 'method', 'method1', 'name', 'num']
>>> dir(s1)
[..., 'age', 'method', 'method1', 'name', 'num']
>>> dir(s2)
[..., 'age', 'method', 'method1', 'name', 'num']
通过类方法,给类重新定义了一个age属性,这里有个问题,实例属性name出现在了类维护的__dict__字典中,这是由于在类中添加了__slots__=(‘name’,)限制,这里的name和实例的name不是一样的,如下:
>>> Student.name
<member 'name' of 'Student' objects>
>>> s1.name
'u1'
>>> s2.name
'u2'
属性的访问限制
为了限制属性的访问域,java中提供了多种访问修饰符,python中通过下划线来划分
# _ "单下划线" 开始的成员变量叫做保护变量,
# 意思是只有类对象(即类实例)和子类对象自己能访问到这些变量,
# 需通过类提供的接口进行访问;不能用'from module import *'导入
# __ "双下划线" 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。
# __xxx__ 也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量
将上面的Student类修改一下,将num属性前加上双下划线__num,然后通过类名访问,如下:
>>> class Student():
... __slots__=('name',)
... __num = '类属性'
... def __init__(self,name):
... self.name = name
... @classmethod
... def method(cls):
... cls.age = '类属性'
... def method1(self):
... print(self.name)
...
>>> Student.__num
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
AttributeError: type object 'Student' has no attribute '__num'
现在无法直接访问该属性了,再看看类维护的__dict__字典,如下:
>>> dir(Student)
['_Student__num', ..., 'method', 'method1', 'name']
会发现,在以前的基础上多了一个_Student__num属性,这个其实就是我们定义的__num属性,如下:
>>> Student._Student__num
'类属性'
但是不建议这样做,因为python非常灵活,因此也很容易出错,所以python会增加一些限制,了解原理只是为了以后更好的解决问题,不要随意破坏这些限制。
@property
在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,可能导致属性被随意的修改,从而破坏程序,因此增加属性的访问限制也是为了避免这类问题。
那么如何访问类的私有属性呢?Java中通常是定义getter和setter方法,python中也可以如此,如下:
>>> class Student():
... __slots__=('name',)
... __num = '类属性'
... def __init__(self,name):
... self.name = name
... @classmethod
... def method(cls):
... cls.age = '类属性'
... def method1(self):
... print(self.name)
... @classmethod
... def setNum(cls,num):
... cls.__num=num
... @classmethod
... def getNum(cls):
... return cls.__num
...
>>> Student.getNum()
'类属性'
>>> Student.setNum('类的私有属性')
>>> Student.getNum()
'类的私有属性'
Student类中新增了setNum和getNum()方法,用来访问__num属性。python中通过@property装饰器提供了更简洁的一种写法,但是是用来修饰实例方法的,如下:
>>> class Student():
... @property
... def score(self):
... return self.__score
... @score.setter
... def score(self,score):
... self.__score=score
...
>>> s1.score=80
>>> s1.score
80
通过@property装饰器将方法变成了可调用属性,可以使用更简洁的方式访问到私有属性。