十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
魔法方法 (Magic Methods) 是Python中的内置函数,一般以双下划线开头和结尾,例如__ init__ 、 __del__ 等。之所以称之为魔法方法,是因为这些方法会在进行特定的操作时会自动被调用。
创新互联公司-专业网站定制、快速模板网站建设、高性价比商南网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式商南网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖商南地区。费用合理售后完善,十载实体公司更值得信赖。
在Python中,可以通过dir()方法来查看某个对象的所有方法和属性,其中双下划线开头和结尾的就是该对象的魔法方法。以字符串对象为例:
可以看到字符串对象有 __add__ 方法,所以在Python中可以直接对字符串对象使用"+"操作,当Python识别到"+"操作时,就会调用该对象的 __add__ 方法。有需要时我们可以在自己的类中重写 __add__ 方法来完成自己想要的效果。
我们重写了 __add__ 方法,当Python识别"+"操作时,会自动调用重写后的 __add__ 方法。可以看到,魔法方法在类或对象的某些事件出发后会自动执行,如果希望根据自己的程序定制特殊功能的类,那么就需要对这些方法进行重写。使用魔法方法,我们可以非常方便地给类添加特殊的功能。
1、构造与初始化
__ new __ 、 __ init __ 这两个魔法方法常用于对类的初始化操作。上面我们创建a1 = A("hello")时,但首先调用的是 __ new __ ;初始化一个类分为两步:
a.调用该类的new方法,返回该类的实例对象
b.调用该类的init方法,对实例对象进行初始化。
__new__ (cls, *args, **kwargs)至少需要一个cls参数,代表传入的类。后面两个参数传递给 __ init __ 。在 __ new __ 可以决定是否继续调用 __ init __ 方法,只有当 __ new __ 返回了当前类cls的实例,才会接着调用 __ init __ 。结合 __ new __ 方法的特性,我们可以通过重写 __ new __ 方法实现Python的单例模式:
可以看到虽然创建了两个对象,但两个对象的地址相同。
2、控制属性访问这类魔法
方法主要对对象的属性进行访问、定义、修改时起作用。主要有:
__getattr__(self, name): 定义当用户试图获取一个属性时的行为。
__getattribute__(self, name):定义当该类的属性被访问时的行为(先调用该方法,查看是否存在该属性,若不存在,接着去调用getattr)。
__setattr__(self, name, value):定义当一个属性被设置时的行为。
当初始化属性时如self.a=a时或修改实例属性如ins.a=1时本质时调用魔法方法self. __ setattr __ (name,values);当实例访问某个属性如ins.a本质是调用魔法方法a. __ getattr __ (name)
3、容器类操作
有一些方法可以让我们自己定义自己的容器,就像Python内置的List,Tuple,Dict等等;容器分为可变容器和不可变容器。
如果自定义一个不可变容器的话,只能定义__ len__ 和__ getitem__ ;定义一个可变容器除了不可变容器的所有魔法方法,还需要定义__ setitem__ 和__ delitem__ ;如果容器可迭代。还需要定义__ iter __。
__len__(self):返回容器的长度
__getitem__(self,key):当需要执行self[key]的方式去调用容器中的对象,调用的是该方法
__setitem__(self,key,value):当需要执行self[key] = value时,调用的是该方法
__iter__(self):当容器可以执行 for x in container:,或者使用iter(container)时,需要定义该方法
下面举一个例子,实现一个容器,该容器有List的一般功能,同时增加一些其它功能如访问第一个元素,最后一个元素,记录每个元素被访问的次数等。
这类方法的使用场景主要在你需要定义一个满足需求的容器类数据结构时会用到,比如可以尝试自定义实现树结构、链表等数据结构(在collections中均已有),或者项目中需要定制的一些容器类型。
魔法方法在Python代码中能够简化代码,提高代码可读性,在常见的Python第三方库中可以看到很多对于魔法方法的运用。
因此当前这篇文章仅是抛砖引玉,真正的使用需要在开源的优秀源码中以及自身的工程实践中不断加深理解并合适应用。
cls是class的缩写。
class A:
member = "this is a test."
def __init__(self):
pass
@classmethod
def Print1(cls):
#杩欎釜鏄被鏂规硶
print "print 1: ", cls.member
def Print2(self):
print "print 2: ", self.member
@classmethod
def Print3(paraTest):
print "print 3: ", paraTest.member
a = A()
A.Print1() #相当于Print1(A)
a.Print2() #相当于Print2(a), 请注意@classmethod
A.Print3()
可以看出来,python在通过“.”调用成员函数的时候,会将“.”前面的东西当作函数的第一个参数调用。
而且pyhon并不关心我们把类的成员函数的第一个参数的名称是什么,我们可以用任意的名称,可以看Print3的定义就知道了。
self:表示一个类的实例对象本身
输出:
a .fun_one() = A .fun_one( a ) 二者是等价的;
cls :所指的就是这个类本身
为了把类中的变量传递给类中的函数,我们需要用到3个特定格式
① 第一个格式 @classmethod 的中文意思就是“类方法”,@classmethod声明了函数1是类方法,这样才能允许函数1使用类属性中的数据。
② 第二个格式 cls 的意思是class的缩写。如果类方法函数1想使用类属性(也就是类中的变量),就要写上cls为函数1的第一个参数,也就是把这个类作为参数传给自己,这样就能被允许使用类中的数据。
③ 第三个格式是 cls.变量 。类方法想使用类属性的时候,需要在这些变量名称前加上cls. 这就好比类方法和类之间的约法三章,所以但凡有任何格式错误都会报错。
如果缺①,即缺了“@classmethod”,类方法就不能直接利用类中的属性,于是报错
类内有三种方法
@staticmethod
def func1(num)
def func2(self, num)
@classmethod
def func3(cls, num)
静态成员方法func1和普通成员方法func2的区别是,静态成员方法可以不实例化,直接被类名+方法名调用;普通成员方法必须被实例化之后的对象调用。
静态成员方法func1和类成员方法func3的相同点是二者都可以直接通过类名+方法名的方式调用,不同点是可以func3通过cls.func2()在func3内部调用func2,但是func1中不能调用func2,func1只能得到类的属性。
一般来说,在Python中,类实例属性的访问规则算是比较直观的。
但是,仍然存在一些不是很直观的地方,特别是对C++和Java程序员来说,更是如此。
在这里,我们需要明白以下几个地方:
1.Python是一门动态语言,任何实体都可以动态地添加或删除属性。
2.一个类定义了一个作用域。
3.类实例也引入了一个作用域,这与相应类定义的作用域不同。
4.在类实例中查找属性的时候,首先在实例自己的作用域中查找,如果没有找到,则再在类定义的作用域中查找。
5.在对类实例属性进行赋值的时候,实际上会在类实例定义的作用域中添加一个属性(如果还不存在的话),并不会影响到相应类中定义的同名属性。
下面看一个例子,加深对上述几点的理解:
复制代码
代码如下:
class A:
cls_i = 0
cls_j
= {}
def __init__(self):
self.instance_i =
self.instance_j =
{}
在这里,我们先定义类A的一个实例a,然后再看看类A的作用域和实例a的作用域中分别有什么:
复制代码
代码如下:
a = A()
a.__dict__
{'instance_j': {}, 'instance_i': 0}
A.__dict__
{'__init__': , '__module__': '__main__', 'cls_i': 0, 'cls_j': {},
'__doc__': None}
我们看到,a的作用域中有instance_i和instance_j,A的作用域中有cls_i和cls_j。
我们再来看看名字查找是如何发生的:
复制代码
代码如下:
a.cls_i
a.instance_i
在查找cls_i的时候,实例a的作用域中是没有它的,却在A的作用域中找到了它;在查找instance_i的时候,直接可在a的作用域中找到它。
如果我们企图通过实例a来修改cls_i的值,那会怎样呢:
复制代码
代码如下:
a.cls_i = 1
a.__dict__
{'instance_j': {}, 'cls_i': 1, 'instance_i': 0}
A.__dict__
{'__init__': , '__module__': '__main__', 'cls_i': 0, 'cls_j': {},
'__doc__': None}
我们可以看到,a的作用域中多了一个cls_i属性,其值为1;同时,我们也注意到A作用域中的cls_i属性的值仍然为0;在这里,我们其实是增加了一个实例属性,并没有修改到类属性。
如果我们通过实例a操纵cls_j中的数据(注意不是cls_j本身),又会怎么样呢:
复制代码
代码如下:
a.cls_j['a'] =
'a'
a.__dict__
{'instance_j': {}, 'cls_i': 1, 'instance_i':
0}
A.__dict__
{'__init__': , '__module__': '__main__',
'cls_i': 0, 'cls_j': {'a': 'a'}, '__doc__': None}
我们可以看到a的作用域没有发生什么变化,但是A的作用域发生了一些变化,cls_j中的数据发生了变化。
实例的作用域发生变化,并不会影响到该类的其它实例,但是类的作用域发生变化,则会影响到该类的所有实例,包括在这之前创建的实例:
复制代码
代码如下:
A.cls_k = 0