(一)私有属性
定义:类里面定义的变量叫类属性,那么类属性有两种,分别公有属性和私有属性。
私有属性定义:
- 单下划线开头:_attr 在外部可以直接访问
- 双下划线开头:__attr 在外部不能直接访问,被改名了,所以在外部访问不了,改成了_Test_attr2
class Test(object):
_attr = 100 # 在外部可以直接访问
__attr2 = 200 # 在外部不能直接访问,被改名了,所以在外部访问不了,改成了_Test_attr2
t = Test()
# 查看该类的所有的属性和方法
print(Test.__dict__)
# 打印结果
{'__module__': '__main__', '_attr': 100, '_Test__attr2': 200, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}
注意: python并没有真正的私有化支持,但是可以用下划线得到伪私有,有一项大多数python 代码都遵循的习惯:带有下划线,前缀的名称应被视为非公开的API的一部分(无论是函数、方法还是数据成员)。它应被视为实现细节,如有更改,恕不另行通知。
(二)__dict__
- 类调用__dict__属性,返回一个字典(类属性和方法)
- 实例调用__dict__属性,返回的是实例相关的实例属性
class MyTest:
name = 100
age = 99
def __init__(self, a, b):
self.a = a
self.b = b
m = MyTest(1, 2)
print(m.__dict__) # 传的如果是对象, 那么就是获取对象的实例属性
print(MyTest.__dict__) # 获取类的属性和方法
ddd = dir(m) # 1、获取的只是属性的名字,没有值。 2、把父类里面的能够使用的属性都获取出来了。
print(ddd)
有童鞋提到了dir这个内置函数是干啥用的,他跟__dict__的区别在于:
- __dict获取的是属性和对应的值以键值对的形式生成,打印出来是一个字典,dir这个内置函数获取的只是属性的名字。
- dir内置函数把对象只要是能够使用的方法(包括父类)里的方法全部打印出来了。
(三)内置属性__slots__
作用1:限制对象可以设置的属性
默认情况下,类的实例有一个字典用于存储属性。这对于具有很少实例变量的对象会浪费空间。当创建大量实例时,空间消耗可能会变得尖锐。
可以通过在类定义中定义__slots__来覆盖默认__dict__行为。__slots__声明接受一个实例变量序列,并在每个实例中只保留足够保存每个变量值的空间。因为没有为每个实例创建__dict__,所以节省空间。
下面我们创建一个list类型的对象,我们看看能不能设置li的name属性=999,通过打印结果发现不能设置,为什么呢?为什么我创建了一个类我能随意设置这个类的对象的属性呢?
li = list()
li.name = 999
print(li)
# 打印结果
AttributeError: 'list' object has no attribute 'name'
class MyTest:
pass
m = MyTest()
m.name = 'wangBigxia'
print(m.__dict__)
# 打印结果
{'name': 'wangBigxia'}
那这个时候我们在MyTest这个类里面加个__slots__ = [] 试试看,发现我们设置m.name会报错,m.__dict__他也会报错,为什么呢?这就是__slot_的作用:限制对象可以设置的属性。
因为设置的__slots__ = [] ,设置的为空列表,那这个对象任何属性都加不了。
那我们修改代码:__slots__ = ['name']试试看:
name属性可以设置,但是age属性设置不了。因为我__slots__ = ['name']
类里面__init__给类的实例化对象初始化属性的时候,如果__slots__设置了他属性范围,那么init方法中就不允许设置初始化其他属性。
实用小技巧:当你这个类会实例化非常非常多的时候,可以通过__slots__阻止这个__dict__的创建。
众所周知,每实例化一个对象,就会自动生成__dict__ 方法。当你这个类会实例化非常非常多的时候,通过这个方法可以节约内存开销。
(四)自定义属性访问
可以定义下面的方法来自定义类实例的属性访问的含义(访问、赋值或者删除x.name)
- object.__getattr__ 当属性查找在通常的地方没有找到该属性时调用
- object.__getattribute__ 查找属性时,第一时间会调用该方法
- object.__setattr__ 设置属性时,调用该方法设置属性
- ojbect.__delattr 在 del obj.attr删除属性时触发
1、获取属性
我创建了一个Demo类,我这个时候实例化一个对象,然后调用他name属性的时候,报错了,因为他没有name属性对吧。
这个时候我们重新创建一个类,然后再调用一下试试看
由打印结果可以看出来,他打印的是none,为什么没有报错呢?因为当你使用对象.name查找属性的时候,其实第一时间调用的其实是__getattribute__方法。
因为我们没有实现找属性的代码,所以他打印出来是None,但是我们怎么该让他去实现自己去找属性的代码呢?我们其实可以调用父类的__getattribute__方法。
当我们执行t.name的时候,先是调用__getattribute__方法,因为这个方法里调用了父类的查找元素的方法,然后t对象没有name属性,就会抛出异常,没有找到属性时,触发AttributeError异常时会调用__getattr__方法
那我们在__getattribute__方法中除了调用父类的__getattribute__方法以外,还要干什么,我们需要将找到的结果保存起来 并且返回,上代码。
2、设置属性
3、删除属性
同设置属性查询属性一样的道理,就不赘述了。
(五)自定义属性管理机制的案例
Demo1 :定义一个MyClass类:可以初始化name,age两个属性
name属性的值只能设置为str
age属性的值只能设置为int
name属性不能被删除
age输出查找出来的结果如果少于0,返回0
"""
定义一个MyClass类:可以初始化name,age两个属性
name属性的值只能设置为str
age属性的值只能设置为int
name属性不能被删除
age输出查找出来的结果如果少于0,返回0
"""
class MyClass:
def __init__(self, name, age):
self.name = name
self.age = age
def __setattr__(self, key, value):
if key == 'name':
if isinstance(value, str):
super().__setattr__(key, value)
else:
raise TypeError('name属性只能设置为字符串')
elif key == 'age':
if isinstance(value, int):
super().__setattr__(key, value)
else:
raise TypeError('age属性只能设置为int')
else:
super().__setattr__(key, value)
m = MyClass('musen', 18)
print(m.name, m.age)
(六)序列类型数据索引取值对于的魔术方法
我们先自己手写一个类,我们为啥不能通过对象[属性名]获取他的属性值呢,是因为我们手写的这个类没有实现__getitem__这个方法。
我们修改一下代码试试看:
下面上传源代码,大家可以自己尝试一下:
class MyClass(object):
def __init__(self, a, b, c, d):
self.a = a
self.b = b
self.c = c
self.d = d
def __getitem__(self, item):
print('Item:', item)
return getattr(self, item)
def __setitem__(self, key, value):
setattr(self, key, value)
def __delitem__(self, key):
print("删除索引:", key)
delattr(self, key)
def __len__(self):
"""len(obj)"""
return len(self.__dict__)
# return 999
m = MyClass(11, 22, 33, 44)
# print(m['d'])
#
# m['abc'] = 11111
#
# print(m['abc'])
#
# del m['b']
# res = len(m)
# print(res)