实例方法
1、在定义实例时,会初始化一个实例对象,其第一个参数一般为self,在创建时候将其传给self,
2、实例的方法和属性只能由实例调用
类方法
1、在类定义中,必须用@classmethod装饰器修饰的方法
2、必须至少有一个参数,第一个参数留给了cls,cls指待调用者即类本身
3、cls这个标识符可以是任意合法名称,但是为了易读性,一般写成cls
4、通过cls直接操作类的属性即方法
静态方法
1、在类定义中,必须用@staticmethod装饰器修饰的方法
2、在调用时不用隐形的传入参数,静态方法,只是表明这个方法属于这个名词空间,函数归在一起,方便管理
总结:
类除了普通方法都可以调用,普通方法需要对象的实例作为第一个参数
实例可以调用类中定义的所有的方法和属性,包括静态方法和类方法,普通方法传入实例对象,类方法和静态方法需要找到实例的类
访问控制
私有(private)属性
使用双下划线开头的属性就属于私有属性
私有变量的本质:类定义的时候,如果声明一个实例变量的时候,使用双下划线,python解释器会将其改名,转化名称为_类名__变量名的名称,所以用原来名字就访问不到了。只要使用新变量的名称,还可以在类以外进行访问和更改
保护变量
在变量名前加一个下划线,称之为保护变量。可以将其理解为君子协议,告诉开发者要将其当做私有变量
私有方法
单下划线的定义只是开发者之间的约定,python解释器不做任何解释
双下划线的方法,称之为私有方法,定义改名与私有属性一致,其方法和属性都可以在类的__dict__中找到
私有成员的总结
在python中使用_单下划线或者__双下划线来标识一个成员被保护或者被私有化隐藏起来
但是不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员,python中没有绝对的安全的保护成员或者私有成员,因此,前导的下划线只是一种提醒或者警告,请遵守这个约定,除非真有必要,不要修改或者使用保护成员或者私有成员,更不要修改他们。
python中魔术方法
1、实例化
class A:
# @staticmethod
def __new__(cls, *args, **kwargs): # 静态方法
cls.test = 'abc' # 给类增加属性,但是不好,每次实例化都要创建一次
# return 'abc'
print(cls)
print(args) # ('tom',)
# args = ('jerry', ) # 不会改变a.name的值
print(kwargs) # {'name': 'tom'}
ret = super().__new__(cls)
# ret.age = 100 # 不建议在这里,可以放在__init__方法中
return ret # 调用父类的方法;返回实例
def __init__(self, name): # 将__new__方法返回的实例注入到self
self.name = name
#a = A('tom')
a = A(name='tom')
print(a) # None
print(a.name)
print(A.__dict__)
#{'__module__': '__main__', '__new__': <staticmethod object at 0x00000000027D9A58>, '__init__': <function A.__init__ at 0x00000000027D3F28>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'test': 'abc'}
<Cart Cart [1, 2, 1, 2]>
print(a.age)
__new__方法很少使用,即使创建了该方法,也会使用return super().new(cls)基类object的__new__方法来创建实例并返回。
2、可视化
class Person:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __str__(self):
return 'str: {} {}'.format(self.name, self.age)
def __repr__(self):
return 'repr: {} {}'.format(self.name, self.age)
def __bytes__(self):
return 'bytes: {} {}'.format(self.name, self.age).encode()
a = Person(name='tom')
print(a) # <__main__.Person object at 0x00000000027F0FD0>
#str: tom 18
print(str(a)) # <__main__.Person object at 0x00000000027F0FD0>
print(str(a).encode()) # b'str: tom 18'
print(bytes(a)) # b'bytes: tom 18'
print(repr(a)) # repr: tom 18
print([a]) # [repr: tom 18] print(str([])) 列表中又调用repr
print((a, )) # (repr: tom 18,)
print('{}'.format(a)) # str: tom 18
没有str方法,会找repr方法,但是没有repr方法,直接找基类
注意不能通过判断是否带引号来判断输出值的类型,类型判断要使用type或isinstance
4.hash
__hash__方法,内建函数hash() 调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash。(整数的hash算法是取模)
class Person:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __hash__(self):
return 1
def __eq__(self, other):
return self.age == other.age # 这样写的话,p1 == p2 ——>True
def __repr__(self):
return '<Person {} {}>'.format(self.name, self.age)
print(hash(1), hash(2), hash(5000000)) # 整数的hash算法是取模,除数是62位的整数
print(hash('abc'))
print(hash(('abc', )))
print(hash(Person))
print(hash(Person('tom')))
p1 = Person('tom')
p2 = Person('jerry')
print(hash(p1), hash(p2))
print({123, 123}) # {123}去重了
print({p1, p2}) # {<Person tom 18>, <Person jerry 18>}
没有去重,是因为p1和p2的内容不同(虽然他们的hash值相同);去重两个条件内容相同,hash值相同
print('~~~~~~~~~~', p1 == p2) # 会调用__eq__方法
print({(123, ), (123, )}) # {(123,)} 去重了
set去重需要两个条件: 内容相同,hash值相同
__hash__方法只是返回一个hash值作为set的key,但是去重,还需要__eq__方法来判断两个对象是否相等,hash值相等,只是hash冲突,不能说明两个对象是相等的。
因此,一般来说提供__hash__方法是为了作为set或者dict的key,如果去重,要同时提供__eq__方法。不可hash对象isinstance(p1, collections.Hashable)一定为False
5、bool
class A:
pass
print(bool(A)) # True
print(bool(A())) # True
print(bool([])) # False
class B:
def __bool__(self):
print('in bool')
# return 1
# return bool(self) # 无限递归
return bool(1)
print(bool(B)) # True
#print(bool(B())) # 会出错
if B():
print('b~~~~~~~~~~~~')
class C:
def __len__(self):
return 1 # 必须大于0
print(bool(C))
print(bool(C()))
6.运算符重载
运算符重载
class A:
def __init__(self, age):
self.age = age
def __sub__(self, other):
return self.age - other.age
def __isub__(self, other):
# return A(self.age - other.age) #新实例
# self.age -= other.age
# return self # 31691272 <__main__.A object at 0x0000000001E39208>\
# 31691272 <__main__.A object at 0x0000000001E39208>就地修改
return A(self - other) # 新实例
a1 = A(20)
a2 = A(12)
print(id(a1), a1) # 32150024 <__main__.A object at 0x0000000001EA9208>
#print(a1 - a2) # 8数值
#print(a1.__sub__(a2)) # 8
#print(a2 - a1, a2.__sub__(a1)) # -8
a1 -= a2 # a1__isub__(a2)
print(id(a1), a1) # 32151032 <__main__.A object at 0x0000000001EA95F8>
__isub__方法定义,一般会in-place就地修改自身;如果没有定义__isub__方法,则会调用__sub__方法
7.容器相关方法
使用上下文管理器计算函数执行时间
with声明是从Python2.5开始引进的关键词。你应该遇过这样子的代码:
with open(‘foo.txt’) as bar:
# do something with bar
在with声明的代码段中,我们可以做一些对象的开始操作和清除操作,还能对异常进行处理。
这需要实现两个魔术方法: enter 和 exit。
enter(self)
__enter__会返回一个值,并赋值给as关键词之后的变量。在这里,你可以定义代码段开始的一些操作。
exit(self, exception_type, exception_value, traceback)
__exit__定义了代码段结束后的一些操作,可以这里执行一些清除操作,或者做一些代码段结束后需要立即执行的命令,比如文件的关闭,socket断开等。如果代码段成功结束,那么exception_type, exception_value, traceback 三个参数传进来时都将为None。如果代码段抛出异常,那么传进来的三个参数将分别为: 异常的类型,异常的值,异常的追踪栈。
如果__exit__返回True, 那么with声明下的代码段的一切异常将会被屏蔽。
如果__exit__返回None, 那么如果有异常,异常将正常抛出,这时候with的作用将不会显现出来。
class Timer:
def __init__(self, fn):
self._fn = fn
def __call__(self, *args, **kwargs):
start = time.time()
self._fn(*args, **kwargs)
end = time.time()
return end - start
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
import time
def add(x, y):
time.sleep(1)
return x + y
with Timer(add) as f:
print(f(4, 5))