python入门09——魔法方法(__XXX__())

一、常用的方法

1. __init__方法

__init__方法是专门用来定义一个类具有哪些属性的方法。

当使用 类名() 创建对象时,会自动执行以下操作:
    1.为对象在内存中分配空间 —— 创建对象
    2.为对象的属性设置初始值 —— 初始化方法(init)
这个 初始化方法 就是 __init__() 方法,是对象的内置方法。其结构如下所示:

def __init__(self):
    self.data = []

类定义了 __init__() 方法,类的实例化操作会自动调用 该方法。如下实例化类 MyClass,对应的 __init__()方法就会被调用:

x = MyClass()

__init__() 方法可以有参数,参数通过 __init__() 传递到类的实例化操作上。如下实例所示:

class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart


x = Complex(3.0, -4.5)  # 这两个实参分别传到realpart和imagpart中去
print(x.r, x.i)  # 输出结果:3.0 -4.5

应用场景: 开发中,如果希望在创建对象的同时就设置对象的属性。我们可以把希望设置的属性值,定义成__init__ ()的参数,然后在方法内部使用 self.属性 = 形参 接收外部传递的参数。在创建对象时,使用 类名(属性1, 属性2...) 调用即可。

2. __new__方法

__new__() 是一种负责创建类实例的静态方法,它无需使用 staticmethod 装饰器修饰,且该方法会优先 __init__() 初始化方法被调用。

class demoClass:
    instances_created = 0

    def __new__(cls, *args, **kwargs):
        print("__new__():", cls, args, kwargs)
        instance = super().__new__(cls)
        instance.number = cls.instances_created
        cls.instances_created += 1  
        return instance

    def __init__(self, attribute):
        print("__init__():", self, attribute)
        self.attribute = attribute


test1 = demoClass("abc")
test2 = demoClass("xyz")
print(test1.number, test1.instances_created)
print(test2.number, test2.instances_created)

该例参考@http://c.biancheng.net/view/5484.html,其运行结果为:
在这里插入图片描述
由该例可以看出系统优先执行__new__()方法,然后再执行 __init__() 方法。
那我们什么时候才会使用到__new__()呢,答案其实很简单,一般是在__init__() 不够用时。例如对 Python 不可变的内置类型(如 int、str、float 等)进行了子类化,这是因为一旦创建了这样不可变的对象实例,就无法__init__() 方法中对其进行修改

class CapStr(str):  # 该类继承str,但是str类型中不自带转变为大写的方法
    def __new__(cls, string):
        string = string.upper()  # 想要对str类型转成大写
        return str.__new__(cls, string)

a = CapStr('I love zhangyixing!')
print(a)
# I LOVE ZHANGYIXING!
3. __del__方法

在 Python 中,当使用 类名() 创建对象时,为对象分配完空间后,会自动 调用 __init__ 方法。那么同样地,当一个对象从内存中被销毁前,会自动调用 __del__ 方法。

class Cat:

    def __init__(self, new_name):
        self.name = new_name
        print("%s 来了,此时调用的是__init__方法" % self.name)

    def __del__(self):
        print("%s 去了,,此时调用的是__del__方法" % self.name)


# tom 是一个全局变量
tom = Cat("Tom")
print(tom.name)  # 输出Tom

# del 关键字可以删除一个对象
del tom  

上述代码的执行结果为:
在这里插入图片描述
生命周期: 一个对象从调用 类名() 创建,生命周期开始一个对象的 __del__ 方法一旦被调用,生命周期结束。
应用场景:__del__ ()如果希望在对象被销毁前,再做一些事情,可以考虑使用 __del__() 方法。

4. __add__方法

Python同样支持运算符重载,我们可以对类的专有方法进行重载,实例如下:

class Vector:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __str__(self):
        return 'Vector (%d, %d)' % (self.a, self.b)

    def __add__(self, other):
        return Vector(self.a + other.a, self.b + other.b)


v1 = Vector(2, 10)  # a = 2, b = 10
v2 = Vector(5, -2)  # a = 5, b = -2
print(v1 + v2)  # a = 2 + 5 = 7, b = 10 + (-2) = 8
5.__str__方法

如果要把一个类的实例变成 str,就需要实现特殊方法__str__()。如果不使用 __str__() ,print打印出来是个对象;使用了就可以把对象变成字符串形式再输出。
在交互模式下演示如下的实例:

class Test(object):
    def __init__(self, value='hello, world!'):
        self.data = value
 
>>> t = Test()
>>> t
<__main__.Test at 0x7fa91c307190>
>>> print t  # 此时的打印类对象并不是很友好,显示的是对象的内存地址
<__main__.Test object at 0x7fa91c307190>


# 下面我们重构下该类的__repr__以及__str__,看看它们俩有啥区别
# 重构__repr__
class TestRepr(Test):
    def __repr__(self):
        return 'TestRepr(%s)' % self.data
 
>>> tr = TestRepr()
>>> tr
TestRepr(hello, world!)
>>> print tr
TestRepr(hello, world!)
# 重构__repr__方法后,不管直接输出对象还是通过print打印的信息都按我们__repr__方法中定义的格式进行显示了
 
 
# 重构__str__
calss TestStr(Test):
    def __str__(self):
        return '[Value: %s]' % self.data
 
>>> ts = TestStr()
>>> ts  # 直接输出对象ts时并没有按我们__str__方法中定义的格式进行输出
<__main__.TestStr at 0x7fa91c314e50>
>>> print ts  # print输出时却按照定义的格式输出
[Value: hello, world!]

总结:__repr____str__这两个方法都是用于显示的,__str__是面向用户的,而__repr__面向程序员的。打印操作会首先尝试__str__和str内置函数(print运行的内部等价形式),它通常应该返回一个友好的显示。

应用场景:__repr__用于所有其他的环境中:用于交互模式下提示回应以及repr函数,如果没有使用__str__,会使用print和str。它通常应该返回一个编码字符串,可以用来重新创建对象,或者给开发者详细的显示。当我们想所有环境下都统一显示的话,可以重构__repr__方法;当我们想在不同环境下支持不同的显示,例如终端用户显示使用__str__,而程序员在开发期间则使用底层的__repr__来显示,实际上__str__只是覆盖了__repr__以得到更友好的用户显示

简单应用
基本要求:定制一个计时器的类,类中start和stop方法代表启动计时和停止计时。假设计时器对象t1,print(t1)和直接调用t1均显示结果。当计时器未启动或已经停止计时,调用stop方法会给予温馨的提示。两个计时器对象可以进行相加:t1 + t2。
使用提供的有限资源就可以完成以上操作,资源如下:

使用time模块的localtime方法获取时间。
time.localtime返回struct_time的时间格式。
表现你的类:__str__和__repr__

#!/usr/bin/python
# -*- coding:utf-8 -*-

import time as t


class MyTimer:
    def __init__(self, func, number=1000000):
        self.prompt = "未开始计时"
        self.lasted = 0.0
        self.default_timer = t.perf_counter
        self.func = func
        self.number = number
    def __str__(self):
        return self.prompt
    __repr__ = __str__
    def __add__(self, other):
        result = self.lasted + other.lasted
        prompt = "总共运行了%0.2f秒" % result
        return prompt
    # 内置方法,计算运行时间
    def timing(self):
        self.begin = self.default_timer()
        for i in range(self.number):
            self.func()
        self.end = self.default_timer()
        self.lasted = self.end - self.begin
        self.prompt = "总共运行了 %0.2f 秒" % self.lasted

    def set_timer(self, timer):
        if timer == 'process_time':
            self.default_timer = t.process_time
        elif timer == 'perf_counter':
            self.default_timer = t.perf_counter
        else:
            print("输入无效")

def test():
    text = "I love FishC.com!"
    char = 'o'
    if char in text:
        pass



t1 = MyTimer(test)
t1.timing()
print(t1)
t2 = MyTimer(test, 100000000)
t2.timing()
print(t2)

实际上,python提供了更强大的计时库——timeit。
timeit 模块定义了接受两个参数的 Timer 类。两个参数都是字符串。 第一个参数是你要计时的语句或者函数。 传递给 Timer 的第二个参数是为第一个参数语句构建环境的导入语句。 从内部讲, timeit 构建起一个独立的虚拟环境, 手工地执行建立语句,然后手工地编译和执行被计时语句。具体用法如下所示:

# 导入timeit.timeit
from timeit import timeit

# 看执行1000000次x=1的时间:
print(timeit('x=1'))

# 看x=1的执行时间,执行1次(number可以省略,默认值为1000000):
print(timeit('x=1', number=1))

# 看一个列表生成器的执行时间,执行1次:
print(timeit('[i for i in range(10000)]', number=1))

# 看一个列表生成器的执行时间,执行10000次:
print(timeit('[i for i in range(100) if i%2==0]', number=10000))

执行结果为:
在这里插入图片描述
【例】测试函数func的运行时间。

from timeit import timeit


def func():
    s = 0
    for i in range(1000):
        s += i
    print(s)

# timeit(函数名_字符串,运行环境_字符串,number=运行次数)
t = timeit('func()', 'from __main__ import func', number=1000)
print(t)

执行结果为:
在这里插入图片描述

二、属性访问

用一个Test类来测试相关方法的使用:

class Test:
    def __getattribute__(self, name):
        print("__getattribute__")
        return super().__getattribute__(name)

    def __getattr__(self, name):
        print("__getattr__")

    def __setattr__(self, name, value):
        print("__setattr__")
        super().__setattr__(name, value)

    def __delattr__(self, name):
        print(" __delattr__")
        super().__delattr__(name)
1.__getattr__方法

该方法中定义当用户试图获取一个不存在的属性时的行为。

t = Test()
# 此时调用一个未定义的属性x
print(t.x)
# __getattribute__
# __getattr__
# None

由执行结果可以看出,当 t 对象去调用属性 x 时,会先执行__getattribute__方法,然后发现属性 x 不存在,就会调用__getattr__方法。

2.__getattribute__方法

该方法中定义当该类的属性被访问时的行为。

print(t.x)
# __getattribute__
3.__setattr__方法

该方法中定义当一个属性被设置时的行为。

t = Test()
t.x = 1  # 定义属性x并为其赋值
print(t.x)
# __setattr__
# __getattribute__
# 1

我们首先为属性x赋值为1,所以执行时会先调用__setattr__方法,然后我们讲x输出显示时会调用__getattribute__方法。

4.__delattr__方法

该方法中定义当一个属性被删除时的行为。

t = Test()
t.x = 1  # 定义属性x并为其赋值
del t.x  # 删除属性x
# __setattr__
# __delattr__

注意一个陷阱!
使用属性访问方法解决实际问题时注意一个陷阱。以下是中招的一个例子:

class Rectangle:
    def __init__(self, width=0, height=0):
        self.width = width
        self.height = height

    def __setattr__(self, name, value):
        if name == 'square':  # 如果name为square,则将value值赋给长和宽
            self.width = value
            self.height = value
        else:
            self.name = value

    def getArea(self):  # 计算面积的方法
        return self.width * self.height

r = Rectangle(4, 5)
print(r)

该方法执行时会出现无限循环,会导致RecursionError错误。其运行结果如下所示:
在这里插入图片描述
这是因为在__init__中,self.width=width会调用__setattr__方法,此时name不是‘square’,故执行else语句self.name=value,这还是一条赋值语句,继续调用__setattr__函数。这样调用自身的递归就会运行出错。
第一种解决方案: 把self.name = value这句改为super().setattr(name,value),因为父类的__setattr__函数是封装正确的,运行不会出错。

class Rectangle:
    def __init__(self, width=0, height=0):
        self.width = width
        self.height = height

    def __setattr__(self, name, value):
        if name == 'square':
            self.width = value
            self.height = value
        else:
            super().__setattr__(name, value)  # 第一种解决方案

    def getArea(self):
        return self.width * self.height

r = Rectangle(4, 5)
print(r.getArea())  # 20

第二种解决方案: 把self.name = value这句改为self.dict[name] = value。这是因为__dict__中存储了字典类型的数据,那么该步骤执行的时取字典中key和value的值,不会触发__setattr__方法。

class Rectangle:
    def __init__(self, width=0, height=0):
        self.width = width
        self.height = height

    def __setattr__(self, name, value):
        if name == 'square':
            self.width = value
            self.height = value
        else:
            self.__dict__[name] = value  # 第二种解决方案:取字典中键值对的值,不会触发setattr

    def getArea(self):
        return self.width * self.height

r = Rectangle()
r.square = 10
print(r.height, r.width)
print(r.getArea())
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值