文章目录
一、常用的方法
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())