- Python 用下划线作为变量前缀和后缀指定特殊变量
分三种:
- 单下划线开始的变量,如_xxx,表示不能用’from module import *'导入
- 双下划线开始的变量,如__xxx,表示类中的私有变量名
- 前后各有双下划线的变量,如__xxx__,表示系统定义名字,也叫magic variable(魔术变量)
class Student:
def __init__(self,name='空',age=None,addr=None):
self.__name=name
self._age=age
self.addr=addr
def get_name(self):
return self.__name
def set_name(self,name):
self.__name=name
s1=Student('xf1',21,'harbin')
print(s1._age)
print(s1.addr)
可以看到_age和addr可以直接访问,但__name不能直接访问.
>>>print(s1.__name)
AttributeError: 'Student' object has no attribute '__name'
>>>print(s1.get_name())
xf1
上面提示__name找不到,其实Student类将__name重命名为_Student__name,看下面:
>>>s1.__dict__
{'_Student__name': 'xf1', '_age': 21, 'addr': 'harbin'}
>>>print(s1._Student__name)
xf1
2 魔术方法(__xxx__)
可以分为以下几类:
- 2.1 类的基础方法
- 2.2 计算属性
- 2.3 可比较的类
- 2.4 行为方式与迭代器类似的类
- 2.5 可序列化的类(未讲)
- 2.6 可在 with 语块中使用的类(未讲)
2.1 类的基础方法
这里介绍__str__()和__repr__,先看一个例子:
class Student:
def __init__(self,name='空',age=None,addr=None):
self.__name=name
self._age=age
self.addr=addr
s1=Student('xf1',21,'harbin')
print(s1)
语句print(s1)打印对象s1,输出结果为:
<__main__.Student object at 0x000001AFBA0D34A8>
现在我们在Student中添加__str__方法如下:
class Student:
def __init__(self,name='空',age=None,addr=None):
self.__name=name
self._age=age
self.addr=addr
def __str__(self):
return '(Student:%s,%s,%s)'%(self.__name,self._age,self.addr)
s1=Student('xf1',21,'harbin')
print(s1)
结果如下:
(Student:xf1,21,harbin)
但是:直接输入s1,结果仍不友好.
>>>s1
<__main__.Student at 0x1afba0d3710>
下面介绍__repr__方法:
class Student:
def __init__(self,name='空',age=None,addr=None):
self.__name=name
self._age=age
self.addr=addr
def __str__(self):
return '(Student:%s,%s,%s)'%(self.__name,self._age,self.addr)
def __repr__(self):
return '(Student info:%s,%s,%s)'%(self.__name,self._age,self.addr)
s1=Student('xf1',21,'harbin')
s1
结果为:
(Student info:xf1,21,harbin)
注: 大家自己总结__str__()和__repr__()的区别.
为什么每个类都最好有一个 repr 方法
如果你没有添加 str 方法,Python 在需要该方法但找不到的时候,它会去调用 repr 方法。因此,我推荐在写自己的类的时候至少添加一个 repr 方法,这能保证类到字符串始终有一个有效的自定义转换方式。
2.2 计算属性
2.2.1 __setattr__ 拦截 属性的的赋值语句 (obj.xx = xx),可以替代set函数
class Student:
def __init__(self,name='空',age=None,addr=None):
self.__name=name
self._age=age
self.addr=addr
def __setattr__(self,key,value):
print('__setattr__ called')
self.__dict__[key]=value
s1=Student('xf1',21,'harbin')
s1._Student__name='xf2'
结果为:
__setattr__ called
__setattr__ called
__setattr__ called
__setattr__ called
2.2.2 __getattribute__和__getattr__
__getattribute__拦截运算(obj.xx),当每次调用属性时,python会无条件进入__getattribute__中,不论属性存在与否.
__getattr__是在查找不到属性时调用,当obj.xx找不到属性xx时,__getattr__被调用.
class Student:
def __init__(self,name='空',age=None,addr=None):
self.__name=name
self._age=age
self.addr=addr
def __setattr__(self,key,value):
print('__setattr__ called')
self.__dict__[key]=value
def __getattribute__(self,*args,**kwargs):
print('__getattribute__ called')
return object.__getattribute__(self,*args,**kwargs)
def __getattr__(self,item):
return 'not found '+item
s1=Student('xf1',21,'harbin')
s1._Student__name='xf2'
s1.gg
注意: 1 在__getattribute__和__getattr__中不用使用类似 self.*** 这种调用方式因为 每次调用类的属性都会强制调用 __getattribute__ ,所以极有可能造成递归调用.
2 访问属性的时候首先会调用__getattribute__ 方法,这个方法会检测__dict__ 里面有没有这个属性,没有的话最后再调用一下 __getattr__.
2.2.3 其他的一些magic method
目的 | 所编写代码 | python实际调用 |
---|---|---|
序列的长度 | len(s) | s.__len__() |
是否包含特定值 | x in s | s.__contains__(x) |
通过键来获取值 | x[key] | x.__getitem__(key) |
通过键来设置值 | x[key]=value | x.__setitem__(key,value) |
class Dog:
def __init__(self,name1,age1,desc):
self.name = name1
self.age = age1
self.desc = desc
def __len__(self):
return len(self.name)
def __getitem__(self,key):
if key in self.__dict__:
return self.__dict__[key]
def __setitem__(self,key,value):
self.__dict__[key] = value
dog1 = Dog('join_merry',2,'my friend')
len(dog1)
dog1['age'] = 3
dog1['age']
dog1['speces'] = '2ha'
dog1.__dict__
再看一个例子。
例1 Stu类
class Stu:
def __init__(self,name='',age=None,addr=None):
self.__name=name
self._age=age
self.addr=addr
def get_name(self):
return self.__name
def set_name(self,name):
self.__name=name
def __repr__(self):
return '(Student info:%s,%s,%s)'%(self.__name,self._age,self.addr)
def __len__(self):
return len(self.__name)
def __getitem__(self,key):
if key=='__name':
return self.__name
elif key=='_age':
return self._age
elif key=='addr':
return self.addr
s1=Stu('xf21',21,'harbin')
s1['__name'],s1['_age'],s1['addr'],len(s1)
结果为(‘xf21’, 21, ‘harbin’, 4).
2.3 可比较的类
目的 | 所编写代码 | python实际调用 |
---|---|---|
相等 | x==y | x.__eq__(y) |
不相等 | x!=y | x.__ne__(y) |
小于 | x<y | x.__lt__(y) |
小于或等于 | x<=y | x.__le__(y) |
大于 | x>y | x.__gt__(y) |
大于或等于 | x>=y | x.__ge__(y) |
上下文环境中的真值 | if x: | x.__bool__() |
2.4 迭代器和可迭代对象
例2 自定义字符串类MyStr(本身也是迭代器),以及可迭代对象MyStr_iter.
class MyStr: #这是一个迭代器,含有__iter__,__next__
import copy
def __init__(self,data):
self.__data=data
self.__index=0
def __repr__(self):
return self.__data
def __len__(self):
return len(self.__data)
def __contains__(self,x): # x in str1
if x in self.__data:
return True
else:
return False
def __getitem__(self,key): # str1[0],str1[1],...
for i in range(len(self)):
if key==i:
return self.__data[i]
def __setitem__(self,key,value): # str1[0]=value1
if key=='__data':
self.__data=value
for i in range(len(self)):
if key==i:
t=list(self.__data)
t[i]=value
self.__data=''.join(t)
#运算符重载
def __setattr__(self,key,value): # 赋值运算符=
#print('__setattr__ called')
self.__dict__[key]=value
def __add__(self,s): # 加法+
self.__data=self.__data+s.__data
return self
#添加迭代器,需要__iter__,__next__
def __iter__(self):
return copy.copy(self)
def __next__(self):
if self.__index < len(self):
res=self.__data[self.__index]
self.__index+=1
return res
else:
raise StopIteration
class MyStr_iter: #创建一个类,用来实例化可迭代对象,可迭代对象只有__iter__方法
def __init__(self,data):
self.__data=data
def __iter__(self):
return MyStr(self.__data)
下面是使用举例.
ms1_iter=MyStr_iter("hello")
for i in ms1_iter:
print(i,end=',')
结果为h,e,l,l,o,.
参考文献
- https://www.cnblogs.com/nkwy2012/p/6264031.html
- http://python.jobbole.com/88582/