魔术方法

魔术方法

属性 含义
__name__ 类、函数、方法等的名字
__module__ 类定义所在的模块名
__class__ 对象或类所属的类
__bases__ 类的基类的元组,舒徐为他们在基类列表中出现的顺序
__doc__ 类、函数的文档字符串,如果没有定义则为None
__mro__ 类的mro,class.mro()返回的结果保存在__mro__中
__dict__ 类或实例的属性,可写的字典

查看属性

方法:__dict__,返回类或者对象的所有成员列表。dir函数就是调用__dir__()。如果提供__dir__(),则返回属性的列表,否则会尽量从__dict__属性中收集信息。

  • 如果dir([object])参数包含方法__dir__,该方法会被调用。如果参数不包含,该方法将会最大限度的收集参数信息。
  • 对于不同类型的对象有不同的行为:如果对象是模块对象,列表包含模块的属性名;如果对象时类型或者类对象,列表包含类的属性名,以及它的基类的属性名;否则列表包含对象的属性名,它的类的属性名和类的基类的属性名。

魔术方法的分类

  1. 创建和销毁:__init__和__del__
  2. hash
  3. bool
  4. 可视化
  5. 运算符重载
  6. 可调用对象
  7. 上下文管理
  8. 反射
  9. 描述器
  10. 其他

hash

  • __hash__内建函数hash调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash
  • __eq__:对应==操作符,判断2个对象值是否相等,返回bool值 __hash__方法只是返回一个hash值作为set的key,但是去重,先要用is判断是不是同一个,然后再用__eq__来判断是否值相等。 hash值相等,只是hash冲突,不能说明两个对象是相等的。因此一般来说提供__hash__方法是为了作为set或者dict的key的,所以去重要同时是提供__eq__方法。 可hash对象必须提供__hash__方法,没有的话,isinstance(p1,collections,HASHABLE)一定为False。
class Point():
	def __init__(self,x,y):
		self.x = x
		self.y = y
	def __repr__(self):
		return 'Point({},{})'.format(self.x,self.y)
	
	def __eq__(self,other):
		return self.x == other.x and self.y == other.y

a = Point(3,4)
b = Point(3,4)

print(a.__repr__(b))
print({a,b})

bool

内建(函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值。没有定义__bool__(),就找__len__()返回长度,非0为真。如果__len__()也没有定义,那么所有实例都返回真。

class A:
    pass

print(bool(A()))

class B:
    def __bool__(self):
        return False

print(bool(B))
print(bool(B()))

class C:
    def __len__(self):
        return 0

print(bool(A()))

可视化 方法|意义 ———-|———— __repr__ |内建函数repr()对一个对象获取字符串表达。如果一个类定义了__repr__但没有有__str__,那么再请求该类的实例的非正式的字符串标识是,也将调用__repr__ __str__ | str函数、内建函数format、print函数调用,需要返回对象的字符串表达 __bytes__ | bytes的时候,返回一个对象的bytes表达,即返回bytes对象

class A:
    def __init__(self):
        self.a ='a'
        self.b = 'b'
    def __eq__(self, other):
        return False

    def __repr__(self):
        return 'REPR:{}({},{})'.format(self.__class__.__name__,self.a,self.b)
    def __str__(self):
        return 'STR:{}({},{})'.format(self.__class__.__name__,self.a,self.b)

print(A())
print([A()])
print(([str(A())]))

运算符重载

operator模块提供一下的特殊方法,可以将类的实例使用下面的操作付来操作:

运算符 特殊方法 含义
<,<=,==,>,>=,!= __lt__,__le__,__eq__,__gt__,__ge__,__ne__ 比较运算符
+,-,*, /,%,//,**,divmod __add__,__sub__,__mul__,__truediv__,__mod__,__floordiv__,__pow__,__divmod__ 算数运算符,位运算符也有对应的方法
+=,-=,*=,/=,%=,//=,**= __iadd__,__isub__,__imul__,__itruediv__,__imod__,__floordiv__,__ipow__
  • 练习:完成point类设计,实现判断点相等的方法,病完成向量的加法
class Point:
		def __init__(self,x,y):
			self.x = x
			self.y = y
			
		def __eq__(self,other):
			return self.x == other.x and self.y == other.y
			
		def __add__(self,other):
			return Point(self.x + other.x,self.y + other.y)
			
		def __repr__(self):
			return 'Point({},{})'.format(self.x,self.y)
	
	p1 = Point(3,4)
	p2 = Point(5,9)
	p3 = Point(5,9)
	print(p1 + p2)
	print(p2 == p3,p2 is p3)
  • 运算符重载应用场景 在做大量连续的运算时,使用运算符这种数学上常见的表达方式非常简单、方便。提供运算符重载,比直接提供加方法更加适合程序员的变成习惯。 int类中几乎实现了所有操作符,可以作为设计类的参考。

容器相关方法

方法 意义
__len__ 內建函数len(),返回对象的长度(>=0的证书),其实即使把对象当做容器类型看,就如同list或者dict。bool()函数调用的时候,如果没有__bool__()方法,就会查看__len__()方法是否存在,存在返回非0为真
__iter__ 迭代容器时调用,返回一个新的迭代器对象
__contains__ in成员运算符,没有实现就调用iter方法遍历
__getitem__ 实现self[key]访问。序列对象,可以接受整数为索引,或者切片。对于set和dict,key就是可hash的类型。可以不存在引发KeyError异常
__setitem__ 和getitem的访问能累死,是设置值的方法
__missing__ 字典使用getitem调用时,key不存在执行此方法
  • 练习:将购物车改造成方便操作的容器类
class Item:
    def __init__(self,**kwargs):
        self._property = kwargs

    def __repr__(self):
        return 'name:{} $:{} color:{}'.format(*self._property.values())

class ShopCar:
    def __init__(self):
        self.items = []

    def additem(self,item:Item):
        self.items.append(item)

    def __iter__(self):
        return iter(self.items)

    def __len__(self):
        return len(self.items)

    def __getitem__(self, index):
        return self.items[index]

    def __setitem__(self,index, value):
        self.items[index] = value


myshopcar = ShopCar()
myphone = Item(name='sumsung',price=4898,color='black')
mycar = Item(name='meserati',price=288,color='red')

myshopcar.additem(myphone)
myshopcar.additem(mycar)
print(myshopcar[1])
# myshopcar[0] = None
for item in myshopcar:
    print(item)

print(len(myshopcar))
print(myshopcar.__dict__)
print(mycar.__dict__,myphone.__dict__)

可调用对象

Python中一切皆对象,函数也是一样。

def foo():
	print(foo.__name__,foo.__name__)
foo()	#等价于foo.__call__()

函数就是对象,对象foo加上(),就是调用对象的__call__方法,在自定义类中定义该方法,那么类的实例就可以像函数对象一样加个括号调用。

  • 练习:定义一个斐波那契数列的类,方便调用,计算第n项
class Fib:
	def __init__(self):
		self.seq = [0,1,1]
	
	def __len__(self):
		return len(self.seq)
		
	def __call__(self,count):
		if count < len(self):#此处的判断是为了减少计算,当已经算过的时候就不再需要重新计算
			return self.seq[count]
		for n in range(len(self)-1,count):#使用实例数列长度来作为起点可以减少计算,不必每次都从头算起。
			self.seq.append(self.seq[n-1]+self.seq[n])
		return self.seq[count]
		
	def __getitem__(self,index):
		if index < 0:
			raise IndexError('Wrong Index')
		if index > len(self):#此处的判断是如果需要的项没有计算出来就立即计算
			return self(index)
		return self.seq[index]
		
	def __iter__(self):
		return iter(self.seq)

上下文管理

文件IO操作可以对文件对象使用上下文管理,使用with…as语法。

with open('test') as f:
	pass
  • 上下文管理对象:当一个对象同事实现了__enter__()和__exit__()方法,他就属于上下文管理的对象
方法 意义
__enter__ 进入与此对象相关的上下文。如果存在该方法,with语法会把该方法的返回值作为绑定到as子句中指定的变量上
__exit__ 退出与此对象相关的上下文。
class Point:
    def __init__(self):
        print('init')
    def __enter__(self):
        print('enter')
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit')

with Point() as f:
    print("let's do it")

根据上例发现,实例化对象的时候,并不会调用enter,当进入with语句块调用enter方法,然后执行语句块,最后退出上下文管理时,调用exit方法。由此表明,with可以开启一个上下文运行环境,在执行前做一些操作,执行后做一些收尾工作。

  • 上下文管理的安全性 分别看下异常和极端情况下对下上下文管理的影响
import sys

class Point:
    def __init__(self):
        print('init')
    def __enter__(self):
        print('enter')
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit')

with Point() as f:
    raise Exception('error')
    # sys.exit() #sys模块方法,可直接退出当前解释器
    print("let's do it")
print('outer le')

上个例子的返回值中观察发现,不管是报错还是强制退出解释器,enter和exit方法都会执行,说明上下文管理很安全

  • enter和exit方法
    1. enter方法没有其他参数,他的返回值就是上下文中使用的对象,with语法会把他的返回值赋给as子句的变量。
    2. exit方法有3个参数,exctype、excvalue、tracback,这三个参数都与异常有关,分别是异常类型、异常的返回值、异常的追踪消息。
    3. exit方法返回一个等效True的值,则压制异常;否则继续抛出异常
  • 练习:使用上下文管理显示加法函数的执行时长
import datetime,time

class TimeIt:
	
	def __init__(self,func):
		self._fn = func
	
    def __enter__(self):
        self.start = datetime.datetime.now()
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        print((datetime.datetime.now() - self.start).total_seconds())

def add(x,y):
    time.sleep(3)
    return x+y

with TimeIt() as f:
    print(add(7,9))
  • 如果要求写成如下形式,代码要如何修改
with TimeIt(add) as foo:
	foo(4,5)

实现如下:

import datetime,time

class TimeIt:
	
	def __init__(self,func):
		self._fn = func
	
	def __call__(self,x,y):
		return self._fn(x,y)
			
    def __enter__(self):
        self.start = datetime.datetime.now()
        return self
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        print((datetime.datetime.now() - self.start).total_seconds())

def add(x,y):
    time.sleep(3)
    return x+y

with TimeIt() as foo:
    print(foo(7,9))

根据以上代码,将类当做装饰器来装饰函数

import datetime,time
from functools import wraps

class TimeIt:
	
	def __init__(self,func):
		self._fn = func
		wraps(func)(self)    #复制add的函数该有的属性到实例add,等价于@warps(func)	
    def __enter__(self):
        self.start = datetime.datetime.now()
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        print((datetime.datetime.now() - self.start).total_seconds())
    
    def __call__(self,x,y):    #使实例可调用的魔法方法
	    return self._fn(x,y)

@TimeIt    #等价于add = TimeIt(add)
def add(x,y):
	"""This is a add function"""
    time.sleep(3)
    return x+y

add(4,5)
print(add.__dict__)
  • 上下文应用场景
    1. 增强功能:在代码执行的前后增加代码,以增强其功能。类死装饰器的功能
    2. 资源管理:打开的资源需要关闭,例如文件对象、网络连接、数据库连接等
    3. 权限验证:在执行代码前,在enter方法中做权限的验证
  • context.contextmanager 它是一个装饰器,装饰一个函数也实现了上下文管理,而且不用像类一样实现enter和exit方法。 对下面的函数有要求,必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。
import contextlib

@contextlib.contextmanager
def foo():
    print('enter')
    yield #yield的值只能有一个,作为enter方法的返回值
    print('exit')

with foo() as f:
	#raise EXCEPTION
    print(f)

上面的代码看起来很正常,当时增加一个异常后发现不能保证exit的执行。

import contextlib

@contextlib.contextmanager
def foo():
    print('enter')
	try:
	    yield #yield的值只能有一个,作为enter方法的返回值
    finaly:
	    print('exit')

with foo() as f:
	raise EXCEPTION
    print(f)

当增加了try\finaly语句之后发现exit又可以执行。在yield发生处为生成器函数增加了上下文管理。

  • 总结:如果业务逻辑简单可以使用函数加装饰器方式,如果业务复杂,用类的方式加enter和exit更加方便。

本文来自投稿,不代表Linux运维部落立场,如若转载,请注明出处:http://www.178linux.com/88766

(0)
上一篇 2017-11-20 21:05
下一篇 2017-11-21 13:15

相关推荐

  • 初识MySQL(一)

        数据库(DATABASES)      数据库是一个单位或是一个应用领域的通用数据处理系统,存储有企业和事业部门、团体和个人的有关数据的集合,该集合中数据是从全局观点出发建立的,按一定的数据模型进行组织、描述和存储。其结构基于数据间的自然联系,从而可提供一切必要的存取路径,且数据不…

    Linux干货 2015-08-24
  • init中的服务故障如何排除

    我们假设ypbind出故障 首先我们设 ypbind服务开机启动 chkconfig yubind on 然后查看服务 chkconfig –list    默认是2345启动   然后更改这个脚本,里面寄一个长期的休眠时间,模仿为错误 sleep 休眠时间 然后重新启动,看看系统如何报错。 然后系统等到要启动该服务的时候回一…

    2017-07-22
  • Linux 练习册1

    一、文件通配符练习题:          1、显示/var目录下所有以 l开头,以一个小写字母结尾,且中间出现至少一位数字的文件或目录。         (1)ls  l*[[:digit:]]*[[:lower:]]    &n…

    2017-07-30
  • LVS NAT + Keepalived HOWTO

    LVS NAT + Keepalived HOWTO   这篇文章主要讲解了,基于LVS/NAT,安装,运行与检测keepalived 目录     1、什么是keepalived?     2、规划你的网络 &nbsp…

    Linux干货 2015-09-21
  • tcpdump诊断nginx问题

    自从上级公司离职后,快一年的时间没有写php程序和搭建LNMP环境,一直在做db运维和运维工具开发方面的事情。 最近决定开发开发一些自动话方面工具,重新拾起php,于是在测试机上搭建一个LNMP环境。 1、 nginx 404错误 一般的出现404错误,找不到页面资源。首先用strace排除了存在文件访问权限的问题,其次nginx的root参数设置也没有任何…

    Linux干货 2016-05-03
  • 密钥和公钥

    安全协议和搭建CA

    Linux干货 2018-01-09